diff --git a/.buildbot/android/Dockerfile b/.buildbot/android/Dockerfile deleted file mode 100755 index d657451d..00000000 --- a/.buildbot/android/Dockerfile +++ /dev/null @@ -1,105 +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 - -# pyzbar dependencies -RUN apt-get -y install -qq --no-install-recommends libzbar0 libtool gettext - -RUN 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 f2c08ac5..00000000 --- a/.buildbot/android/build.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash -export LC_ALL=en_US.UTF-8 -export LANG=en_US.UTF-8 - -# buildozer OOM workaround -mkdir -p ~/.gradle -echo "org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8" \ - > ~/.gradle/gradle.properties - -# workaround for symlink -rm -rf src/pybitmessage -mkdir -p src/pybitmessage -cp src/*.py src/pybitmessage -cp -r src/bitmessagekivy src/backend src/mockbm src/images src/pybitmessage - -pushd packages/android - -BUILDMODE=debug - -if [ "$BUILDBOT_JOBNAME" = "android" -a \ - "$BUILDBOT_REPOSITORY" = "https://github.com/Bitmessage/PyBitmessage" -a \ - "$BUILDBOT_BRANCH" = "v0.6" ]; then - sed -e 's/android.release_artifact *=.*/release_artifact = aab/' -i "" buildozer.spec - BUILDMODE=release -fi - -buildozer android $BUILDMODE || exit $? -popd - -mkdir -p ../out -RELEASE_ARTIFACT=$(grep release_artifact packages/android/buildozer.spec |cut -d= -f2|tr -Cd 'a-z') -cp packages/android/bin/*.${RELEASE_ARTIFACT} ../out diff --git a/.buildbot/android/test.sh b/.buildbot/android/test.sh deleted file mode 100755 index 65a0fe7d..00000000 --- a/.buildbot/android/test.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -RELEASE_ARTIFACT=$(grep release_artifact packages/android/buildozer.spec |cut -d= -f2|tr -Cd 'a-z') - -if [ $RELEASE_ARTIFACT = "aab" ]; then - exit -fi - -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 c4dde327..00000000 --- a/.buildbot/appimage/Dockerfile +++ /dev/null @@ -1,28 +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 . . - -CMD .buildbot/tox-bionic/build.sh diff --git a/.buildbot/appimage/build.sh b/.buildbot/appimage/build.sh deleted file mode 100755 index 0dc50f63..00000000 --- a/.buildbot/appimage/build.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/bash - -export APPIMAGE_EXTRACT_AND_RUN=1 -BUILDER=appimage-builder-x86_64.AppImage -RECIPE=packages/AppImage/AppImageBuilder.yml - -git remote add -f upstream https://github.com/Bitmessage/PyBitmessage.git -HEAD="$(git rev-parse HEAD)" -UPSTREAM="$(git merge-base --fork-point upstream/v0.6)" -export APP_VERSION=$(git describe --tags | cut -d- -f1,3 | tr -d v) -[ "$HEAD" != "$UPSTREAM" ] && APP_VERSION="${APP_VERSION}-alpha" - -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 -} - -function build_appimage { - set_sourceline - ./${BUILDER} --recipe ${RECIPE} || exit 1 - rm -rf build -} - -[ -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} - -chmod 1777 /tmp - -export ARCH=amd64 -export APPIMAGE_ARCH=x86_64 -export RUNTIME=${APPIMAGE_ARCH} - -build_appimage - -export ARCH=armhf -export APPIMAGE_ARCH=${ARCH} -export RUNTIME=gnueabihf -export CC=arm-linux-gnueabihf-gcc -export CXX=${CC} - -build_appimage - -export ARCH=arm64 -export APPIMAGE_ARCH=aarch64 -export RUNTIME=${APPIMAGE_ARCH} -export CC=aarch64-linux-gnu-gcc -export CXX=${CC} - -build_appimage - -EXISTING_OWNER=$(stat -c %u ../out) || mkdir -p ../out - -sha256sum PyBitmessage*.AppImage >> ../out/SHA256SUMS -cp PyBitmessage*.AppImage ../out - -if [ ${EXISTING_OWNER} ]; then - chown ${EXISTING_OWNER} ../out/PyBitmessage*.AppImage ../out/SHA256SUMS -fi 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 c9c98b01..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<71' 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 3a83ade7..00000000 --- a/.buildbot/snap/build.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -git remote add -f upstream https://github.com/Bitmessage/PyBitmessage.git -HEAD="$(git rev-parse HEAD)" -UPSTREAM="$(git merge-base --fork-point upstream/v0.6)" -SNAP_DIFF="$(git diff upstream/v0.6 -- packages/snap .buildbot/snap)" - -[ -z "${SNAP_DIFF}" ] && [ $HEAD != $UPSTREAM ] && exit 0 - -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 1acf58dc..00000000 --- a/.buildbot/tox-bionic/Dockerfile +++ /dev/null @@ -1,26 +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 - -ADD . . - -CMD .buildbot/tox-bionic/test.sh 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 fecc0819..00000000 --- a/.buildbot/tox-focal/Dockerfile +++ /dev/null @@ -1,17 +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 . . - -CMD .buildbot/tox-focal/test.sh 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 b15c3b8f..00000000 --- a/.buildbot/tox-jammy/Dockerfile +++ /dev/null @@ -1,16 +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 - -ADD . . - -CMD .buildbot/tox-jammy/test.sh diff --git a/.buildbot/tox-jammy/test.sh b/.buildbot/tox-jammy/test.sh deleted file mode 100755 index ab6134c4..00000000 --- a/.buildbot/tox-jammy/test.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -tox -e lint || 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 ed4f2b89..00000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,41 +0,0 @@ -FROM ubuntu:jammy - -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 c21698ec..00000000 --- a/.dockerignore +++ /dev/null @@ -1,8 +0,0 @@ -bin -build -dist -__pycache__ -.buildozer -.tox -mprofile_* -**.so 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..b0bf5ae4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,27 +1,9 @@ **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 +*.dll \ No newline at end of file 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/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 4f11b199..823608fe 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,117 +1,100 @@ -# PyBitmessage Installation Instructions -- Binary (64bit, no separate installation of dependencies required) - - Windows: https://artifacts.bitmessage.at/winebuild/ - - 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` +#PyBitmessage Installation Instructions -## Notes on the AppImages +For an up-to-date version of these instructions, please visit the +[Bitmessage Wiki](https://bitmessage.org/wiki/Compiling_instructions). -The [AppImage](https://docs.appimage.org/introduction/index.html) -is a bundle, built by the -[appimage-builder](https://github.com/AppImageCrafters/appimage-builder) from -the Ubuntu Bionic deb files, the sources and `bitmsghash.so`, precompiled for -3 architectures, using the `packages/AppImage/AppImageBuilder.yml` recipe. +PyBitmessage can be run either straight from source or from an installed +package. -When you run the appimage the bundle is loop mounted to a location like -`/tmp/.mount_PyBitm97wj4K` with `squashfs-tools`. - -The appimage name has several informational filds: -``` -PyBitmessage--g[-alpha]-.AppImage -``` - -E.g. `PyBitmessage-0.6.3.2-ge571ba8a-x86_64.AppImage` is an appimage, built from -the `v0.6` for x86_64 and `PyBitmessage-0.6.3.2-g9de2aaf1-alpha-aarch64.AppImage` -is one, built from some development branch for arm64. - -You can also build the appimage with local code. For that you need installed -docker: - -``` -$ docker build -t bm-appimage -f .buildbot/appimage/Dockerfile . -$ docker run -t --rm -v "$(pwd)"/dist:/out bm-appimage .buildbot/appimage/build.sh -``` - -The appimages should be in the dist dir. - - -## 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. - -### If checkdeps fails, then verify manually which dependencies are missing from below +##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. + +####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 deleted file mode 100644 index 15a6bf81..00000000 --- a/MANIFEST.in +++ /dev/null @@ -1,5 +0,0 @@ -include COPYING -include README.md -include requirements.txt -recursive-include desktop * -recursive-include packages/apparmor * diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..fa1c4c91 --- /dev/null +++ b/Makefile @@ -0,0 +1,62 @@ +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 diff --git a/README.md b/README.md index 06c97c01..7a161d04 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 "non-content" data, 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/packages/unmaintained/arch.sh b/arch.sh similarity index 100% rename from packages/unmaintained/arch.sh rename to arch.sh diff --git a/packages/unmaintained/archpackage/PKGBUILD b/archpackage/PKGBUILD similarity index 100% rename from packages/unmaintained/archpackage/PKGBUILD rename to archpackage/PKGBUILD diff --git a/buildscripts/README.md b/buildscripts/README.md deleted file mode 100644 index 248d2c41..00000000 --- a/buildscripts/README.md +++ /dev/null @@ -1,2 +0,0 @@ -This directory contains scripts that are helpful for developers when building -or maintaining PyBitmessage. 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/updatetranslations.sh b/buildscripts/updatetranslations.sh deleted file mode 100755 index ba5a3fdb..00000000 --- a/buildscripts/updatetranslations.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash - -if [ ! -f "$1" ]; then - echo "$1 not found, please specify the file name for source" - exit -fi - -srcdir=`mktemp -d` - -unzip "$1" -d $srcdir - -for i in $srcdir/*ts; do - o=`basename $i|cut -b3-` - o="${o,,}" - o="${o//@/_}" - echo "$i -> $o" - mv "$i" "$HOME/src/PyBitmessage/src/translations/$o" -done - -rm -rf -- $srcdir - -lrelease-qt4 "$HOME/src/PyBitmessage/src/translations/bitmessage.pro" 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 deleted file mode 100755 index 0a28a6d2..00000000 --- a/checkdeps.py +++ /dev/null @@ -1,178 +0,0 @@ -#!/usr/bin/env python -""" -Check dependencies 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 - -from src.depends import detectOS, PACKAGES, PACKAGE_MANAGER - - -COMPILING = { - "Debian": "build-essential libssl-dev", - "Ubuntu": "build-essential libssl-dev", - "Fedora": "gcc-c++ redhat-rpm-config python-devel openssl-devel", - "openSUSE": "gcc-c++ libopenssl-devel python-devel", - "optional": False, -} - -# 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 detectPrereqs(missing=True): - available = [] - for module in PACKAGES: - try: - import_module(module) - if not missing: - available.append(module) - except ImportError: - if missing: - available.append(module) - return available - - -def prereqToPackages(): - if not detectPrereqs(): - return - print("%s %s" % ( - PACKAGE_MANAGER[detectOS()], " ".join( - PACKAGES[x][detectOS()] for x in detectPrereqs()))) - - -def compilerToPackages(): - if not detectOS() in COMPILING: - return - print("%s %s" % ( - PACKAGE_MANAGER[detectOS.result], COMPILING[detectOS.result])) - - -def testCompiler(): - if not HAVE_SETUPTOOLS: - # silent, we can't test without setuptools - return True - - bitmsghash = Extension( - 'bitmsghash', - sources=['src/bitmsghash/bitmsghash.cpp'], - libraries=['pthread', 'crypto'], - ) - - dist = Distribution() - dist.ext_modules = [bitmsghash] - cmd = build_ext(dist) - cmd.initialize_options() - cmd.finalize_options() - cmd.force = True - try: - cmd.run() - except CompileError: - return False - else: - fullPath = os.path.join(cmd.build_lib, cmd.get_ext_filename("bitmsghash")) - return os.path.isfile(fullPath) - - -prereqs = detectPrereqs() -compiler = testCompiler() - -if (not compiler or prereqs) and detectOS() in PACKAGE_MANAGER: - print( - "It looks like you're using %s. " - "It is highly recommended to use the package manager\n" - "to install the missing dependencies." % detectOS.result) - -if not compiler: - print( - "Building the bitmsghash module failed.\n" - "You may be missing a C++ compiler and/or the OpenSSL headers.") - -if prereqs: - mandatory = [x for x in prereqs if not PACKAGES[x].get("optional")] - optional = [x for x in prereqs if PACKAGES[x].get("optional")] - if mandatory: - print("Missing mandatory dependencies: %s" % " ".join(mandatory)) - if optional: - print("Missing optional dependencies: %s" % " ".join(optional)) - for package in optional: - print(PACKAGES[package].get('description')) - -# 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: - compilerToPackages() - prereqToPackages() - if prereqs and mandatory: - sys.exit(1) -else: - print("All the dependencies satisfied, you can install PyBitmessage") diff --git a/compiletest.py b/compiletest.py new file mode 100644 index 00000000..91df6c5e --- /dev/null +++ b/compiletest.py @@ -0,0 +1,20 @@ +#!/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: + ctypes.windll.user32.MessageBoxA(0, traceback.format_exc(), "Exception in " + filename, 1) + sys.exit(1) \ No newline at end of file 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/debian.sh b/debian.sh new file mode 100755 index 00000000..9caed2dc --- /dev/null +++ b/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/debian/changelog b/debian/changelog new file mode 100644 index 00000000..9fc04ddb --- /dev/null +++ b/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/debian/compat b/debian/compat new file mode 100644 index 00000000..ec635144 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +9 diff --git a/debian/control b/debian/control new file mode 100644 index 00000000..e72de58a --- /dev/null +++ b/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/debian/copyright b/debian/copyright new file mode 100644 index 00000000..b341b873 --- /dev/null +++ b/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/debian/docs b/debian/docs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/debian/docs @@ -0,0 +1 @@ + diff --git a/debian/manpages b/debian/manpages new file mode 100644 index 00000000..54af5648 --- /dev/null +++ b/debian/manpages @@ -0,0 +1 @@ +man/pybitmessage.1.gz diff --git a/debian/pybm b/debian/pybm new file mode 100644 index 00000000..95e61e54 --- /dev/null +++ b/debian/pybm @@ -0,0 +1,4 @@ +#!/bin/sh +cd /usr/share/pybitmessage +exec python bitmessagemain.py + diff --git a/debian/rules b/debian/rules new file mode 100755 index 00000000..5b29d243 --- /dev/null +++ b/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/debian/source/format b/debian/source/format new file mode 100644 index 00000000..163aaf8d --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/debian/source/include-binaries b/debian/source/include-binaries new file mode 100644 index 00000000..f676fce8 --- /dev/null +++ b/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/desktop/can-icon.svg b/desktop/can-icon.svg index 7c854e34..b4c2bd89 100644 --- a/desktop/can-icon.svg +++ b/desktop/can-icon.svg @@ -9,11 +9,11 @@ xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="793.70081" - height="1122.5197" + width="744.09448819" + height="1052.3622047" id="svg2" version="1.1" - inkscape:version="0.92.1 r" + inkscape:version="0.48.4 r9939" sodipodi:docname="can-icon.svg"> @@ -45,7 +45,7 @@ image/svg+xml - + @@ -55,95 +55,129 @@ id="layer1"> + transform="translate(9.8994953,147.07822)"> + + + + + + + + + + + + id="path3105" + d="M 320.96654,38.913858 51.9306,499.516" + style="fill:none;stroke:#000000;stroke-width:1.54755318px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + + d="M 370.82523,26.309365 105.49275,476.8148" + style="fill:#808080;stroke:#000000;stroke-width:1.54387176px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.11949684" /> + d="M 181.18883,489.0675 437.71536,40.464131" + style="fill:none;stroke:#000000;stroke-width:1.55417168px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.06918239" /> + + d="M 265.93611,524.57433 515.39415,72.865001" + style="fill:#b3b3b3;stroke:#000000;stroke-width:1.54755318px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.07547171" /> + + + d="M 414.69412,653.42954 657.84449,196.92693" + style="fill:none;stroke:#000000;stroke-width:1.54755318px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.21383649" /> + id="path3789" + d="m 432.89205,699.31284 c 0.17105,-7.00112 -5.92592,-25.26221 -12.06513,-36.13638 l -5.18621,-9.18613 6.39957,-11.51258 c 7.57517,-13.6274 209.52877,-392.42011 225.13618,-422.27505 l 10.74842,-20.5603 3.78885,6.2232 c 5.34342,8.77662 10.631,24.86087 11.21936,34.12823 0.47079,7.41538 -0.0329,8.8489 -8.73578,24.86068 -5.07938,9.34538 -58.93099,111.94581 -119.67008,228.00102 -60.73907,116.0552 -110.73218,211.10533 -111.09578,211.22251 -0.3636,0.11718 -0.60636,-2.02717 -0.53946,-4.76523 l 0,0 z M 383.58623,615.47494 629.5772,157.12597" + style="fill:#b3b3b3;stroke:#000000;stroke-width:1.54755318;stroke-opacity:0.21383649" /> + id="path3791" + d="m 408.26635,642.79453 c -2.77517,-3.91094 -9.09904,-11.83931 -14.05304,-17.6186 l -9.00726,-10.5078 11.06562,-19.93851 c 6.08602,-10.96623 60.695,-112.54525 121.35316,-225.73125 60.65817,-113.186 110.57207,-205.90346 110.91978,-206.03881 0.96223,-0.37455 11.55983,11.72618 19.91814,22.74327 l 7.46029,9.83339 -4.53134,9.80365 C 645.37692,218.35301 419.87091,640.72729 416.18164,645.8899 l -2.86948,4.01545 -5.04578,-7.1108 z" + style="fill:#cccccc;stroke:#000000;stroke-width:1.54755318;stroke-opacity:0.21383649" /> + id="path3092" + d="m 371.74406,601.83061 c -21.9825,-21.71509 -57.77462,-48.54944 -91.21678,-68.38776 -7.00036,-4.15271 -12.90367,-7.66798 -13.11848,-7.81172 -0.2148,-0.14376 51.06833,-93.4237 113.96253,-207.28879 C 444.26554,204.47725 499.99579,103.54769 505.21636,94.054416 l 9.49193,-17.260489 12.31004,7.244658 c 16.16532,9.513561 42.09046,26.931205 55.80488,37.492175 14.43229,11.11376 43.65233,37.36326 43.15949,38.77184 -1.19016,3.40165 -242.33727,452.21019 -242.92494,452.11733 -0.3889,-0.0615 -5.48007,-4.82664 -11.3137,-10.58932 l 0,0 z" + style="fill:#b3b3b3" /> - - - + id="path3094" + d="m 370.32984,599.64651 c -13.89342,-13.08279 -30.14402,-26.59088 -31.98957,-26.59088 -0.69915,0 -2.08148,-1.29748 -3.07184,-2.88329 -0.99036,-1.58582 -2.41623,-2.50287 -3.16861,-2.03787 -0.75238,0.46499 -1.36796,0.23149 -1.36796,-0.51889 0,-1.85189 -23.8082,-18.60406 -44.54773,-31.34517 -9.3338,-5.73412 -17.16014,-10.54638 -17.39186,-10.69391 -0.41663,-0.26524 97.56855,-178.64134 195.50085,-355.89748 48.34786,-87.508983 50.71018,-91.472283 53.61341,-89.948371 7.3399,3.852715 34.04527,20.993951 48.43449,31.088381 16.63992,11.67337 56.24235,44.92528 57.5435,48.31602 0.74942,1.95294 -57.86712,112.36525 -180.22205,339.47312 -32.74563,60.78045 -59.93672,110.92569 -60.42466,111.43384 -0.48793,0.50817 -6.29652,-4.1698 -12.90797,-10.3955 z" + style="fill:#e6e6e6" /> diff --git a/desktop/pybitmessage.desktop b/desktop/pybitmessage.desktop index c30276e4..a97bd664 100644 --- a/desktop/pybitmessage.desktop +++ b/desktop/pybitmessage.desktop @@ -1,9 +1,9 @@ [Desktop Entry] Type=Application Name=PyBitmessage -GenericName=Bitmessage Client +GenericName=PyBitmessage Comment=Send encrypted messages Exec=pybitmessage %F Icon=pybitmessage Terminal=false -Categories=Office;Email;Network; +Categories=Office;Email; diff --git a/dev/README.md b/dev/README.md deleted file mode 100644 index 41bf59c8..00000000 --- a/dev/README.md +++ /dev/null @@ -1,3 +0,0 @@ -This directory contains code for future features, but it's not integrated into -PyBitmessage and may not work at all. Developers can look at it if they are -interested. diff --git a/dev/bloomfiltertest.py b/dev/bloomfiltertest.py deleted file mode 100644 index 19c93c76..00000000 --- a/dev/bloomfiltertest.py +++ /dev/null @@ -1,66 +0,0 @@ -""" -dev/bloomfiltertest.py -====================== - -""" - -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')) - -conn.text_factory = str -cur = conn.cursor() -rawlen = 0 -itemcount = 0 - -cur.execute('''SELECT COUNT(hash) FROM inventory''') -for row in cur.fetchall(): - itemcount = row[0] - -filtersize = 1000 * (int(itemcount / 1000) + 1) -errorrate = 1.0 / 1000.0 - -bf1 = BloomFilter1(capacity=filtersize, error_rate=errorrate) -bf2 = BloomFilter2(capacity=filtersize, error_rate=errorrate) - -item = '''SELECT hash FROM inventory''' -cur.execute(item, '') -bf1time = 0 -bf2time = 0 -for row in cur.fetchall(): - rawlen += len(row[0]) - try: - times = [time()] - bf1.add(row[0]) - times.append(time()) - bf2.add(row[0]) - times.append(time()) - bf1time += times[1] - times[0] - bf2time += times[2] - times[1] - except IndexError: - pass - -# 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%%" % - (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%%" % - (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)) diff --git a/dev/powinterrupttest.py b/dev/powinterrupttest.py deleted file mode 100644 index bfb55d78..00000000 --- a/dev/powinterrupttest.py +++ /dev/null @@ -1,49 +0,0 @@ -import ctypes -import hashlib -from multiprocessing import current_process -import os -import signal -from struct import unpack, pack -from threading import current_thread - -shutdown = 0 - - -def signal_handler(signal, frame): - global shutdown - 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": - return - shutdown = 1 - - -def _doCPoW(target, initialHash): - # 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") - for c in range(0, 200000): - 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") - return [trialValue, nonce] - - -signal.signal(signal.SIGINT, signal_handler) -signal.signal(signal.SIGTERM, signal_handler) - -bso = ctypes.CDLL(os.path.join("bitmsghash", "bitmsghash.so")) - -bmpow = bso.BitmessagePOW -bmpow.restype = ctypes.c_ulonglong - -_doCPoW(2**44, "") diff --git a/dev/ssltest.py b/dev/ssltest.py deleted file mode 100644 index 7268b65f..00000000 --- a/dev/ssltest.py +++ /dev/null @@ -1,104 +0,0 @@ -import os -import select -import socket -import ssl -import sys -import traceback - -HOST = "127.0.0.1" -PORT = 8912 - - -def sslProtocolVersion(): - # sslProtocolVersion - 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): - # this means any SSL/TLS. SSLv2 and 3 are excluded with an option after context is created - return ssl.PROTOCOL_SSLv23 - else: - # this means TLSv1, there is no way to set "TLSv1 or higher" or - # "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) - sock.bind((HOST, PORT)) - sock.listen(0) - return sock - - -def sslHandshake(sock, server=False): - 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) - 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') - - while True: - try: - sslSock.do_handshake() - break - except ssl.SSLWantReadError: - print("Waiting for SSL socket handhake read") - select.select([sslSock], [], [], 10) - except ssl.SSLWantWriteError: - print("Waiting for SSL socket handhake write") - select.select([], [sslSock], [], 10) - except Exception: - print("SSL socket handhake failed, shutting down connection") - traceback.print_exc() - return - print("Success!") - return sslSock - - -if __name__ == "__main__": - if len(sys.argv) != 2: - print("Usage: ssltest.py client|server") - sys.exit(0) - elif sys.argv[1] == "server": - serversock = listen() - while True: - print("Waiting for connection") - sock, addr = serversock.accept() - print("Got connection from %s:%i" % (addr[0], addr[1])) - sslSock = sslHandshake(sock, True) - if sslSock: - sslSock.shutdown(socket.SHUT_RDWR) - sslSock.close() - elif sys.argv[1] == "client": - sock = connect() - sslSock = sslHandshake(sock, False) - if sslSock: - sslSock.shutdown(socket.SHUT_RDWR) - sslSock.close() - else: - print("Usage: ssltest.py client|server") - sys.exit(0) diff --git a/docker-test.sh b/docker-test.sh deleted file mode 100755 index 18b6569a..00000000 --- a/docker-test.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -DOCKERFILE=.buildbot/tox-bionic/Dockerfile - -docker build -t pybm/tox -f $DOCKERFILE . - -if [ $? -gt 0 ]; then - docker build --no-cache -t pybm/tox -f $DOCKERFILE . -fi - -docker run --rm -it pybm/tox 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/packages/unmaintained/ebuild.sh b/ebuild.sh similarity index 94% rename from packages/unmaintained/ebuild.sh rename to ebuild.sh index f26fe58e..cd73685b 100755 --- a/packages/unmaintained/ebuild.sh +++ b/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/generate.sh b/generate.sh similarity index 100% rename from packages/unmaintained/generate.sh rename to generate.sh 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/buildscripts/osx.sh b/osx.sh similarity index 100% rename from buildscripts/osx.sh rename to osx.sh 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 deleted file mode 100644 index 2905ec20..00000000 --- a/packages/README.md +++ /dev/null @@ -1,36 +0,0 @@ -The `generate.sh` script is obsolete, but is included for historical reasons. - -Maintained packages can be obtained: - -Windows: -======== - -https://github.com/Bitmessage/PyBitmessage/releases - -Works on Windows XP or higher. - - -OSX: -==== - -https://github.com/Bitmessage/PyBitmessage/releases - -Works on OSX 10.7.5 or higher - - -Arch linux: -=========== - -Releases matching PyBitmessage releases: - -https://aur.archlinux.org/packages/pybitmessage-git/ - -Development snapshot equivalent to the v0.6 git branch: - -https://aur.archlinux.org/packages/pybitmessage-dev-git/ - - -FreeBSD: -======== - -Use the FreeBSD ports. diff --git a/packages/android/buildozer.spec b/packages/android/buildozer.spec deleted file mode 100644 index afdd4282..00000000 --- a/packages/android/buildozer.spec +++ /dev/null @@ -1,359 +0,0 @@ -[app] - -# (str) Title of your application -title = PyBitmessage Mock - -# (str) Package name -package.name = pybitmessagemock - -# (str) Package domain (needed for android/ios packaging) -package.domain = at.bitmessage - -# (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,json - -# (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.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,sqlite3,kivymd==1.0.2,Pillow,opencv,kivy-garden.qrcode,qrcode,typing_extensions,pypng,pyzbar,xcamera,zbarcam - -# (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 = 33 - -# (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 = [:] - -android.release_artifact = apk - -# -# 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 deleted file mode 100644 index d15c3a48..00000000 --- a/packages/collectd/pybitmessagestatus.py +++ /dev/null @@ -1,68 +0,0 @@ -#!/usr/bin/env python2.7 - -import collectd -import json -import xmlrpclib - -pybmurl = "" -api = "" - - -def init_callback(): - global api - api = xmlrpclib.ServerProxy(pybmurl) - collectd.info('pybitmessagestatus.py init done') - - -def config_callback(ObjConfiguration): - global pybmurl - apiUsername = "" - apiPassword = "" - apiInterface = "127.0.0.1" - apiPort = 8445 - for node in ObjConfiguration.children: - key = node.key.lower() - if key.lower() == "apiusername" and node.values: - apiUsername = node.values[0] - elif key.lower() == "apipassword" and node.values: - apiPassword = node.values[0] - elif key.lower() == "apiinterface" and node.values: - apiInterface = node.values[0] - elif key.lower() == "apiport" and node.values: - apiPort = node.values[0] - pybmurl = "http://{}:{}@{}:{}/".format(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 - collectd.info("Exception loading or parsing JSON") - return - - for i in ["networkConnections", "numberOfPubkeysProcessed", - "numberOfMessagesProcessed", "numberOfBroadcastsProcessed"]: - metric = collectd.Values() - metric.plugin = "pybitmessagestatus" - if i[0:6] == "number": - metric.type = 'counter' - else: - metric.type = 'gauge' - metric.type_instance = i.lower() - try: - metric.values = [clientStatus[i]] - except (TypeError, KeyError): - collectd.info("Value for %s missing" % (i)) - metric.dispatch() - - -if __name__ == "__main__": - main() -else: - collectd.register_init(init_callback) - collectd.register_config(config_callback) - collectd.register_read(read_callback) diff --git a/packages/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 deleted file mode 100644 index fb2b572d..00000000 --- a/packages/pyinstaller/bitmessagemain.spec +++ /dev/null @@ -1,144 +0,0 @@ -# -*- 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) - -snapshot = False - -hookspath = os.path.join(spec_root, 'hooks') - -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 -) - - -def addTranslations(): - extraDatas = [] - for file_ in os.listdir(os.path.join(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": - continue - extraDatas.append(( - os.path.join('translations', file_), - os.path.join(qtdir, 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') -] - -# append the translations directory -a.datas += addTranslations() -a.datas += [('default.ini', os.path.join(srcPath, 'default.ini'), 'DATA')] - -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") - - -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' -) 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 47c27936..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 | cut -d- -f1,3 | 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/systemd/bitmessage.service b/packages/systemd/bitmessage.service deleted file mode 100644 index 1a9f7f47..00000000 --- a/packages/systemd/bitmessage.service +++ /dev/null @@ -1,18 +0,0 @@ -[Unit] -Description=Bitmessage Daemon -After=network.target auditd.service - -[Service] -ExecStart=/usr/bin/python2 /usr/src/PyBitmessage/src/bitmessagemain.py -ExecReload=/bin/kill -HUP $MAINPID -KillMode=process -Restart=on-failure -Type=forking -PIDFile=/var/lib/bitmessage/.config/PyBitmessage/singleton.lock -User=bitmessage -Group=nogroup -WorkingDirectory=/var/lib/bitmessage -Environment="HOME=/var/lib/bitmessage" - -[Install] -WantedBy=multi-user.target diff --git a/packages/upstart/bitmessage.conf b/packages/upstart/bitmessage.conf deleted file mode 100644 index 970fd4a7..00000000 --- a/packages/upstart/bitmessage.conf +++ /dev/null @@ -1,37 +0,0 @@ -# This is an upstart script for bitmessage for when using daemon mode -# Bitmessage forks more than twice before daemonizing, so a workaround is -# necessary - -description "bitmessage" -author "Peter Surda" - -start on (local-filesystems and net-device-up) -stop on runlevel [!2345] - -setuid bitmessage -setgid bitmessage - -chdir /home/bitmessage -env HOME="/home/bitmessage" - -pre-start script - /usr/src/PyBitmessage/src/bitmessagemain.py -end script - -script - while [ ! -f $HOME/.config/PyBitmessage/singleton.lock ]; do - sleep 1 - done - while [ -f $HOME/.config/PyBitmessage/singleton.lock ]; do - sleep 1 - done -end script - -post-stop script - if [ -f $HOME/.config/PyBitmessage/singleton.lock ]; then - pid=`lsof -F p $HOME/.config/PyBitmessage/singleton.lock|cut -b2-` - if [ -n "$pid" ]; then - kill $pid - fi - fi -end script diff --git a/packages/unmaintained/puppy.sh b/puppy.sh similarity index 99% rename from packages/unmaintained/puppy.sh rename to puppy.sh index 1d3bdd31..a78e021c 100755 --- a/packages/unmaintained/puppy.sh +++ b/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/packages/unmaintained/puppypackage/icon14.xpm b/puppypackage/icon14.xpm similarity index 100% rename from packages/unmaintained/puppypackage/icon14.xpm rename to puppypackage/icon14.xpm diff --git a/packages/unmaintained/puppypackage/pybitmessage-0.3.5.pet.specs b/puppypackage/pybitmessage-0.3.5.pet.specs similarity index 100% rename from packages/unmaintained/puppypackage/pybitmessage-0.3.5.pet.specs rename to puppypackage/pybitmessage-0.3.5.pet.specs 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/pyinstaller/bitmessagemain.spec b/pyinstaller/bitmessagemain.spec new file mode 100644 index 00000000..0b41bb00 --- /dev/null +++ b/pyinstaller/bitmessagemain.spec @@ -0,0 +1,58 @@ +srcPath = "C:\\src\\PyBitmessage\\src\\" +qtPath = "C:\\Qt\\4.8.6\\" +openSSLPath = "C:\\OpenSSL-1.0.2e\\" +outPath = "C:\\src\\PyInstaller\\bitmessagemain" + +# -*- mode: python -*- +a = Analysis([srcPath + 'bitmessagemain.py'], + pathex=[outPath], + hiddenimports=[], + hookspath=None, + runtime_hooks=None) + +# fix duplicates +for d in a.datas: + if 'pyconfig' in d[0]: + a.datas.remove(d) + break + +def addTranslations(): + import os + extraDatas = [] + for file in os.listdir(srcPath + 'translations'): + if file[-3:] != ".qm": + continue + extraDatas.append(('translations\\'+file, srcPath + 'translations\\' + file, 'DATA')) + for file in os.listdir(qtPath + 'translations'): + if file[0:3] != "qt_" or file[5:8] != ".qm": + continue + extraDatas.append(('translations\\'+file, qtPath + 'translations\\' + file, 'DATA')) + return extraDatas + +def addUIs(): + import os + extraDatas = [] + for file in os.listdir(srcPath + 'bitmessageqt'): + if file[-3:] != ".ui": + continue + extraDatas.append(('ui\\'+file, srcPath + 'bitmessageqt\\' + file, 'DATA')) + return extraDatas + +# append the translations directory +a.datas += addTranslations() +a.datas += addUIs() + +a.binaries.append(('msvcr120.dll', 'C:\\WINDOWS\\system32\\msvcr120.dll', 'BINARY')) + +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + a.binaries + [('libeay32.dll', openSSLPath + 'libeay32.dll', 'BINARY'), ('bitmsghash\\bitmsghash32.dll', srcPath + 'bitmsghash\\bitmsghash32.dll', 'BINARY'), ('bitmsghash\\bitmsghash.cl', srcPath + 'bitmsghash\\bitmsghash.cl', 'BINARY'), ('sslkeys\\cert.pem', srcPath + 'sslkeys\\cert.pem', 'BINARY'), ('sslkeys\\key.pem', srcPath + 'sslkeys\\key.pem', 'BINARY')], + name='Bitmessage.exe', + debug=False, + strip=None, + upx=False, + console=False, icon= srcPath + 'images\\can-icon.ico') diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index c787d2dd..00000000 --- a/requirements.txt +++ /dev/null @@ -1,8 +0,0 @@ -coverage -psutil -pycryptodome -PyQt5;python_version>="3.7" and platform_machine=="x86_64" -mock;python_version<="2.7" -python_prctl;platform_system=="Linux" -six -xvfbwrapper;platform_system=="Linux" diff --git a/packages/unmaintained/rpm.sh b/rpm.sh similarity index 100% rename from packages/unmaintained/rpm.sh rename to rpm.sh diff --git a/packages/unmaintained/rpmpackage/pybitmessage.spec b/rpmpackage/pybitmessage.spec similarity index 100% rename from packages/unmaintained/rpmpackage/pybitmessage.spec rename to rpmpackage/pybitmessage.spec 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/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 deleted file mode 100644 index 30436bec..00000000 --- a/setup.py +++ /dev/null @@ -1,193 +0,0 @@ -#!/usr/bin/env python2.7 - -import os -import platform -import shutil -import sys - -from importlib import import_module -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: - os.makedirs('desktop/icons/scalable') - except os.error: - pass - shutil.copyfile( - 'desktop/can-icon.svg', 'desktop/icons/scalable/pybitmessage.svg') - try: - os.makedirs('desktop/icons/24x24') - except os.error: - pass - shutil.copyfile( - 'desktop/icon24.png', 'desktop/icons/24x24/pybitmessage.png') - - return install.run(self) - - -if __name__ == "__main__": - here = os.path.abspath(os.path.dirname(__file__)) - 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'] - packages = [ - 'pybitmessage', - 'pybitmessage.bitmessageqt', - 'pybitmessage.bitmessagecurses', - 'pybitmessage.fallback', - 'pybitmessage.messagetypes', - 'pybitmessage.network', - 'pybitmessage.plugins', - 'pybitmessage.pyelliptic', - 'pybitmessage.storage' - ] - 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") - except ImportError: - try: - import_module('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 - - dist = setup( - name='pybitmessage', - version=softwareVersion, - description="Reference client for Bitmessage: " - "a P2P communications protocol", - long_description=README, - license='MIT', - # TODO: add author info - url='https://bitmessage.org', - # TODO: add keywords - install_requires=installRequires, - tests_require=requirements, - test_suite='tests.unittest_discover', - extras_require=EXTRAS_REQUIRE, - classifiers=[ - "License :: OSI Approved :: MIT License" - "Operating System :: OS Independent", - "Programming Language :: Python :: 2.7 :: Only", - "Topic :: Internet", - "Topic :: Security :: Cryptography", - "Topic :: Software Development :: Libraries :: Python Modules", - ], - package_dir={'pybitmessage': 'src'}, - packages=packages, - package_data=package_data, - data_files=data_files, - ext_modules=[bitmsghash], - zip_safe=False, - entry_points={ - 'bitmessage.gui.menu': [ - 'address.qrcode = pybitmessage.plugins.menu_qrcode [qrcode]' - ], - 'bitmessage.notification.message': [ - 'notify2 = pybitmessage.plugins.notification_notify2' - '[gir, notify2]' - ], - 'bitmessage.notification.sound': [ - 'theme.canberra = pybitmessage.plugins.sound_canberra', - 'file.gstreamer = pybitmessage.plugins.sound_gstreamer' - '[gir]', - 'file.fallback = pybitmessage.plugins.sound_playfile' - '[sound]' - ], - 'bitmessage.indicator': [ - 'libmessaging =' - 'pybitmessage.plugins.indicator_libmessaging [gir]' - ], - '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 [] - }, - scripts=['src/pybitmessage'], - cmdclass={'install': InstallCmd}, - command_options={ - 'build_sphinx': { - 'source_dir': ('setup.py', 'docs')} - } - ) diff --git a/packages/unmaintained/slack.sh b/slack.sh similarity index 100% rename from packages/unmaintained/slack.sh rename to slack.sh diff --git a/packages/unmaintained/slackpackage/doinst.sh b/slackpackage/doinst.sh similarity index 100% rename from packages/unmaintained/slackpackage/doinst.sh rename to slackpackage/doinst.sh diff --git a/packages/unmaintained/slackpackage/slack-desc b/slackpackage/slack-desc similarity index 100% rename from packages/unmaintained/slackpackage/slack-desc rename to slackpackage/slack-desc diff --git a/src/__init__.py b/src/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/addresses.py b/src/addresses.py index 885c1f64..fa87677a 100644 --- a/src/addresses.py +++ b/src/addresses.py @@ -1,183 +1,153 @@ -""" -Operations with addresses -""" -# pylint: disable=inconsistent-return-statements - -import logging +import hashlib +from struct import * +from pyelliptic import arithmetic from binascii import hexlify, unhexlify -from struct import pack, unpack - -try: - from highlevelcrypto import double_sha512 -except ImportError: - from .highlevelcrypto import double_sha512 -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 encodeAddress(version, stream, ripe): - """Convert ripe to address""" +def calculateInventoryHash(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): 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 - checksum = double_sha512(storedBinaryData)[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) + sha = hashlib.new('sha512') + sha.update(storedBinaryData) + currentHash = sha.digest() + sha = hashlib.new('sha512') + sha.update(currentHash) + checksum = sha.digest()[0:4] + 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() @@ -187,82 +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:] - if checksum != double_sha512(data[:-4])[0: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 f9bf55de..a05b1301 100644 --- a/src/api.py +++ b/src/api.py @@ -1,379 +1,68 @@ # 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. """ -import base64 -import errno -import hashlib +if __name__ == "__main__": + print comment + import sys + sys.exit(0) + +from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler, SimpleXMLRPCServer import json -import random -import socket -import subprocess # nosec B404 +from binascii import hexlify + +import shared import time -from binascii import hexlify, unhexlify -from struct import pack, unpack - -import six -from six.moves import configparser, http_client, xmlrpc_server - +from addresses import decodeAddress,addBMIfNotPresent,decodeVarint,calculateInventoryHash,varintDecodeError import helper_inbox import helper_sent -import protocol -import proofofwork -import queues -import shared +import hashlib -import shutdown -import state -from addresses import ( - addBMIfNotPresent, - decodeAddress, - decodeVarint, - varintDecodeError -) -from bmconfigparser import config +from pyelliptic.openssl import OpenSSL +from struct import pack + +# Classes +from helper_sql import sqlQuery,sqlExecute,SqlBulkExecute,sqlStoredProcedure from debug import logger -from defaults import ( - networkDefaultProofOfWorkNonceTrialsPerByte, - networkDefaultPayloadLengthExtraBytes) -from helper_sql import ( - SqlBulkExecute, sqlExecute, sqlQuery, sqlStoredProcedure, sql_ready) -from highlevelcrypto import calculateInventoryHash - -try: - from network import connectionpool -except ImportError: - connectionpool = None - -from network import stats, StoppableThread, invQueue -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""" - - 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 +class StoppableXMLRPCServer(SimpleXMLRPCServer): + def serve_forever(self): + while shared.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(): @@ -390,43 +79,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 @@ -442,1151 +114,937 @@ 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 == shared.config.get('bitmessagesettings', 'apiusername') and password == shared.config.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) + return text.decode(decode_type) 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 + def HandleListAddresses(self, method): + data = '{"addresses":[' + configSections = shared.config.sections() + for addressInKeysFile in configSections: + if addressInKeysFile != 'bitmessagesettings': + status, addressVersionNumber, streamNumber, hash01 = decodeAddress( + addressInKeysFile) + if len(data) > 20: + data += ',' + if shared.config.has_option(addressInKeysFile, 'chan'): + chan = shared.config.getboolean(addressInKeysFile, 'chan') + else: + chan = False + label = shared.config.get(addressInKeysFile, 'label') + if method == 'listAddresses2': + label = label.encode('base64') + data += json.dumps({'label': label, 'address': addressInKeysFile, 'stream': + streamNumber, 'enabled': shared.config.getboolean(addressInKeysFile, 'enabled'), 'chan': chan}, indent=4, separators=(',', ': ')) + data += ']}' + return data - @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': - 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} - - # 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':label.encode('base64'), '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', '')) + shared.UISignalQueue.put(('rerenderMessagelistFromLabels','')) + shared.UISignalQueue.put(('rerenderMessagelistToLabels','')) + shared.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', '')) + shared.UISignalQueue.put(('rerenderMessagelistFromLabels','')) + shared.UISignalQueue.put(('rerenderMessagelistToLabels','')) + shared.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( - networkDefaultProofOfWorkNonceTrialsPerByte * totalDifficulty) - payloadLengthExtraBytes = self.config.get( - 'bitmessagesettings', 'defaultpayloadlengthextrabytes' - ) if not smallMessageDifficulty else int( - 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 = shared.config.get( + 'bitmessagesettings', 'defaultnoncetrialsperbyte') + payloadLengthExtraBytes = shared.config.get( + 'bitmessagesettings', 'defaultpayloadlengthextrabytes') + elif len(params) == 2: + label, eighteenByteRipe = params + nonceTrialsPerByte = shared.config.get( + 'bitmessagesettings', 'defaultnoncetrialsperbyte') + payloadLengthExtraBytes = shared.config.get( + 'bitmessagesettings', 'defaultpayloadlengthextrabytes') + elif len(params) == 3: + label, eighteenByteRipe, totalDifficulty = params + nonceTrialsPerByte = int( + shared.networkDefaultProofOfWorkNonceTrialsPerByte * totalDifficulty) + payloadLengthExtraBytes = shared.config.get( + 'bitmessagesettings', 'defaultpayloadlengthextrabytes') + elif len(params) == 4: + label, eighteenByteRipe, totalDifficulty, smallMessageDifficulty = params + nonceTrialsPerByte = int( + shared.networkDefaultProofOfWorkNonceTrialsPerByte * totalDifficulty) + payloadLengthExtraBytes = int( + shared.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 + shared.apiAddressGeneratorReturnQueue.queue.clear() streamNumberForAddress = 1 - queues.addressGeneratorQueue.put(( - 'createRandomAddress', 4, streamNumberForAddress, label, 1, "", - eighteenByteRipe, nonceTrialsPerByte, payloadLengthExtraBytes - )) - return queues.apiAddressGeneratorReturnQueue.get() + shared.addressGeneratorQueue.put(( + 'createRandomAddress', 4, streamNumberForAddress, label, 1, "", eighteenByteRipe, nonceTrialsPerByte, payloadLengthExtraBytes)) + return shared.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( - networkDefaultProofOfWorkNonceTrialsPerByte * totalDifficulty) - payloadLengthExtraBytes = self.config.get( - 'bitmessagesettings', 'defaultpayloadlengthextrabytes' - ) if not smallMessageDifficulty else int( - 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 = shared.config.get( + 'bitmessagesettings', 'defaultnoncetrialsperbyte') + payloadLengthExtraBytes = shared.config.get( + 'bitmessagesettings', 'defaultpayloadlengthextrabytes') + elif len(params) == 2: + passphrase, numberOfAddresses = params + addressVersionNumber = 0 + streamNumber = 0 + eighteenByteRipe = False + nonceTrialsPerByte = shared.config.get( + 'bitmessagesettings', 'defaultnoncetrialsperbyte') + payloadLengthExtraBytes = shared.config.get( + 'bitmessagesettings', 'defaultpayloadlengthextrabytes') + elif len(params) == 3: + passphrase, numberOfAddresses, addressVersionNumber = params + streamNumber = 0 + eighteenByteRipe = False + nonceTrialsPerByte = shared.config.get( + 'bitmessagesettings', 'defaultnoncetrialsperbyte') + payloadLengthExtraBytes = shared.config.get( + 'bitmessagesettings', 'defaultpayloadlengthextrabytes') + elif len(params) == 4: + passphrase, numberOfAddresses, addressVersionNumber, streamNumber = params + eighteenByteRipe = False + nonceTrialsPerByte = shared.config.get( + 'bitmessagesettings', 'defaultnoncetrialsperbyte') + payloadLengthExtraBytes = shared.config.get( + 'bitmessagesettings', 'defaultpayloadlengthextrabytes') + elif len(params) == 5: + passphrase, numberOfAddresses, addressVersionNumber, streamNumber, eighteenByteRipe = params + nonceTrialsPerByte = shared.config.get( + 'bitmessagesettings', 'defaultnoncetrialsperbyte') + payloadLengthExtraBytes = shared.config.get( + 'bitmessagesettings', 'defaultpayloadlengthextrabytes') + elif len(params) == 6: + passphrase, numberOfAddresses, addressVersionNumber, streamNumber, eighteenByteRipe, totalDifficulty = params + nonceTrialsPerByte = int( + shared.networkDefaultProofOfWorkNonceTrialsPerByte * totalDifficulty) + payloadLengthExtraBytes = shared.config.get( + 'bitmessagesettings', 'defaultpayloadlengthextrabytes') + elif len(params) == 7: + passphrase, numberOfAddresses, addressVersionNumber, streamNumber, eighteenByteRipe, totalDifficulty, smallMessageDifficulty = params + nonceTrialsPerByte = int( + shared.networkDefaultProofOfWorkNonceTrialsPerByte * totalDifficulty) + payloadLengthExtraBytes = int( + shared.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.') - 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. - """ + 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.') + shared.apiAddressGeneratorReturnQueue.queue.clear() + logger.debug('Requesting that the addressGenerator create %s addresses.', numberOfAddresses) + shared.addressGeneratorQueue.put( + ('createDeterministicAddresses', addressVersionNumber, streamNumber, + 'unused API address', numberOfAddresses, passphrase, eighteenByteRipe, nonceTrialsPerByte, payloadLengthExtraBytes)) + data = '{"addresses":[' + queueReturn = shared.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.') - 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 - )) - return queues.apiAddressGeneratorReturnQueue.get() - - @command('createChan') - def HandleCreateChan(self, passphrase): - """ - Creates a new chan. passphrase must be base64 encoded. - Returns the corresponding Bitmessage address. - """ + raise APIError(3, ' The stream number must be 1. Others aren\'t supported.') + shared.apiAddressGeneratorReturnQueue.queue.clear() + logger.debug('Requesting that the addressGenerator create %s addresses.', numberOfAddresses) + shared.addressGeneratorQueue.put( + ('getDeterministicAddress', addressVersionNumber, + streamNumber, 'unused API address', numberOfAddresses, passphrase, eighteenByteRipe)) + return shared.apiAddressGeneratorReturnQueue.get() + 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 - )) - queueReturn = queues.apiAddressGeneratorReturnQueue.get() - try: - return queueReturn[0] - except IndexError: + shared.apiAddressGeneratorReturnQueue.queue.clear() + logger.debug('Requesting that the addressGenerator create chan %s.', passphrase) + shared.addressGeneratorQueue.put(('createChan', addressVersionNumber, streamNumber, label, passphrase)) + queueReturn = shared.apiAddressGeneratorReturnQueue.get() + 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: + shared.apiAddressGeneratorReturnQueue.queue.clear() + shared.addressGeneratorQueue.put(('joinChan', suppliedAddress, label, passphrase)) + addressGeneratorReturnValue = shared.apiAddressGeneratorReturnQueue.get() + + if addressGeneratorReturnValue == '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 shared.config.has_section(address): + raise APIError(13, 'Could not find this address in your keys.dat file.') + if not shared.safeConfigGetBoolean(address, 'chan'): + raise APIError(25, 'Specified address is not a chan address. Use deleteAddress API call instead.') + shared.config.remove_section(address) + with open(shared.appdata + 'keys.dat', 'wb') as configfile: + shared.config.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 shared.config.has_section(address): + raise APIError(13, 'Could not find this address in your keys.dat file.') + shared.config.remove_section(address) + with open(shared.appdata + 'keys.dat', 'wb') as configfile: + shared.config.write(configfile) + shared.UISignalQueue.put(('rerenderMessagelistFromLabels','')) + shared.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': subject.encode( + 'base64'), 'message': message.encode('base64'), '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) + shared.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':subject.encode('base64'), 'message':message.encode('base64'), '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':subject.encode('base64'), 'message':message.encode('base64'), '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':subject.encode('base64'), 'message':message.encode('base64'), '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':subject.encode('base64'), 'message':message.encode('base64'), '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':subject.encode('base64'), 'message':message.encode('base64'), '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':subject.encode('base64'), 'message':message.encode('base64'), '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): - raise APIError(6, 'The encoding type must be 2 or 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 != 2: + raise APIError(6, 'The encoding type must be 2 because that is the only one this program currently supports.') 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 = shared.config.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) + ackdata = OpenSSL.rand(32) + + 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 - - queues.UISignalQueue.put(('displayNewSentMessage', ( + 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))) + shared.UISignalQueue.put(('displayNewSentMessage', ( toAddress, toLabel, fromAddress, subject, message, ackdata))) - queues.workerQueue.put(('sendmessage', toAddress)) + + shared.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): - raise APIError(6, 'The encoding type must be 2 or 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 != 2: + raise APIError(6, 'The encoding type must be 2 because that is the only one this program currently supports.') 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 = shared.config.getboolean( + fromAddress, 'enabled') + except: + raise APIError(13, 'could not find your fromAddress in the keys.dat file.') + ackdata = OpenSSL.rand(32) + 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 - queues.UISignalQueue.put(('displayNewSentMessage', ( + toLabel = '[Broadcast subscribers]' + shared.UISignalQueue.put(('displayNewSentMessage', ( toAddress, toLabel, fromAddress, subject, message, ackdata))) - queues.workerQueue.put(('sendbroadcast', '')) + shared.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', '')) + shared.UISignalQueue.put(('rerenderMessagelistFromLabels', '')) + shared.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', '')) + shared.UISignalQueue.put(('rerenderMessagelistFromLabels', '')) + shared.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} + if len(data) > 20: + data += ',' + data += json.dumps({'label':label.encode('base64'), 'address': address, 'enabled': enabled == 1}, indent=4, separators=(',',': ')) + data += ']}' + return data - @command('disseminatePreEncryptedMsg', 'disseminatePreparedObject') - def HandleDisseminatePreparedObject( - self, encryptedPayload, - nonceTrialsPerByte=networkDefaultProofOfWorkNonceTrialsPerByte, - payloadLengthExtraBytes=networkDefaultPayloadLengthExtraBytes - ): - """ - Handle a request to disseminate an encrypted message. - - The device issuing this command to PyBitmessage supplies an object - that has already been encrypted but which may still need the PoW - to be done. PyBitmessage accepts this object and sends it out - to the rest of the Bitmessage network as if it had generated - the message itself. - - *encryptedPayload* is a hex encoded string starting with the nonce, - 8 zero bytes in case of no PoW done. - """ + 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") - - nonce, = unpack('>Q', encryptedPayload[:8]) - objectType, toStreamNumber, expiresTime = \ - protocol.decodeObjectParameters(encryptedPayload) - - if nonce == 0: # Let us do the POW and attach it to the front - encryptedPayload = encryptedPayload[8:] - TTL = expiresTime - time.time() + 300 # a bit of extra padding - # Let us do the POW and attach it to the front - 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(nonceTrialsPerByte) - / networkDefaultProofOfWorkNonceTrialsPerByte, - float(payloadLengthExtraBytes) - / networkDefaultPayloadLengthExtraBytes, - ) - powStartTime = time.time() - target = 2**64 / ( - nonceTrialsPerByte * ( - len(encryptedPayload) + 8 + payloadLengthExtraBytes + (( - TTL * ( - len(encryptedPayload) + 8 + payloadLengthExtraBytes - )) / (2 ** 16)) - )) - 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) - ) - encryptedPayload = pack('>Q', nonce) + encryptedPayload - + # Let us do the POW and attach it to the front + 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) / shared.networkDefaultProofOfWorkNonceTrialsPerByte, 'Required small message difficulty:', float(requiredPayloadLengthExtraBytes) / shared.networkDefaultPayloadLengthExtraBytes + powStartTime = time.time() + initialHash = hashlib.sha512(encryptedPayload).digest() + trialValue, nonce = proofofwork.run(target, initialHash) + 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) - state.Inventory[inventoryHash] = ( - objectType, toStreamNumber, encryptedPayload, - expiresTime, b'' - ) - logger.info( - 'Broadcasting inv for msg(API disseminatePreEncryptedMsg' - ' command): %s', hexlify(inventoryHash)) - invQueue.put((toStreamNumber, inventoryHash)) - return hexlify(inventoryHash).decode() + objectType = 2 + TTL = 2.5 * 24 * 60 * 60 + shared.inventory[inventoryHash] = ( + objectType, toStreamNumber, encryptedPayload, int(time.time()) + TTL,'') + with shared.printLock: + print 'Broadcasting inv for msg(API disseminatePreEncryptedMsg command):', hexlify(inventoryHash) + shared.broadcastToSendDataQueues(( + toStreamNumber, 'advertiseobject', inventoryHash)) - @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) + networkDefaultPayloadLengthExtraBytes + 8 - ) * networkDefaultProofOfWorkNonceTrialsPerByte) - logger.info('(For pubkey message via API) Doing proof of work...') + target = 2 ** 64 / ((len(payload) + shared.networkDefaultPayloadLengthExtraBytes + + 8) * shared.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 - state.Inventory[inventoryHash] = ( - objectType, pubkeyStreamNumber, payload, int(time.time()) + TTL, '' - ) - logger.info( - 'broadcasting inv within API command disseminatePubkey with' - ' hash: %s', hexlify(inventoryHash)) - invQueue.put((pubkeyStreamNumber, inventoryHash)) - - @command( - 'getMessageDataByDestinationHash', 'getMessageDataByDestinationTag') - def HandleGetMessageDataByDestinationHash(self, requestedHash): - """Handle a request to get message data by destination hash""" + shared.inventory[inventoryHash] = ( + objectType, pubkeyStreamNumber, payload, int(time.time()) + TTL,'') + with shared.printLock: + print 'broadcasting inv within API command disseminatePubkey with hash:', hexlify(inventoryHash) + shared.broadcastToSendDataQueues(( + streamNumber, 'advertiseobject', inventoryHash)) + 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(shared.connectedHostsList) == 0: networkStatus = 'notConnected' - elif state.clientHasReceivedIncomingConnections: - networkStatus = 'connectedAndReceivingIncomingConnections' - else: + elif len(shared.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(shared.connectedHostsList),'numberOfMessagesProcessed':shared.numberOfMessagesProcessed, 'numberOfBroadcastsProcessed':shared.numberOfBroadcastsProcessed, 'numberOfPubkeysProcessed':shared.numberOfPubkeysProcessed, 'networkStatus':networkStatus, 'softwareName':'PyBitmessage','softwareVersion':shared.softwareVersion}, indent=4, separators=(',', ': ')) - @command('listConnections') - def HandleListConnections(self): - """ - Returns bitmessage connection information as dict with keys *inbound*, - *outbound*. - """ - if connectionpool is None: - raise APIError(21, 'Could not import BMConnectionPool.') - inboundConnections = [] - outboundConnections = [] - for i in connectionpool.pool.inboundConnections.values(): - inboundConnections.append({ - 'host': i.destination.host, - 'port': i.destination.port, - 'fullyEstablished': i.fullyEstablished, - 'userAgent': str(i.userAgent) - }) - for i in connectionpool.pool.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':ripe.encode('base64')}, 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""" - queues.UISignalQueue.put(('updateStatusBar', message)) - return "success" + def HandleStatusBar(self, params): + message, = params + shared.UISignalQueue.put(('updateStatusBar', message)) - @testmode('undeleteMessage') - def HandleUndeleteMessage(self, msgid): - """Undelete message""" - msgid = self._decode(msgid, "hex") - helper_inbox.undeleteMessage(msgid) - return "Undeleted message" - - @command('deleteAndVacuum') - def HandleDeleteAndVacuum(self): - """Cleanup trashes and vacuum messages database""" + def HandleDeleteAndVacuum(self, params): sqlStoredProcedure('deleteandvacuume') 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 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) - - 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__ + return "API Error 0021: Unexpected API Failure - %s" % str(e) 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..87d71f05 100644 --- a/src/bitmessagecli.py +++ b/src/bitmessagecli.py @@ -1,165 +1,149 @@ -#!/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. +#!/usr/bin/env python2.7.x +# 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.4.2, by .dok (Version 0.3.0) -TODO: fix the following (currently ignored) violations: -""" - -import datetime -import imghdr -import json -import ntpath -import os -import socket -import sys -import time +import ConfigParser import xmlrpclib - -from bmconfigparser import config - +import datetime +import hashlib +import getopt +import imghdr +import ntpath +import json +import time +import sys +import os 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 safeConfigGetBoolean(section,field): + global keysPath + config = ConfigParser.SafeConfigParser() + config.read(keysPath) + + try: + return config.getboolean(section,field) + except: + return False -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""" - + global keysName + config = ConfigParser.SafeConfigParser() + 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 - + config.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. + config.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') - + 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 keysPath global usrPrompt + config = ConfigParser.SafeConfigParser() config.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": # + config.set('bitmessagesettings','apienabled','true') #Sets apienabled to true in keys.dat with open(keysPath, 'wb') as configfile: config.write(configfile) - - print('Done') + + 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') + + print ' -----------------------------------\n' + + config.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. + config.set('bitmessagesettings','apienabled','true') config.set('bitmessagesettings', 'apiport', apiPort) config.set('bitmessagesettings', 'apiinterface', '127.0.0.1') config.set('bitmessagesettings', 'apiusername', apiUsr) @@ -167,468 +151,476 @@ def apiInit(apiEnabled): config.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') + + 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 + + config = ConfigParser.SafeConfigParser() + config.read(keysPath) #First try to load the config file (the keys.dat file) from the program directory try: - config.get('bitmessagesettings', 'port') + config.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 = ConfigParser.SafeConfigParser() config.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') + config.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 + 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') + 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(safeConfigGetBoolean('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 + 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) + "/" + + 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 - + config = ConfigParser.SafeConfigParser() keysPath = 'keys.dat' - - config.read(keysPath) # Read the keys.dat + + config.read(keysPath)#Read the keys.dat try: port = config.get('bitmessagesettings', 'port') - except: # noqa:E722 - print('\n File not found.\n') + except: + print '\n File not found.\n' usrPrompt = 0 main() - - startonlogon = config.safeGetBoolean('bitmessagesettings', 'startonlogon') - minimizetotray = config.safeGetBoolean('bitmessagesettings', 'minimizetotray') - showtraynotifications = config.safeGetBoolean('bitmessagesettings', 'showtraynotifications') - startintray = config.safeGetBoolean('bitmessagesettings', 'startintray') + + startonlogon = safeConfigGetBoolean('bitmessagesettings', 'startonlogon') + minimizetotray = safeConfigGetBoolean('bitmessagesettings', 'minimizetotray') + showtraynotifications = safeConfigGetBoolean('bitmessagesettings', 'showtraynotifications') + startintray = safeConfigGetBoolean('bitmessagesettings', 'startintray') defaultnoncetrialsperbyte = config.get('bitmessagesettings', 'defaultnoncetrialsperbyte') defaultpayloadlengthextrabytes = config.get('bitmessagesettings', 'defaultpayloadlengthextrabytes') - daemon = config.safeGetBoolean('bitmessagesettings', 'daemon') + daemon = safeConfigGetBoolean('bitmessagesettings', 'daemon') socksproxytype = config.get('bitmessagesettings', 'socksproxytype') sockshostname = config.get('bitmessagesettings', 'sockshostname') socksport = config.get('bitmessagesettings', 'socksport') - socksauthentication = config.safeGetBoolean('bitmessagesettings', 'socksauthentication') + socksauthentication = safeConfigGetBoolean('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)) 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)) 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)) 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)) 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)) 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)) 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)) 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)) 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)) 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)) 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)) 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)) 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)) 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)) 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) 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 = - + + + uInput = userInput("Are you sure, (Y)es or (N)o?").lower() + 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 = str(jsonAddresses['addresses'][addNum]['label']) address = str(jsonAddresses['addresses'][addNum]['address']) 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: + generatedAddress = api.createRandomAddress(addressLabel) + 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 +629,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,361 +848,317 @@ 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): + uInput = userInput('(Press Enter to continue or type (Exit) to return to the main menu.)').lower() + + 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): + uInput = userInput('(Press Enter to continue or type (Exit) to return to the main menu.)').lower() - 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""" + global usrPrompt if address in knownAddresses: return knownAddresses[address] @@ -1223,24 +1169,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,560 +1188,508 @@ 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") - - -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() elif usrInput == "create": createChan() - usrPrompt = 1 + userPrompt = 1 main() elif usrInput == "join": joinChan() - usrPrompt = 1 + userPrompt = 1 main() - + elif usrInput == "leave": leaveChan() - usrPrompt = 1 + userPrompt = 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') - usrPrompt = 1 + print '\n Invalid Entry.\n' + userPrompt = 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 +1697,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() @@ -1839,42 +1724,49 @@ def UI(usrInput): usrPrompt = 1 main() - elif usrInput == "shutdown": - shutdown() + elif usrInput == "million+": + genMilAddr() usrPrompt = 1 main() + elif usrInput == "million-": + delMilAddr() + usrPrompt = 1 + main() + else: - print('\n "', usrInput, '" is not a command.\n') + 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.2.6 for BM 0.3.5 |' + 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 +1774,5 @@ def main(): except EOFError: UI("quit") - if __name__ == "__main__": main() diff --git a/src/bitmessagecurses/__init__.py b/src/bitmessagecurses/__init__.py index 64fd735b..3b740247 100644 --- a/src/bitmessagecurses/__init__.py +++ b/src/bitmessagecurses/__init__.py @@ -1,40 +1,33 @@ -""" -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 -import l10n -import network.stats -import queues +from helper_sql import * + import shared -import shutdown -import state +import ConfigParser +from addresses import * +from pyelliptic.openssl import OpenSSL +import l10n -from addresses import addBMIfNotPresent, decodeAddress -from bmconfigparser import config -from helper_sql import sqlExecute, sqlQuery - -# pylint: disable=global-statement - - -quit_ = False +quit = False menutab = 1 menu = ["Inbox", "Send", "Sent", "Your Identities", "Subscriptions", "Address Book", "Blacklist", "Network Status"] naptime = 100 @@ -60,206 +53,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 = state.Inventory.numberOfInventoryLookupsPerformed - state.Inventory.numberOfInventoryLookupsPerformed = 0 + inventorydata = shared.numberOfInventoryLookupsPerformed + shared.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: @@ -267,97 +217,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: @@ -365,16 +291,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] @@ -383,31 +309,30 @@ 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 + global addrbook 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) @@ -417,85 +342,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"), @@ -503,41 +404,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 @@ -549,151 +440,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)) + shared.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))) + shared.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) + shared.config.set(a, "label", label) # Write config - config.save() + shared.writeKeysFile() 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 + shared.config.set(a, "enabled", "true") # Set config # Write config - config.save() + shared.writeKeysFile() # Change color - if config.safeGetBoolean(a, 'chan'): - addresses[addrcur][3] = 9 # orange - elif config.safeGetBoolean(a, 'mailinglist'): - addresses[addrcur][3] = 5 # magenta + if shared.safeConfigGetBoolean(a, 'chan'): + addresses[addrcur][3] = 9 # orange + elif shared.safeConfigGetBoolean(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 + shared.config.set(a, "enabled", "false") # Set config + addresses[addrcur][3] = 8 # Set color to gray # Write config - config.save() + shared.writeKeysFile() 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 + shared.config.remove_section(addresses[addrcur][2]) + shared.writeKeysFile() + 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 shared.safeConfigGetBoolean(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 = shared.safeConfigGetBoolean(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: + shared.config.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 = shared.config.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 + shared.config.set(a, "mailinglist", "true") + shared.config.set(a, "mailinglistname", mn) + addresses[addrcur][3] = 6 # Set color to magenta # Write config - config.save() + shared.writeKeysFile() 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")]) @@ -711,42 +568,30 @@ def handlech(c, stdscr): subscriptions.append([label, addr, True]) subscriptions.reverse() - sqlExecute("INSERT INTO subscriptions VALUES (?,?,?)", label, addr, True) + sqlExecute("INSERT INTO subscriptions VALUES (?,?,?)", label, address, 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")]) @@ -762,14 +607,14 @@ def handlech(c, stdscr): subscriptions.append([label, addr, True]) subscriptions.reverse() - sqlExecute("INSERT INTO subscriptions VALUES (?,?,?)", label, addr, True) + sqlExecute("INSERT INTO subscriptions VALUES (?,?,?)", label, address, True) shared.reloadBroadcastSendersForWhichImWatching() elif t == "3": 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 @@ -781,39 +626,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: @@ -831,17 +662,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: @@ -858,47 +689,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) @@ -914,12 +736,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" @@ -930,17 +752,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)) @@ -948,76 +766,99 @@ 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) - queues.workerQueue.put(("sendmessage", addr)) - else: # Broadcast + ackdata = OpenSSL.rand(32) + 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 + shared.config.getint('bitmessagesettings', 'ttl')) + shared.workerQueue.put(("sendmessage", addr)) + 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') - queues.workerQueue.put(('sendbroadcast', '')) + ackdata = OpenSSL.rand(32) + 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 + shared.config.getint('bitmessagesettings', 'ttl')) + shared.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 FROM inbox WHERE folder='inbox' AND %s LIKE ? ORDER BY received """ % (where,), what) + global inbox 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 = shared.config.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 shared.config.has_section(fromaddr): + fromlabel = shared.config.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: @@ -1025,30 +866,27 @@ 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 FROM sent WHERE folder='sent' AND %s LIKE ? ORDER BY lastactiontime """ % (where,), what) + global sent 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) @@ -1061,18 +899,18 @@ def loadSent(): for r in qr: tolabel, = r if tolabel == "": - if config.has_section(toaddr): - tolabel = config.get(toaddr, "label") + if shared.config.has_section(toaddr): + tolabel = shared.config.get(toaddr, "label") if tolabel == "": tolabel = toaddr - + # Set label for from address fromlabel = "" - if config.has_section(fromaddr): - fromlabel = config.get(fromaddr, "label") + if shared.config.has_section(fromaddr): + fromlabel = shared.config.get(fromaddr, "label") if fromlabel == "": fromlabel = fromaddr - + # Set status string if status == "awaitingpubkey": statstr = "Waiting for their public key. Will request it again soon" @@ -1081,21 +919,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 + "." + elif status == "askreceived": + 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": @@ -1103,49 +941,34 @@ 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") + global addrbook 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 = shared.config.get("bitmessagesettings", "blackwhitelist") if bwtype == "black": ret = sqlQuery("SELECT label, address, enabled FROM blacklist") else: @@ -1155,83 +978,80 @@ 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() - + shutdown() 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 = shared.config.sections() for addressInKeysFile in configSections: - isEnabled = config.getboolean(addressInKeysFile, "enabled") - addresses.append([config.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 - else: - addresses[len(addresses) - 1].append(0) # black + if addressInKeysFile != "bitmessagesettings": + isEnabled = shared.config.getboolean(addressInKeysFile, "enabled") + addresses.append([shared.config.get(addressInKeysFile, "label"), isEnabled, addressInKeysFile]) + # Set address color + if not isEnabled: + addresses[len(addresses)-1].append(8) # gray + elif shared.safeConfigGetBoolean(addressInKeysFile, 'chan'): + addresses[len(addresses)-1].append(9) # orange + elif shared.safeConfigGetBoolean(addressInKeysFile, 'mailinglist'): + addresses[len(addresses)-1].append(5) # magenta + else: + 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""" +def shutdown(): sys.stdout = sys.__stdout__ print("Shutting down...") sys.stdout = printlog - shutdown.doCleanShutdown() + shared.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 12104c57..00000000 --- a/src/bitmessagekivy/baseclass/allmail.py +++ /dev/null @@ -1,70 +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 -""" - -import logging - -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, -) - -logger = logging.getLogger('default') - - -class AllMails(Screen): - """AllMails Screen for Kivy UI""" - data = ListProperty() - has_refreshed = True - all_mails = ListProperty() - account = StringProperty() - label_str = 'No messages for this account.' - - def __init__(self, *args, **kwargs): - """Initialize the AllMails screen.""" - super().__init__(*args, **kwargs) # pylint: disable=missing-super-argument - self.kivy_state = kivy_state_variables() - self._initialize_selected_address() - Clock.schedule_once(self.init_ui, 0) - - def _initialize_selected_address(self): - """Initialize the selected address from the identity list.""" - if not self.kivy_state.selected_address and App.get_running_app().identity_list: - self.kivy_state.selected_address = App.get_running_app().identity_list[0] - - def init_ui(self, dt=0): - """Initialize the UI by loading the message list.""" - self.load_message_list() - logger.debug("UI initialized after %s seconds.", dt) - - def load_message_list(self): - """Load the Inbox, Sent, and Draft message lists.""" - self.account = self.kivy_state.selected_address - self.ids.tag_label.text = 'All Mails' if self.all_mails else '' - self._update_mail_count() - - def _update_mail_count(self): - """Update the mail count and handle empty states.""" - if self.all_mails: - total_count = int(self.kivy_state.sent_count) + int(self.kivy_state.inbox_count) - self.kivy_state.all_count = str(total_count) - self.set_all_mail_count(self.kivy_state.all_count) - else: - self.set_all_mail_count('0') - self.ids.ml.add_widget(empty_screen_label(self.label_str)) - - @staticmethod - def set_all_mail_count(count): - """Set the message count for all mails.""" - allmail_count_widget = App.get_running_app().root.ids.content_drawer.ids.allmail_cnt - allmail_count_widget.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 50f883d6..00000000 --- a/src/bitmessagekivy/baseclass/draft.py +++ /dev/null @@ -1,54 +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 for managing draft messages in Kivy UI. -""" -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): - """Initialize the Draft screen and set the default account""" - super().__init__(*args, **kwargs) - self.kivy_state = kivy_state_variables() - if not 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): - """Initialize the UI and load draft messages""" - self.load_draft() - logger.debug(f"UI initialized with dt: {dt}") # noqa: E999 - - def load_draft(self, where="", what=""): - """Load the list of 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 messages in the UI""" - draft_count_obj = App.get_running_app().root.ids.content_drawer.ids.draft_cnt - draft_count_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 17ea9a9b..00000000 --- a/src/bitmessagekivy/baseclass/inbox.py +++ /dev/null @@ -1,59 +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 and set up the UI""" - super().__init__(*args, **kwargs) # pylint: disable=missing-super-argument - 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_default_address(self): - """Set the default address if none is selected""" - if not self.kivy_state.selected_address and self.kivy_running_app.identity_list: - self.kivy_state.selected_address = self.kivy_running_app.identity_list[0] - - def init_ui(self, dt=0): - """Initialize UI and load message list""" - self.loadMessagelist() - - def loadMessagelist(self, where="", what=""): - """Load inbox messages""" - self.set_default_address() - self.account = self.kivy_state.selected_address - - def refresh_callback(self, *args): - """Refresh the inbox messages while showing a loading spinner""" - - def refresh_on_scroll_down(interval): - """Reset search fields and reload data on scroll""" - 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 11fb6c79..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 3df69e05..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 74723e32..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 974ef1c4..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 209287e2..00000000 --- a/src/bitmessagekivy/tests/telenium_process.py +++ /dev/null @@ -1,126 +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 requests.exceptions import ChunkedEncodingError - -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 - try: - super(TeleniumTestProcess, cls).tearDownClass() - except ChunkedEncodingError: - pass - 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 ab131a4c..4818f97b 100755 --- a/src/bitmessagemain.py +++ b/src/bitmessagemain.py @@ -1,381 +1,298 @@ -#!/usr/bin/env python -""" -The PyBitmessage startup script -""" +#!/usr/bin/env 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. -import os -import sys - -try: - import pathmagic -except ImportError: - from pybitmessage import pathmagic -app_dir = pathmagic.setup() +# The software version variable is now held in shared.py import depends depends.check_dependencies() -import getopt -import multiprocessing -# Used to capture a Ctrl-C keypress so that Bitmessage can shutdown gracefully. -import signal -import threading +import signal # Used to capture a Ctrl-C keypress so that Bitmessage can shutdown gracefully. +# The next 3 are used for the API +import singleton +import os +import socket +import ctypes +from struct import pack +import sys +from subprocess import call import time -import traceback -import defaults -# Network subsystem -import network -import shutdown -import state +from api import MySimpleXMLRPCRequestHandler, StoppableXMLRPCServer +from helper_startup import isOurOperatingSystemLimitedToHavingVeryFewHalfOpenConnections -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) +import shared +from helper_sql import sqlQuery +import threading +# Classes +from class_sqlThread import sqlThread +from class_singleCleaner import singleCleaner +from class_objectProcessor import objectProcessor +from class_outgoingSynSender import outgoingSynSender +from class_singleListener import singleListener +from class_singleWorker import singleWorker +from class_addressGenerator import addressGenerator +from class_smtpDeliver import smtpDeliver +from debug import logger -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() +# Helper Functions +import helper_bootstrap +import helper_generic +from helper_threading import * + +def connectToStream(streamNumber): + shared.streamsInWhichIAmParticipating[streamNumber] = 'no data' + selfInitiatedConnections[streamNumber] = {} + + if isOurOperatingSystemLimitedToHavingVeryFewHalfOpenConnections(): + # Some XP and Vista systems can only have 10 outgoing connections at a time. + 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.') + maximumNumberOfHalfOpenConnections = 64 + try: + # don't overload Tor + if shared.config.get('bitmessagesettings', 'socksproxytype') != 'none': + maximumNumberOfHalfOpenConnections = 4 + except: + pass + for i in range(maximumNumberOfHalfOpenConnections): + a = outgoingSynSender() + a.setup(streamNumber, selfInitiatedConnections) + a.start() +def _fixWinsock(): + if not ('win32' in sys.platform) and not ('win64' in sys.platform): + return -class Main(object): - """Main PyBitmessage class""" - def start(self): - """Start main application""" - # pylint: disable=too-many-statements,too-many-branches,too-many-locals - fixSocket() - adjustHalfOpenConnectionsLimit() + # 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 - daemon = config.safeGetBoolean('bitmessagesettings', 'daemon') + # 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, 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: - opts, _ = getopt.getopt( - sys.argv[1:], "hcdt", - ["help", "curses", "daemon", "test"]) + s.connect((shared.config.get('bitmessagesettings', 'apiinterface'), shared.config.getint( + 'bitmessagesettings', 'apiport'))) + s.shutdown(socket.SHUT_RDWR) + s.close() + except: + pass - except getopt.GetoptError: - self.usage() - sys.exit(2) + def run(self): + se = StoppableXMLRPCServer((shared.config.get('bitmessagesettings', 'apiinterface'), shared.config.getint( + 'bitmessagesettings', 'apiport')), MySimpleXMLRPCRequestHandler, True, True) + se.register_introspection_functions() + se.serve_forever() - for opt, _ in opts: - if opt in ("-h", "--help"): - self.usage() - sys.exit() - elif opt in ("-d", "--daemon"): - daemon = True - elif opt in ("-c", "--curses"): - state.curses = True - 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') - ) +# This is a list of current connections (the thread pointers at least) +selfInitiatedConnections = {} - if daemon: - state.enableGUI = False # run without a UI +if shared.useVeryEasyProofOfWorkForTesting: + shared.networkDefaultProofOfWorkNonceTrialsPerByte = int( + shared.networkDefaultProofOfWorkNonceTrialsPerByte / 100) + shared.networkDefaultPayloadLengthExtraBytes = int( + shared.networkDefaultPayloadLengthExtraBytes / 100) + +class Main: + def start(self, daemon=False): + _fixWinsock() + + shared.daemon = daemon + + # get curses flag + shared.curses = False + if '-c' in sys.argv: + shared.curses = True - 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 = singleton.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_bootstrap.knownNodes() + # Start the address generation thread + addressGeneratorThread = addressGenerator() + addressGeneratorThread.daemon = True # close the main program even if there are threads left + addressGeneratorThread.start() - if state.testmode or config.safeGetBoolean( - 'bitmessagesettings', 'extralowdifficulty'): - defaults.networkDefaultProofOfWorkNonceTrialsPerByte = int( - defaults.networkDefaultProofOfWorkNonceTrialsPerByte / 100) - defaults.networkDefaultPayloadLengthExtraBytes = int( - defaults.networkDefaultPayloadLengthExtraBytes / 100) + # 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() - state.Inventory = Inventory() # init - 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 shared.safeConfigGet("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() - - # 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: - network.connectionpool.pool.connectToStream(1) + if shared.safeConfigGetBoolean('bitmessagesettings', 'apienabled'): + try: + apiNotifyPath = shared.config.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() + + connectToStream(1) + + singleListenerThread = singleListener() + singleListenerThread.setup(selfInitiatedConnections) + singleListenerThread.daemon = True # close the main program even if there are threads left + singleListenerThread.start() + + if shared.safeConfigGetBoolean('bitmessagesettings','upnp'): + import upnp + upnpThread = upnp.uPnPThread() + upnpThread.start() + + if daemon == False and shared.safeConfigGetBoolean('bitmessagesettings', 'daemon') == False: + if shared.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') + shared.config.remove_option('bitmessagesettings', 'dontconnect') - if state.testmode: - populate_api_test_data() - - 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 - - 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.""" - grandfatherPid = os.getpid() - parentPid = None - try: - if os.fork(): - # unlock - state.thisapp.cleanup() - # wait until grandchild ready - while True: - time.sleep(1) - os._exit(0) # pylint: disable=protected-access - except AttributeError: - # fork not implemented - pass - else: - parentPid = os.getpid() - state.thisapp.lock() # relock + while True: + time.sleep(20) + def daemonize(self): + if os.fork(): + exit(0) + shared.thisapp.lock() # relock os.umask(0) - try: - os.setsid() - except AttributeError: - # setsid not implemented - pass - try: - if os.fork(): - # unlock - state.thisapp.cleanup() - # wait until child ready - while True: - time.sleep(1) - os._exit(0) # pylint: disable=protected-access - except AttributeError: - # fork not implemented - pass - else: - state.thisapp.lock() # relock - state.thisapp.lockPid = None # indicate we're the final child + os.setsid() + if os.fork(): + exit(0) + 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) - os.dup2(si.fileno(), sys.stdin.fileno()) - os.dup2(so.fileno(), sys.stdout.fileno()) - os.dup2(se.fileno(), sys.stderr.fileno()) - if parentPid: - # signal ready - os.kill(parentPid, signal.SIGTERM) - os.kill(grandfatherPid, signal.SIGTERM) + si = file('/dev/null', 'r') + so = file('/dev/null', 'a+') + se = file('/dev/null', 'a+', 0) + os.dup2(si.fileno(), sys.stdin.fileno()) + os.dup2(so.fileno(), sys.stdout.fileno()) + os.dup2(se.fileno(), sys.stderr.fileno()) - @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(''' -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() + shared.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 shared.safeConfigGetBoolean('bitmessagesettings', 'apienabled'): return None - address = config.get('bitmessagesettings', 'apiinterface') - port = config.getint('bitmessagesettings', 'apiport') - return {'address': address, 'port': port} - - -def main(): - """Triggers main module""" - mainprogram = Main() - mainprogram.start() - + address = shared.config.get('bitmessagesettings', 'apiinterface') + port = shared.config.getint('bitmessagesettings', 'apiport') + return {'address':address,'port':port} if __name__ == "__main__": - main() + mainprogram = Main() + mainprogram.start(shared.safeConfigGetBoolean('bitmessagesettings', 'daemon')) # So far, the creation of and management of the Bitmessage protocol and this diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 1b1a7885..adca4c6c 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -1,160 +1,149 @@ -""" -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 -from tr import _translate -from account import ( - accountClass, getSortedSubscriptions, - BMAccount, GatewayAccount, MailchuckAccount, AccountColor) -from addresses import decodeAddress, addBMIfNotPresent -from bitmessageui import Ui_MainWindow -from bmconfigparser import config -import namecoin -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) -import settingsmixin -import support -from helper_sql import sqlQuery, sqlExecute, sqlExecuteChunked, sqlStoredProcedure -import helper_addressbook -import helper_search -import l10n -from utils import str_broadcast_subscribers, avatarize -import dialogs -from network.stats import pendingDownload, pendingUpload -from uisignaler import UISignaler -import paths -from proofofwork import getPowType -import queues -import shutdown -from statusbar import BMStatusBar -import sound -# This is needed for tray icon -import bitmessage_icons_rc # noqa:F401 pylint: disable=unused-import -import helper_sent +withMessagingMenu = False +try: + from gi.repository import MessagingMenu + from gi.repository import Notify + withMessagingMenu = True +except ImportError: + MessagingMenu = None try: - from plugins.plugin import get_plugin, get_plugins -except ImportError: - get_plugins = False + from PyQt4 import QtCore, QtGui + from PyQt4.QtCore import * + from PyQt4.QtGui import * + from PyQt4.QtNetwork import QLocalSocket, QLocalServer +except Exception as err: + logmsg = 'PyBitmessage requires PyQt unless you want to run it as a daemon and interact with it using the API. You can download it from http://www.riverbankcomputing.com/software/pyqt/download or by searching Google for \'PyQt Download\' (without quotes).' + logger.critical(logmsg, exc_info=True) + sys.exit() -is_windows = sys.platform.startswith('win') +try: + _encoding = QtGui.QApplication.UnicodeUTF8 +except AttributeError: + logger.exception('QtGui.QApplication.UnicodeUTF8 error', exc_info=True) +from addresses import * +import shared +from bitmessageui import * +from namecoin import namecoinConnection, ensureNamecoinOptions +from newaddressdialog import * +from newaddresswizard import * +from messageview import MessageView +from migrationwizard import * +from foldertree import * +from newsubscriptiondialog import * +from regenerateaddresses import * +from newchandialog import * +from safehtmlparser import * +from specialaddressbehavior import * +from emailgateway import * +from settings import * +import settingsmixin +import support +from about import * +from help import * +from iconglossary import * +from connect import * +import locale as pythonlocale +import sys +from time import strftime, localtime, gmtime +import time +import os +import hashlib +from pyelliptic.openssl import OpenSSL +import pickle +import platform +import textwrap +import debug +import random +import subprocess +import string +import datetime +from helper_sql import * +import helper_search +import l10n +import openclpow +import types +from utils import * +from collections import OrderedDict +from account import * +from dialogs import AddAddressDialog +from class_objectHashHolder import objectHashHolder +from class_singleWorker import singleWorker -# 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 _translate(context, text, disambiguation = None, encoding = None, number = None): + if number is None: + return QtGui.QApplication.translate(context, text) + else: + return QtGui.QApplication.translate(context, text, None, QtCore.QCoreApplication.CodecForTr, number) + +def change_translation(locale): + 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 (shared.codePath(), 'translations', 'bitmessage_' + locale) + qmytranslator.load(translationpath) + QtGui.QApplication.installTranslator(qmytranslator) + + qsystranslator = QtCore.QTranslator() + if shared.frozen: + translationpath = os.path.join (shared.codePath(), 'translations', 'qt_' + locale) + else: + translationpath = os.path.join (str(QtCore.QLibraryInfo.location(QtCore.QLibraryInfo.TranslationsPath)), 'qt_' + locale) + qsystranslator.load(translationpath) + QtGui.QApplication.installTranslator(qsystranslator) + + lang = pythonlocale.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 is_windows: - os.startfile(keysfile) # pylint: disable=no-member - + pythonlocale.setlocale(pythonlocale.LC_ALL, lang) + if 'win32' not in sys.platform and 'win64' not in sys.platform: + l10n.encoding = pythonlocale.nl_langinfo(pythonlocale.CODESET) + else: + l10n.encoding = pythonlocale.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): + # sound type constants + SOUND_NONE = 0 + SOUND_KNOWN = 1 + SOUND_UNKNOWN = 2 + SOUND_CONNECTED = 3 + SOUND_DISCONNECTED = 4 + SOUND_CONNECTION_GREEN = 5 + + # the last time that a message arrival sound was played + lastSoundTime = datetime.datetime.now() - datetime.timedelta(days=1) + # the maximum frequency of message sounds in seconds maxSoundFrequencySec = 60 + str_chan = '[chan]' + 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( "triggered()"), self.quit) - QtCore.QObject.connect(self.ui.actionNetworkSwitch, QtCore.SIGNAL( - "triggered()"), self.network_switch) QtCore.QObject.connect(self.ui.actionManageKeys, QtCore.SIGNAL( "triggered()"), self.click_actionManageKeys) QtCore.QObject.connect(self.ui.actionDeleteAllTrashedMessages, @@ -165,10 +154,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( @@ -177,8 +165,6 @@ class MyForm(settingsmixin.SMainWindow): "clicked()"), self.click_pushButtonAddSubscription) QtCore.QObject.connect(self.ui.pushButtonTTL, QtCore.SIGNAL( "clicked()"), self.click_pushButtonTTL) - QtCore.QObject.connect(self.ui.pushButtonClear, QtCore.SIGNAL( - "clicked()"), self.click_pushButtonClear) QtCore.QObject.connect(self.ui.pushButtonSend, QtCore.SIGNAL( "clicked()"), self.click_pushButtonSend) QtCore.QObject.connect(self.ui.pushButtonFetchNamecoinID, QtCore.SIGNAL( @@ -232,19 +218,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 @@ -274,33 +260,17 @@ class MyForm(settingsmixin.SMainWindow): _translate( "MainWindow", "Email gateway"), self.on_action_EmailGatewayDialog) - self.actionMarkAllRead = self.ui.addressContextMenuToolbarYourIdentities.addAction( - _translate( - "MainWindow", "Mark all messages as read"), - self.on_action_MarkAllRead) self.ui.treeWidgetYourIdentities.setContextMenuPolicy( QtCore.Qt.CustomContextMenu) 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) @@ -321,9 +291,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..."), @@ -334,7 +301,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 @@ -356,10 +323,6 @@ class MyForm(settingsmixin.SMainWindow): _translate( "MainWindow", "Set avatar..."), self.on_action_AddressBookSetAvatar) - self.actionAddressBookSetSound = \ - self.ui.addressBookContextMenuToolbar.addAction( - _translate("MainWindow", "Set notification sound..."), - self.on_action_AddressBookSetSound) self.actionAddressBookNew = self.ui.addressBookContextMenuToolbar.addAction( _translate( "MainWindow", "Add New Address"), self.on_action_AddressBookNew) @@ -371,9 +334,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) @@ -392,17 +357,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( @@ -414,9 +378,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,16 +389,15 @@ class MyForm(settingsmixin.SMainWindow): # sort ascending when creating if treeWidget.topLevelItemCount() == 0: - treeWidget.header().setSortIndicator( - 0, QtCore.Qt.AscendingOrder) + treeWidget.header().setSortIndicator(0, 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) @@ -449,8 +409,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 @@ -480,16 +440,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: @@ -501,36 +455,36 @@ 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( - 0, QtCore.Qt.AscendingOrder) + treeWidget.header().setSortIndicator(0, Qt.AscendingOrder) # init dictionary db = {} enabled = {} - - for toAddress in config.addresses(True): - isEnabled = config.getboolean( + + for toAddress in getSortedAccounts(): + isEnabled = shared.config.getboolean( toAddress, 'enabled') - isChan = config.safeGetBoolean( + isChan = shared.safeConfigGetBoolean( toAddress, 'chan') - isMaillinglist = config.safeGetBoolean( + isMaillinglist = shared.safeConfigGetBoolean( toAddress, 'mailinglist') if treeWidget == self.ui.treeWidgetYourIdentities: @@ -543,16 +497,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 @@ -565,10 +515,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(): @@ -577,8 +527,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 @@ -587,9 +537,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,31 +551,29 @@ class MyForm(settingsmixin.SMainWindow): if len(db[toAddress]) > 0: j = 0 for f, c in db[toAddress].iteritems(): - 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]) j = 0 unread = 0 for folder in folders: - if toAddress is not None and tab == 'messages' and folder == "new": + if toAddress is not None 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): @@ -634,41 +581,44 @@ 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() + shared.config.remove_section(addressInKeysFile) + shared.writeKeysFile() - 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 = QSettings(RUN_PATH, 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 shared.config.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.init_file_menu() self.init_inbox_popup_menu() self.init_identities_popup_menu() @@ -684,12 +634,14 @@ class MyForm(settingsmixin.SMainWindow): self.rerenderTabTreeMessages() # Set welcome message - self.ui.textEditInboxMessage.setText(_translate("MainWindow", """ + self.ui.textEditInboxMessage.setText( + """ Welcome to easy and secure Bitmessage * send messages to other people * send broadcast messages like twitter or * discuss in chan(nel)s with other people - """)) + """ + ) # Initialize the address book self.rerenderAddressBook() @@ -739,37 +691,33 @@ class MyForm(settingsmixin.SMainWindow): "itemSelectionChanged ()"), self.treeWidgetItemClicked) QtCore.QObject.connect(self.ui.treeWidgetChans, QtCore.SIGNAL( "itemChanged (QTreeWidgetItem *, int)"), self.treeWidgetItemChanged) - QtCore.QObject.connect( - self.ui.tabWidget, QtCore.SIGNAL("currentChanged(int)"), - self.tabWidgetCurrentChanged - ) # Put the colored icon on the status bar # self.pushButtonStatusIcon.setIcon(QIcon(":/newPrefix/images/yellowicon.png")) - self.setStatusBar(BMStatusBar()) self.statusbar = self.statusBar() self.pushButtonStatusIcon = QtGui.QPushButton(self) self.pushButtonStatusIcon.setText('') - self.pushButtonStatusIcon.setIcon( - QtGui.QIcon(':/newPrefix/images/redicon.png')) + self.pushButtonStatusIcon.setIcon(QIcon(':/newPrefix/images/redicon.png')) self.pushButtonStatusIcon.setFlat(True) self.statusbar.insertPermanentWidget(0, self.pushButtonStatusIcon) 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( @@ -779,12 +727,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( @@ -810,9 +755,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 @@ -828,97 +770,49 @@ class MyForm(settingsmixin.SMainWindow): self.rerenderComboBoxSendFrom() self.rerenderComboBoxSendFromBroadcast() - + # Put the TTL slider in the correct spot - TTL = config.getint('bitmessagesettings', 'ttl') + TTL = shared.config.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 is_windows: # 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"] = shared.config.get('bitmessagesettings', 'namecoinrpctype') + options["host"] = shared.config.get('bitmessagesettings', 'namecoinrpchost') + options["port"] = shared.config.get('bitmessagesettings', 'namecoinrpcport') + options["user"] = shared.config.get('bitmessagesettings', 'namecoinrpcuser') + options["password"] = shared.config.get('bitmessagesettings', 'namecoinrpcpassword') + nc = namecoinConnection(options) + if nc.test()[0] == 'failed': + self.ui.pushButtonFetchNamecoinID.hide() + except: + 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() - + shared.config.set('bitmessagesettings', 'ttl', str(TTL)) + shared.writeKeysFile() + def updateHumanFriendlyTTLDescription(self, TTL): numberOfHours = int(round(TTL / (60*60))) - font = QtGui.QFont() - stylesheet = "" - if numberOfHours < 48: - self.ui.labelHumanFriendlyTTLDescription.setText( - _translate("MainWindow", "%n hour(s)", None, QtCore.QCoreApplication.CodecForTr, numberOfHours) + - ", " + - _translate("MainWindow", "not recommended for chans", None, QtCore.QCoreApplication.CodecForTr) - ) - stylesheet = "QLabel { color : red; }" - font.setBold(True) + self.ui.labelHumanFriendlyTTLDescription.setText(_translate("MainWindow", "%n hour(s)", None, QtCore.QCoreApplication.CodecForTr, numberOfHours)) else: numberOfDays = int(round(TTL / (24*60*60))) - 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) + self.ui.labelHumanFriendlyTTLDescription.setText(_translate("MainWindow", "%n day(s)", None, QtCore.QCoreApplication.CodecForTr, numberOfDays)) # Show or hide the application window after clicking an item within the # tray icon or, on Windows, the try icon itself. @@ -932,6 +826,14 @@ class MyForm(settingsmixin.SMainWindow): self.raise_() self.activateWindow() + # pointer to the application + # app = None + # The most recent message + newMessageItem = None + + # The most recent broadcast + newBroadcastItem = None + # show the application window def appIndicatorShow(self): if self.actionShow is None: @@ -948,12 +850,6 @@ class MyForm(settingsmixin.SMainWindow): self.actionShow.setChecked(False) self.appIndicatorShowOrHideWindow() - def appIndicatorSwitchQuietMode(self): - config.set( - 'bitmessagesettings', 'showtraynotifications', - str(not self.actionQuiet.isChecked()) - ) - # application indicator show or hide """# application indicator show or hide def appIndicatorShowBitmessage(self): @@ -967,148 +863,98 @@ class MyForm(settingsmixin.SMainWindow): self.appIndicatorShowOrHideWindow()""" # Show the program window and select inbox tab - def appIndicatorInbox(self, item=None): + def appIndicatorInbox(self, mm_app, source_id): self.appIndicatorShow() # select inbox - self.ui.tabWidget.setCurrentIndex( - self.ui.tabWidget.indexOf(self.ui.inbox) - ) - self.ui.treeWidgetYourIdentities.setCurrentItem( - self.ui.treeWidgetYourIdentities.topLevelItem(0).child(0) - ) - - if item: - self.ui.tableWidgetInbox.setCurrentItem(item) + self.ui.tabWidget.setCurrentIndex(0) + selectedItem = None + if source_id == 'Subscriptions': + # select unread broadcast + if self.newBroadcastItem is not None: + selectedItem = self.newBroadcastItem + self.newBroadcastItem = None + else: + # select unread message + if self.newMessageItem is not None: + selectedItem = self.newMessageItem + self.newMessageItem = None + # make it the current item + if selectedItem is not None: + try: + self.ui.tableWidgetInbox.setCurrentItem(selectedItem) + except Exception: + self.ui.tableWidgetInbox.setCurrentCell(0, 0) self.tableWidgetInboxItemClicked() else: + # just select the first item self.ui.tableWidgetInbox.setCurrentCell(0, 0) + self.tableWidgetInboxItemClicked() # Show the program window and select send tab def appIndicatorSend(self): self.appIndicatorShow() - self.ui.tabWidget.setCurrentIndex( - self.ui.tabWidget.indexOf(self.ui.send) - ) + self.ui.tabWidget.setCurrentIndex(1) # Show the program window and select subscriptions tab def appIndicatorSubscribe(self): self.appIndicatorShow() - self.ui.tabWidget.setCurrentIndex( - self.ui.tabWidget.indexOf(self.ui.subscriptions) - ) + self.ui.tabWidget.setCurrentIndex(2) # Show the program window and select channels tab def appIndicatorChannel(self): self.appIndicatorShow() - self.ui.tabWidget.setCurrentIndex( - self.ui.tabWidget.indexOf(self.ui.chans) - ) - - def updateUnreadStatus(self, widget, row, msgid, unread=True): - """ - Switch unread for item of msgid and related items in - other STableWidgets "All Accounts" and "Chans" - """ - status = widget.item(row, 0).unread - if status != unread: - return - - widgets = [self.ui.tableWidgetInbox, self.ui.tableWidgetInboxChans] - rrow = None - try: - widgets.remove(widget) - related = widgets.pop() - except ValueError: - pass - 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: - break - - for col in range(widget.columnCount()): - widget.item(row, col).setUnread(not status) - if rrow: - related.item(rrow, 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') + self.ui.tabWidget.setCurrentIndex(3) + + 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) @@ -1117,114 +963,102 @@ 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) - acct.parseMessage(toAddress, fromAddress, subject, "") - - if status == 'awaitingpubkey': - statusText = _translate( - "MainWindow", - "Waiting for their encryption key. Will request it again soon." - ) - elif status == 'doingpowforpubkey': - statusText = _translate( - "MainWindow", "Doing work necessary to request encryption key." - ) - elif status == 'msgqueued': - statusText = _translate("MainWindow", "Queued.") - elif status == 'msgsent': - 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)) - 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)) - elif status == 'broadcastqueued': - statusText = _translate( - "MainWindow", "Broadcast queued.") - elif status == 'doingbroadcastpow': - statusText = _translate( - "MainWindow", "Doing work necessary to send broadcast.") - elif status == 'broadcastsent': - 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)) - elif status == 'badkey': - 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.") - else: - 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)] - self.addMessageListItem(tableWidget, items) - - return acct - - def addMessageListItemInbox( - self, tableWidget, toAddress, fromAddress, subject, - msgid, received, read - ): - if toAddress == str_broadcast_subscribers: - acct = accountClass(fromAddress) - else: - acct = accountClass(toAddress) or accountClass(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( - 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) - ] - self.addMessageListItem(tableWidget, items) + 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.") + elif status == 'doingpowforpubkey': + statusText = _translate( + "MainWindow", "Encryption key request queued.") + elif status == 'msgqueued': + statusText = _translate( + "MainWindow", "Queued.") + elif status == 'msgsent': + 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)) + elif status == 'doingmsgpow': + statusText = _translate( + "MainWindow", "Need to do work to send message. Work is queued.") + elif status == 'ackreceived': + statusText = _translate("MainWindow", "Acknowledgement of the message received %1").arg( + l10n.formatTimestamp(lastactiontime)) + elif status == 'broadcastqueued': + statusText = _translate( + "MainWindow", "Broadcast queued.") + elif status == 'broadcastsent': + 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)) + elif status == 'badkey': + 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.") + else: + statusText = _translate("MainWindow", "Unknown status: %1 %2").arg(status).arg( + l10n.formatTimestamp(lastactiontime)) + newItem = myTableWidgetItem(statusText) + newItem.setToolTip(statusText) + newItem.setData(Qt.UserRole, 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, msgfolder, msgid, toAddress, fromAddress, subject, received, read): + font = QFont() + font.setBold(True) + if toAddress == str_broadcast_subscribers: + acct = accountClass(fromAddress) + else: + acct = accountClass(toAddress) + if acct is None: + acct = accountClass(fromAddress) + if acct is None: + acct = BMAccount(fromAddress) + acct.parseMessage(toAddress, fromAddress, subject, "") + + 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(Qt.UserRole, 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 @@ -1239,40 +1073,32 @@ 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) - - for row in queryreturn: - self.addMessageListItemSent(tableWidget, *row) - - tableWidget.horizontalHeader().setSortIndicator( - 3, QtCore.Qt.DescendingOrder) - tableWidget.setSortingEnabled(True) - tableWidget.horizontalHeaderItem(3).setText( - _translate("MainWindow", "Sent")) - 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) + queryreturn = helper_search.search_sql(xAddress, account, "sent", where, what, False) + for row in queryreturn: + toAddress, fromAddress, subject, status, ackdata, lastactiontime = row + self.addMessageListItemSent(tableWidget, toAddress, fromAddress, subject, status, ackdata, lastactiontime) + + tableWidget.setSortingEnabled(False) + tableWidget.horizontalHeader().setSortIndicator(3, Qt.DescendingOrder) + tableWidget.horizontalHeaderItem(3).setText(_translate("MainWindow", "Sent", None)) + + # Load messages from database file + 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: @@ -1282,22 +1108,19 @@ class MyForm(settingsmixin.SMainWindow): tableWidget.setColumnHidden(0, False) tableWidget.setColumnHidden(1, False) - queryreturn = helper_search.search_sql( - xAddress, account, folder, where, what, unreadOnly) + 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.horizontalHeader().setSortIndicator(3, Qt.DescendingOrder) tableWidget.setSortingEnabled(True) tableWidget.selectRow(0) - tableWidget.horizontalHeaderItem(3).setText( - _translate("MainWindow", "Received")) - tableWidget.setUpdatesEnabled(True) + tableWidget.horizontalHeaderItem(3).setText(_translate("MainWindow", "Received", None)) # create application indicator def appIndicatorInit(self, app): @@ -1306,7 +1129,7 @@ class MyForm(settingsmixin.SMainWindow): QtCore.QObject.connect(self.tray, QtCore.SIGNAL( traySignal), self.__icon_activated) - m = QtGui.QMenu() + m = QMenu() self.actionStatus = QtGui.QAction(_translate( "MainWindow", "Not Connected"), m, checkable=False) @@ -1320,20 +1143,12 @@ 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 shared.config.getboolean( 'bitmessagesettings', 'startintray')) self.actionShow.triggered.connect(self.appIndicatorShowOrHideWindow) if not sys.platform[0:3] == 'win': m.addAction(self.actionShow) - # quiet mode - self.actionQuiet = QtGui.QAction(_translate( - "MainWindow", "Quiet Mode"), m, checkable=True) - self.actionQuiet.setChecked(not config.getboolean( - 'bitmessagesettings', 'showtraynotifications')) - self.actionQuiet.triggered.connect(self.appIndicatorSwitchQuietMode) - m.addAction(self.actionQuiet) - # Send actionSend = QtGui.QAction(_translate( "MainWindow", "Send"), m, checkable=False) @@ -1364,156 +1179,273 @@ class MyForm(settingsmixin.SMainWindow): self.tray.setContextMenu(m) self.tray.show() + # Ubuntu Messaging menu object + mmapp = None + + # is the operating system Ubuntu? + def isUbuntu(self): + for entry in platform.uname(): + if "Ubuntu" in entry: + return True + return False + + # When an unread inbox row is selected on then clear the messaging menu + def ubuntuMessagingMenuClear(self, inventoryHash): + global withMessagingMenu + + # if this isn't ubuntu then don't do anything + if not self.isUbuntu(): + return + + # has messageing menu been installed + if not withMessagingMenu: + return + + # if there are no items on the messaging menu then + # the subsequent query can be avoided + if not (self.mmapp.has_source("Subscriptions") or self.mmapp.has_source("Messages")): + return + + queryreturn = sqlQuery( + '''SELECT toaddress, read FROM inbox WHERE msgid=?''', inventoryHash) + for row in queryreturn: + toAddress, read = row + if not read: + if toAddress == str_broadcast_subscribers: + if self.mmapp.has_source("Subscriptions"): + self.mmapp.remove_source("Subscriptions") + else: + if self.mmapp.has_source("Messages"): + self.mmapp.remove_source("Messages") + # returns the number of unread messages and subscriptions def getUnread(self): - counters = [0, 0] + unreadMessages = 0 + unreadSubscriptions = 0 - queryreturn = sqlQuery(''' - SELECT msgid, toaddress, read FROM inbox where folder='inbox' - ''') - for msgid, toAddress, read in queryreturn: + queryreturn = sqlQuery( + '''SELECT msgid, toaddress, read FROM inbox where folder='inbox' ''') + for row in queryreturn: + msgid, toAddress, read = row + + try: + if toAddress == str_broadcast_subscribers: + toLabel = str_broadcast_subscribers + else: + toLabel = shared.config.get(toAddress, 'label') + except: + toLabel = '' + if toLabel == '': + toLabel = toAddress if not read: - # increment the unread subscriptions if True (1) - # else messages (0) - counters[toAddress == str_broadcast_subscribers] += 1 + if toLabel == str_broadcast_subscribers: + # increment the unread subscriptions + unreadSubscriptions = unreadSubscriptions + 1 + else: + # increment the unread messages + unreadMessages = unreadMessages + 1 + return unreadMessages, unreadSubscriptions - return counters + # show the number of unread messages and subscriptions on the messaging + # menu + def ubuntuMessagingMenuUnread(self, drawAttention): + unreadMessages, unreadSubscriptions = self.getUnread() + # unread messages + if unreadMessages > 0: + self.mmapp.append_source( + "Messages", None, "Messages (" + str(unreadMessages) + ")") + if drawAttention: + self.mmapp.draw_attention("Messages") + + # unread subscriptions + if unreadSubscriptions > 0: + self.mmapp.append_source("Subscriptions", None, "Subscriptions (" + str( + unreadSubscriptions) + ")") + if drawAttention: + self.mmapp.draw_attention("Subscriptions") + + # initialise the Ubuntu messaging menu + def ubuntuMessagingMenuInit(self): + global withMessagingMenu + + # if this isn't ubuntu then don't do anything + if not self.isUbuntu(): + return + + # has messageing menu been installed + if not withMessagingMenu: + logger.warning('WARNING: MessagingMenu is not available. Is libmessaging-menu-dev installed?') + return + + # create the menu server + if withMessagingMenu: + try: + self.mmapp = MessagingMenu.App( + desktop_id='pybitmessage.desktop') + self.mmapp.register() + self.mmapp.connect('activate-source', self.appIndicatorInbox) + self.ubuntuMessagingMenuUnread(True) + except Exception: + withMessagingMenu = False + logger.warning('WARNING: messaging menu disabled') + + # update the Ubuntu messaging menu + def ubuntuMessagingMenuUpdate(self, drawAttention, newItem, toLabel): + global withMessagingMenu + + # if this isn't ubuntu then don't do anything + if not self.isUbuntu(): + return + + # has messageing menu been installed + if not withMessagingMenu: + logger.warning('WARNING: messaging menu disabled or libmessaging-menu-dev not installed') + return + + # remember this item to that the messaging menu can find it + if toLabel == str_broadcast_subscribers: + self.newBroadcastItem = newItem + else: + self.newMessageItem = newItem + + # Remove previous messages and subscriptions entries, then recreate them + # There might be a better way to do it than this + if self.mmapp.has_source("Messages"): + self.mmapp.remove_source("Messages") + + if self.mmapp.has_source("Subscriptions"): + self.mmapp.remove_source("Subscriptions") + + # update the menu entries + self.ubuntuMessagingMenuUnread(drawAttention) + + # returns true if the given sound category is a connection sound + # rather than a received message sound + def isConnectionSound(self, category): + if (category is self.SOUND_CONNECTED or + category is self.SOUND_DISCONNECTED or + category is self.SOUND_CONNECTION_GREEN): + return True + return False # play a sound def playSound(self, category, label): # filename of the sound to be played soundFilename = None - def _choose_ext(basename): - for ext in sound.extensions: - if os.path.isfile(os.extsep.join([basename, ext])): - return os.extsep + ext + # whether to play a sound or not + play = True # if the address had a known label in the address book - if label: + if label is not None: # Does a sound file exist for this particular contact? - soundFilename = state.appdata + 'sounds/' + label - ext = _choose_ext(soundFilename) - if not ext: - category = sound.SOUND_KNOWN - soundFilename = None + if (os.path.isfile(shared.appdata + 'sounds/' + label + '.wav') or + os.path.isfile(shared.appdata + 'sounds/' + label + '.mp3')): + soundFilename = shared.appdata + 'sounds/' + label + + # Avoid making sounds more frequently than the threshold. + # This suppresses playing sounds repeatedly when there + # are many new messages + if (soundFilename is None and + not self.isConnectionSound(category)): + # elapsed time since the last sound was played + dt = datetime.datetime.now() - self.lastSoundTime + # suppress sounds which are more frequent than the threshold + if dt.total_seconds() < self.maxSoundFrequencySec: + play = False if soundFilename is None: - # Avoid making sounds more frequently than the threshold. - # This suppresses playing sounds repeatedly when there - # are many new messages - if not sound.is_connection_sound(category): - # elapsed time since the last sound was played - dt = datetime.now() - self.lastSoundTime - # suppress sounds which are more frequent than the threshold - if dt.total_seconds() < self.maxSoundFrequencySec: - return - # the sound is for an address which exists in the address book - if category is sound.SOUND_KNOWN: - soundFilename = state.appdata + 'sounds/known' + if category is self.SOUND_KNOWN: + soundFilename = shared.appdata + 'sounds/known' # the sound is for an unknown address - elif category is sound.SOUND_UNKNOWN: - soundFilename = state.appdata + 'sounds/unknown' + elif category is self.SOUND_UNKNOWN: + soundFilename = shared.appdata + 'sounds/unknown' # initial connection sound - elif category is sound.SOUND_CONNECTED: - soundFilename = state.appdata + 'sounds/connected' + elif category is self.SOUND_CONNECTED: + soundFilename = shared.appdata + 'sounds/connected' # disconnected sound - elif category is sound.SOUND_DISCONNECTED: - soundFilename = state.appdata + 'sounds/disconnected' + elif category is self.SOUND_DISCONNECTED: + soundFilename = shared.appdata + 'sounds/disconnected' # sound when the connection status becomes green - elif category is sound.SOUND_CONNECTION_GREEN: - soundFilename = state.appdata + 'sounds/green' + elif category is self.SOUND_CONNECTION_GREEN: + soundFilename = shared.appdata + 'sounds/green' - if soundFilename is None: - logger.warning("Probably wrong category number in playSound()") - return + if soundFilename is not None and play is True: + if not self.isConnectionSound(category): + # record the last time that a received message sound was played + self.lastSoundTime = datetime.datetime.now() - if not sound.is_connection_sound(category): - # record the last time that a received message sound was played - self.lastSoundTime = datetime.now() + # if not wav then try mp3 format + if not os.path.isfile(soundFilename + '.wav'): + soundFilename = soundFilename + '.mp3' + else: + soundFilename = soundFilename + '.wav' - try: # try already known format - soundFilename += ext - except (TypeError, NameError): - ext = _choose_ext(soundFilename) - if not ext: - try: # if no user sound file found try to play from theme - return self._theme_player(category, label) - except TypeError: - return - - soundFilename += ext - - self._player(soundFilename) - - # Adapters and converters for QT <-> sqlite - def sqlInit(self): - register_adapter(QtCore.QByteArray, str) - - def indicatorInit(self): - """ - Try init the distro specific appindicator, - for example the Ubuntu MessagingMenu - """ - def _noop_update(*args, **kwargs): - pass - - try: - self.indicatorUpdate = get_plugin('indicator')(self) - except (NameError, TypeError): - logger.warning("No indicator plugin found") - self.indicatorUpdate = _noop_update + if os.path.isfile(soundFilename): + if 'linux' in sys.platform: + # Note: QSound was a nice idea but it didn't work + if '.mp3' in soundFilename: + gst_available=False + try: + subprocess.call(["gst123", soundFilename], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE) + gst_available=True + except: + logger.warning("WARNING: gst123 must be installed in order to play mp3 sounds") + if not gst_available: + try: + subprocess.call(["mpg123", soundFilename], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE) + gst_available=True + except: + logger.warning("WARNING: mpg123 must be installed in order to play mp3 sounds") + else: + try: + subprocess.call(["aplay", soundFilename], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE) + except: + logger.warning("WARNING: aplay must be installed in order to play WAV sounds") + elif sys.platform[0:3] == 'win': + # use winsound on Windows + import winsound + winsound.PlaySound(soundFilename, winsound.SND_FILENAME) # initialise the message notifier def notifierInit(self): - def _simple_notify( - title, subtitle, category, label=None, icon=None): - self.tray.showMessage(title, subtitle, 1, 2000) + global withMessagingMenu + if withMessagingMenu: + Notify.init('pybitmessage') - self._notifier = _simple_notify - # does nothing if isAvailable returns false - self._player = QtGui.QSound.play + # shows a notification + def notifierShow(self, title, subtitle, fromCategory, label): + global withMessagingMenu - if not get_plugins: + self.playSound(fromCategory, label) + + if withMessagingMenu: + n = Notify.Notification.new( + title, subtitle, 'notification-message-email') + try: + n.show() + except: + # n.show() has been known to throw this exception: + # gi._glib.GError: GDBus.Error:org.freedesktop.Notifications. + # MaxNotificationsExceeded: Exceeded maximum number of + # notifications + pass return - - _plugin = get_plugin('notification.message') - if _plugin: - self._notifier = _plugin else: - logger.warning("No notification.message plugin found") - - self._theme_player = get_plugin('notification.sound', 'theme') - - if not QtGui.QSound.isAvailable(): - _plugin = get_plugin( - 'notification.sound', 'file', fallback='file.fallback') - if _plugin: - self._player = _plugin - else: - logger.warning("No notification.sound plugin found") - - def notifierShow( - self, title, subtitle, category, label=None, icon=None): - self.playSound(category, label) - self._notifier( - unicode(title), unicode(subtitle), category, label, icon) + self.tray.showMessage(title, subtitle, 1, 2000) # tree 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()) @@ -1522,12 +1454,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: @@ -1554,177 +1485,150 @@ class MyForm(settingsmixin.SMainWindow): currentAddress = self.getCurrentAccount() if currentAddress: self.setSendFromComboBox(currentAddress) - self.ui.tabWidgetSend.setCurrentIndex( - self.ui.tabWidgetSend.indexOf(self.ui.sendDirect) - ) - self.ui.tabWidget.setCurrentIndex( - self.ui.tabWidget.indexOf(self.ui.send) - ) + self.ui.tabWidgetSend.setCurrentIndex(0) + self.ui.tabWidget.setCurrentIndex(1) 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' def click_actionManageKeys(self): if 'darwin' in sys.platform or 'linux' in sys.platform: - if state.appdata == '': + if shared.appdata == '': # reply = QtGui.QMessageBox.information(self, '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.', 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."), 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(shared.appdata), 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) + if shared.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) 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(shared.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' + + # menu botton 'regenerate deterministic addresses' def click_actionRegenerateDeterministicAddresses(self): - dialog = dialogs.RegenerateAddressesDialog(self) - if dialog.exec_(): - if dialog.lineEditPassphrase.text() == "": - QtGui.QMessageBox.about( - self, _translate("MainWindow", "bad passphrase"), - _translate( - "MainWindow", - "You must type your passphrase. If you don\'t" - " have one then this is not the form for you." - )) + self.regenerateAddressesDialogInstance = regenerateAddressesDialog( + self) + if self.regenerateAddressesDialogInstance.exec_(): + if self.regenerateAddressesDialogInstance.ui.lineEditPassphrase.text() == "": + QMessageBox.about(self, _translate("MainWindow", "bad passphrase"), _translate( + "MainWindow", "You must type your passphrase. If you don\'t have one then this is not the form for you.")) return - streamNumberForAddress = int(dialog.lineEditStreamNumber.text()) + streamNumberForAddress = int( + self.regenerateAddressesDialogInstance.ui.lineEditStreamNumber.text()) try: addressVersionNumber = int( - dialog.lineEditAddressVersionNumber.text()) + self.regenerateAddressesDialogInstance.ui.lineEditAddressVersionNumber.text()) except: - QtGui.QMessageBox.about( - self, - _translate("MainWindow", "Bad address version number"), - _translate( - "MainWindow", - "Your address version number must be a number:" - " either 3 or 4." - )) + QMessageBox.about(self, _translate("MainWindow", "Bad address version number"), _translate( + "MainWindow", "Your address version number must be a number: either 3 or 4.")) return if addressVersionNumber < 3 or addressVersionNumber > 4: - QtGui.QMessageBox.about( - self, - _translate("MainWindow", "Bad address version number"), - _translate( - "MainWindow", - "Your address version number must be either 3 or 4." - )) + QMessageBox.about(self, _translate("MainWindow", "Bad address version number"), _translate( + "MainWindow", "Your address version number must be either 3 or 4.")) return - queues.addressGeneratorQueue.put(( - 'createDeterministicAddresses', - addressVersionNumber, streamNumberForAddress, - "regenerated deterministic address", - dialog.spinBoxNumberOfAddressesToMake.value(), - dialog.lineEditPassphrase.text().toUtf8(), - dialog.checkBoxEighteenByteRipe.isChecked() - )) - self.ui.tabWidget.setCurrentIndex( - self.ui.tabWidget.indexOf(self.ui.chans) - ) + shared.addressGeneratorQueue.put(('createDeterministicAddresses', addressVersionNumber, streamNumberForAddress, "regenerated deterministic address", self.regenerateAddressesDialogInstance.ui.spinBoxNumberOfAddressesToMake.value( + ), self.regenerateAddressesDialogInstance.ui.lineEditPassphrase.text().toUtf8(), self.regenerateAddressesDialogInstance.ui.checkBoxEighteenByteRipe.isChecked())) + self.ui.tabWidget.setCurrentIndex(3) # opens 'join chan' dialog def click_actionJoinChan(self): - dialogs.NewChanDialog(self) + self.newChanDialogInstance = newChanDialog(self) + if self.newChanDialogInstance.exec_(): + if self.newChanDialogInstance.ui.radioButtonCreateChan.isChecked(): + if self.newChanDialogInstance.ui.lineEditChanNameCreate.text() == "": + QMessageBox.about(self, _translate("MainWindow", "Chan name needed"), _translate( + "MainWindow", "You didn't enter a chan name.")) + return + shared.apiAddressGeneratorReturnQueue.queue.clear() + shared.addressGeneratorQueue.put(('createChan', 4, 1, self.str_chan + ' ' + str(self.newChanDialogInstance.ui.lineEditChanNameCreate.text().toUtf8()), self.newChanDialogInstance.ui.lineEditChanNameCreate.text().toUtf8())) + addressGeneratorReturnValue = shared.apiAddressGeneratorReturnQueue.get() + logger.debug('addressGeneratorReturnValue ' + str(addressGeneratorReturnValue)) + if len(addressGeneratorReturnValue) == 0: + QMessageBox.about(self, _translate("MainWindow", "Address already present"), _translate( + "MainWindow", "Could not add chan because it appears to already be one of your identities.")) + return + createdAddress = addressGeneratorReturnValue[0] + QMessageBox.about(self, _translate("MainWindow", "Success"), _translate( + "MainWindow", "Successfully created chan. To let others join your chan, give them the chan name and this Bitmessage address: %1. This address also appears in 'Your Identities'.").arg(createdAddress)) + self.ui.tabWidget.setCurrentIndex(3) + elif self.newChanDialogInstance.ui.radioButtonJoinChan.isChecked(): + if self.newChanDialogInstance.ui.lineEditChanNameJoin.text() == "": + QMessageBox.about(self, _translate("MainWindow", "Chan name needed"), _translate( + "MainWindow", "You didn't enter a chan name.")) + return + if decodeAddress(self.newChanDialogInstance.ui.lineEditChanBitmessageAddress.text())[0] == 'versiontoohigh': + QMessageBox.about(self, _translate("MainWindow", "Address too new"), _translate( + "MainWindow", "Although that Bitmessage address might be valid, its version number is too new for us to handle. Perhaps you need to upgrade Bitmessage.")) + return + if decodeAddress(self.newChanDialogInstance.ui.lineEditChanBitmessageAddress.text())[0] != 'success': + QMessageBox.about(self, _translate("MainWindow", "Address invalid"), _translate( + "MainWindow", "That Bitmessage address is not valid.")) + return + shared.apiAddressGeneratorReturnQueue.queue.clear() + shared.addressGeneratorQueue.put(('joinChan', addBMIfNotPresent(self.newChanDialogInstance.ui.lineEditChanBitmessageAddress.text()), self.str_chan + ' ' + str(self.newChanDialogInstance.ui.lineEditChanNameJoin.text().toUtf8()), self.newChanDialogInstance.ui.lineEditChanNameJoin.text().toUtf8())) + addressGeneratorReturnValue = shared.apiAddressGeneratorReturnQueue.get() + logger.debug('addressGeneratorReturnValue ' + str(addressGeneratorReturnValue)) + if addressGeneratorReturnValue == 'chan name does not match address': + QMessageBox.about(self, _translate("MainWindow", "Address does not match chan name"), _translate( + "MainWindow", "Although the Bitmessage address you entered was valid, it doesn\'t match the chan name.")) + return + if len(addressGeneratorReturnValue) == 0: + QMessageBox.about(self, _translate("MainWindow", "Address already present"), _translate( + "MainWindow", "Could not add chan because it appears to already be one of your identities.")) + return + createdAddress = addressGeneratorReturnValue[0] + QMessageBox.about(self, _translate("MainWindow", "Success"), _translate( + "MainWindow", "Successfully joined chan. ")) + self.ui.tabWidget.setCurrentIndex(3) + self.rerenderAddressBook() def showConnectDialog(self): - dialog = dialogs.ConnectDialog(self) - if dialog.exec_(): - if dialog.radioButtonConnectNow.isChecked(): - self.ui.updateNetworkSwitchMenuLabel(False) - config.remove_option( - 'bitmessagesettings', 'dontconnect') - config.save() - elif dialog.radioButtonConfigureNetwork.isChecked(): - self.click_actionSettings() + self.connectDialogInstance = connectDialog(self) + if self.connectDialogInstance.exec_(): + if self.connectDialogInstance.ui.radioButtonConnectNow.isChecked(): + shared.config.remove_option('bitmessagesettings', 'dontconnect') + shared.writeKeysFile() else: - self._firstrun = False + self.click_actionSettings() def showMigrationWizard(self, level): self.migrationWizardInstance = Ui_MigrationWizard(["a"]) @@ -1732,7 +1636,7 @@ class MyForm(settingsmixin.SMainWindow): pass else: pass - + def changeEvent(self, event): if event.type() == QtCore.QEvent.LanguageChange: self.ui.retranslateUi(self) @@ -1745,12 +1649,14 @@ 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: - QtCore.QTimer.singleShot(0, self.appIndicatorHide) + if shared.config.getboolean('bitmessagesettings', 'minimizetotray') and not 'darwin' in sys.platform: + 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: @@ -1761,69 +1667,60 @@ class MyForm(settingsmixin.SMainWindow): connected = False def setStatusIcon(self, color): - _notifications_enabled = not config.getboolean( - 'bitmessagesettings', 'hidetrayconnectionnotifications') - if color not in ('red', 'yellow', 'green'): - return - - self.pushButtonStatusIcon.setIcon( - QtGui.QIcon(":/newPrefix/images/%sicon.png" % color)) - state.statusIconColor = color + global withMessagingMenu + # print 'setting status icon color' if color == 'red': + self.pushButtonStatusIcon.setIcon( + 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'): - 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 - )) + if self.connected: + self.notifierShow('Bitmessage', unicode(_translate( + "MainWindow", "Connection lost").toUtf8(),'utf-8'), + self.SOUND_DISCONNECTED, None) 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().showMessage('') + self.pushButtonStatusIcon.setIcon(QIcon( + ":/newPrefix/images/yellowicon.png")) + shared.statusIconColor = 'yellow' + # if a new connection has been established then show a notification + if not self.connected: + self.notifierShow('Bitmessage', unicode(_translate( + "MainWindow", "Connected").toUtf8(),'utf-8'), + self.SOUND_CONNECTED, None) + 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().showMessage('') + self.pushButtonStatusIcon.setIcon( + QIcon(":/newPrefix/images/greenicon.png")) + shared.statusIconColor = 'green' + if not self.connected: + self.notifierShow('Bitmessage', unicode(_translate( + "MainWindow", "Connected").toUtf8(),'utf-8'), + self.SOUND_CONNECTION_GREEN, None) + 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 - self.tray = QtGui.QSystemTrayIcon( + self.tray = QSystemTrayIcon( self.calcTrayIcon(iconFileName, self.findInboxUnreadCount()), app) def setTrayIconFile(self, iconFileName): @@ -1831,7 +1728,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" @@ -1843,8 +1740,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 = "+" @@ -1853,10 +1749,9 @@ class MyForm(settingsmixin.SMainWindow): fontMetrics = QtGui.QFontMetrics(font) rect = fontMetrics.boundingRect(txt) # draw text - painter = QtGui.QPainter() + painter = QPainter() painter.begin(pixmap) - painter.setPen( - QtGui.QPen(QtGui.QColor(255, 0, 0), QtCore.Qt.SolidPattern)) + painter.setPen(QtGui.QPen(QtGui.QColor(255, 0, 0), Qt.SolidPattern)) painter.setFont(font) painter.drawText(24-rect.right()-marginX, -rect.top()+marginY, txt) painter.end() @@ -1865,14 +1760,13 @@ class MyForm(settingsmixin.SMainWindow): def drawTrayIcon(self, iconFileName, inboxUnreadCount): self.tray.setIcon(self.calcTrayIcon(iconFileName, inboxUnreadCount)) - def changedInboxUnread(self, row=None): - self.drawTrayIcon( - self.currentTrayIconFileName, self.findInboxUnreadCount()) + def changedInboxUnread(self, row = None): + self.drawTrayIcon(self.currentTrayIconFileName, self.findInboxUnreadCount()) self.rerenderTabTreeMessages() self.rerenderTabTreeSubscriptions() self.rerenderTabTreeChans() - def findInboxUnreadCount(self, count=None): + def findInboxUnreadCount(self, count = None): if count is None: queryreturn = sqlQuery('''SELECT count(*) from inbox WHERE folder='inbox' and read=0''') cnt = 0 @@ -1884,29 +1778,21 @@ 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()): - rowAddress = sent.item(i, 0).data(QtCore.Qt.UserRole) + rowAddress = sent.item( + i, 0).data(Qt.UserRole) if toAddress == rowAddress: 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( @@ -1915,28 +1801,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(Qt.UserRole) + tableAckdata = sent.item( + i, 3).data(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( @@ -1944,54 +1824,37 @@ 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(Qt.UserRole).toPyObject()): + self.statusBar().showMessage(_translate( + "MainWindow", "Message trashed")) + treeWidget = self.widgetConvert(inbox) + self.propagateUnreadCount(inbox.item(i, 1 if inbox.item(i, 1).type == AccountMixin.SUBSCRIPTION else 0).data(Qt.UserRole), self.getCurrentFolder(treeWidget), treeWidget, 0) + 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) - self.updateStatusBar(_translate( - "MainWindow", - "New version of PyBitmessage is available: %1. Download it" - " from https://github.com/Bitmessage/PyBitmessage/releases/latest" - ).arg(self.notifiedNewVersion) - ) + self.statusBar().showMessage(_translate("MainWindow", "New version of PyBitmessage is available: %1. Download it from https://github.com/Bitmessage/PyBitmessage/releases/latest").arg(self.notifiedNewVersion)) def displayAlert(self, title, text, exitAfterUserClicksOk): - self.updateStatusBar(text) - QtGui.QMessageBox.critical(self, title, text, QtGui.QMessageBox.Ok) + self.statusBar().showMessage(text) + QtGui.QMessageBox.critical(self, title, text, QMessageBox.Ok) if exitAfterUserClicksOk: 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() @@ -2009,7 +1872,7 @@ class MyForm(settingsmixin.SMainWindow): oldRows[item.address] = [item.label, item.type, i] if self.ui.tableWidgetAddressBook.rowCount() == 0: - self.ui.tableWidgetAddressBook.horizontalHeader().setSortIndicator(0, QtCore.Qt.AscendingOrder) + self.ui.tableWidgetAddressBook.horizontalHeader().setSortIndicator(0, Qt.AscendingOrder) if self.ui.tableWidgetAddressBook.isSortingEnabled(): self.ui.tableWidgetAddressBook.setSortingEnabled(False) @@ -2020,9 +1883,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 shared.safeConfigGetBoolean(address, 'enabled')): newRows[address] = [account.getLabel(), AccountMixin.CHAN] # normal accounts queryreturn = sqlQuery('SELECT * FROM addressbook') @@ -2031,58 +1895,42 @@ 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]) completerList.append(unicode(newRows[address][0], encoding="UTF-8") + " <" + address + ">") # sort - self.ui.tableWidgetAddressBook.sortByColumn( - 0, QtCore.Qt.AscendingOrder) + self.ui.tableWidgetAddressBook.sortByColumn(0, Qt.AscendingOrder) self.ui.tableWidgetAddressBook.setSortingEnabled(True) self.ui.lineEditTo.completer().model().setStringList(completerList) def rerenderSubscriptions(self): self.rerenderTabTreeSubscriptions() - def click_pushButtonTTL(self): - QtGui.QMessageBox.information( - self, - 'Time To Live', - _translate( - "MainWindow", """The TTL, or Time-To-Live is the length of time that the network will hold the message. - The recipient must get it during this time. If your Bitmessage client does not hear an acknowledgement - ,it will resend the message automatically. The longer the Time-To-Live, the - more work your computer must do to send the message. - A Time-To-Live of four or five days is often appropriate."""), - QtGui.QMessageBox.Ok) - def click_pushButtonClear(self): - self.ui.lineEditSubject.setText("") - self.ui.lineEditTo.setText("") - self.ui.textEditMessage.reset() - self.ui.comboBoxSendFrom.setCurrentIndex(0) + def click_pushButtonTTL(self): + QtGui.QMessageBox.information(self, 'Time To Live', _translate( + "MainWindow", """The TTL, or Time-To-Live is the length of time that the network will hold the message. + The recipient must get it during this time. If your Bitmessage client does not hear an acknowledgement, it + will resend the message automatically. The longer the Time-To-Live, the + more work your computer must do to send the message. A Time-To-Live of four or five days is often appropriate."""), QMessageBox.Ok) def click_pushButtonSend(self): - encoding = 3 if QtGui.QApplication.queryKeyboardModifiers() & QtCore.Qt.ShiftModifier else 2 + self.statusBar().showMessage('') - self.statusbar.clearMessage() - - if self.ui.tabWidgetSend.currentIndex() == \ - self.ui.tabWidgetSend.indexOf(self.ui.sendDirect): + if self.ui.tabWidgetSend.currentIndex() == 0: # message to specific people sendMessageToPeople = True fromAddress = str(self.ui.comboBoxSendFrom.itemData( - self.ui.comboBoxSendFrom.currentIndex(), - QtCore.Qt.UserRole).toString()) - toAddresses = str(self.ui.lineEditTo.text().toUtf8()) + self.ui.comboBoxSendFrom.currentIndex(), + Qt.UserRole).toString()) + toAddresses = str(self.ui.lineEditTo.text()) subject = str(self.ui.lineEditSubject.text().toUtf8()) message = str( self.ui.textEditMessage.document().toPlainText().toUtf8()) @@ -2090,186 +1938,122 @@ class MyForm(settingsmixin.SMainWindow): # broadcast message sendMessageToPeople = False fromAddress = str(self.ui.comboBoxSendFromBroadcast.itemData( - self.ui.comboBoxSendFromBroadcast.currentIndex(), - QtCore.Qt.UserRole).toString()) + self.ui.comboBoxSendFromBroadcast.currentIndex(), + Qt.UserRole).toString()) subject = str(self.ui.lineEditSubjectBroadcast.text().toUtf8()) message = str( self.ui.textEditMessageBroadcast.document().toPlainText().toUtf8()) """ - The whole network message must fit in 2^18 bytes. - Let's assume 500 bytes of overhead. If someone wants to get that - too an exact number you are welcome to but I think that it would - be a better use of time to support message continuation so that - users can send messages of any length. + The whole network message must fit in 2^18 bytes. Let's assume 500 + bytes of overhead. If someone wants to get that too an exact + number you are welcome to but I think that it would be a better + use of time to support message continuation so that users can + send messages of any length. """ - if len(message) > (2 ** 18 - 500): - QtGui.QMessageBox.about( - self, _translate("MainWindow", "Message too long"), - _translate( - "MainWindow", - "The message that you are trying to send is too long" - " by %1 bytes. (The maximum is 261644 bytes). Please" - " cut it down before sending." - ).arg(len(message) - (2 ** 18 - 500))) + if len(message) > (2 ** 18 - 500): + QMessageBox.about(self, _translate("MainWindow", "Message too long"), _translate( + "MainWindow", "The message that you are trying to send is too long by %1 bytes. (The maximum is 261644 bytes). Please cut it down before sending.").arg(len(message) - (2 ** 18 - 500))) return - + 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 if "<" in toAddress and ">" in toAddress: toAddress = toAddress.split('<')[1].split('>')[0] # email address - if toAddress.find("@") >= 0: + elif toAddress.find("@") >= 0: if isinstance(acct, GatewayAccount): acct.createMessage(toAddress, fromAddress, subject, message) subject = acct.subject toAddress = acct.toAddress else: - if QtGui.QMessageBox.question( - self, - "Sending an email?", - _translate( - "MainWindow", - "You are trying to send an email instead of a bitmessage. " - "This requires registering with a gateway. Attempt to register?"), - QtGui.QMessageBox.Yes|QtGui.QMessageBox.No) != QtGui.QMessageBox.Yes: - continue email = acct.getLabel() - if email[-14:] != "@mailchuck.com": # attempt register + 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() - self.updateStatusBar(_translate( - "MainWindow", - "Error: Your account wasn't registered at" - " an email gateway. Sending registration" - " now as %1, please wait for the registration" - " to be processed before retrying sending." - ).arg(email) - ) + shared.config.set(fromAddress, 'label', email) + shared.config.set(fromAddress, 'gateway', 'mailchuck') + shared.writeKeysFile() + self.statusBar().showMessage(_translate( + "MainWindow", "Error: Your account wasn't registered at an email gateway. Sending registration now as %1, please wait for the registration to be processed before retrying sending.").arg(email)) return - status, addressVersionNumber, streamNumber = decodeAddress(toAddress)[:3] + status, addressVersionNumber, streamNumber, ripe = decodeAddress( + toAddress) if status != 'success': - try: - toAddress = unicode(toAddress, 'utf-8', 'ignore') - except: - pass - logger.error('Error: Could not decode recipient address ' + toAddress + ':' + status) + logger.error('Error: Could not decode ' + toAddress + ':' + status) if status == 'missingbm': - self.updateStatusBar(_translate( - "MainWindow", - "Error: Bitmessage addresses start with" - " BM- Please check the recipient address %1" - ).arg(toAddress)) + self.statusBar().showMessage(_translate( + "MainWindow", "Error: Bitmessage addresses start with BM- Please check %1").arg(toAddress)) elif status == 'checksumfailed': - self.updateStatusBar(_translate( - "MainWindow", - "Error: The recipient address %1 is not" - " typed or copied correctly. Please check it." - ).arg(toAddress)) + self.statusBar().showMessage(_translate( + "MainWindow", "Error: The address %1 is not typed or copied correctly. Please check it.").arg(toAddress)) elif status == 'invalidcharacters': - self.updateStatusBar(_translate( - "MainWindow", - "Error: The recipient address %1 contains" - " invalid characters. Please check it." - ).arg(toAddress)) + self.statusBar().showMessage(_translate( + "MainWindow", "Error: The address %1 contains invalid characters. Please check it.").arg(toAddress)) elif status == 'versiontoohigh': - self.updateStatusBar(_translate( - "MainWindow", - "Error: The version of the recipient address" - " %1 is too high. Either you need to upgrade" - " your Bitmessage software or your" - " acquaintance is being clever." - ).arg(toAddress)) + self.statusBar().showMessage(_translate( + "MainWindow", "Error: The address version in %1 is too high. Either you need to upgrade your Bitmessage software or your acquaintance is being clever.").arg(toAddress)) elif status == 'ripetooshort': - self.updateStatusBar(_translate( - "MainWindow", - "Error: Some data encoded in the recipient" - " address %1 is too short. There might be" - " something wrong with the software of" - " your acquaintance." - ).arg(toAddress)) + self.statusBar().showMessage(_translate( + "MainWindow", "Error: Some data encoded in the address %1 is too short. There might be something wrong with the software of your acquaintance.").arg(toAddress)) elif status == 'ripetoolong': - self.updateStatusBar(_translate( - "MainWindow", - "Error: Some data encoded in the recipient" - " address %1 is too long. There might be" - " something wrong with the software of" - " your acquaintance." - ).arg(toAddress)) + self.statusBar().showMessage(_translate( + "MainWindow", "Error: Some data encoded in the address %1 is too long. There might be something wrong with the software of your acquaintance.").arg(toAddress)) elif status == 'varintmalformed': - self.updateStatusBar(_translate( - "MainWindow", - "Error: Some data encoded in the recipient" - " address %1 is malformed. There might be" - " something wrong with the software of" - " your acquaintance." - ).arg(toAddress)) + self.statusBar().showMessage(_translate( + "MainWindow", "Error: Some data encoded in the address %1 is malformed. There might be something wrong with the software of your acquaintance.").arg(toAddress)) else: - self.updateStatusBar(_translate( - "MainWindow", - "Error: Something is wrong with the" - " recipient address %1." - ).arg(toAddress)) + self.statusBar().showMessage(_translate( + "MainWindow", "Error: Something is wrong with the address %1.").arg(toAddress)) elif fromAddress == '': - self.updateStatusBar(_translate( - "MainWindow", - "Error: You must specify a From address. If you" - " don\'t have one, go to the" - " \'Your Identities\' tab.") - ) + self.statusBar().showMessage(_translate( + "MainWindow", "Error: You must specify a From address. If you don\'t have one, go to the \'Your Identities\' tab.")) else: toAddress = addBMIfNotPresent(toAddress) if addressVersionNumber > 4 or addressVersionNumber <= 1: - 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))) + 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))) + 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': - self.updateStatusBar(_translate( - "MainWindow", - "Warning: You are currently not connected." - " Bitmessage will do the work necessary to" - " send the message but it won\'t send until" - " you connect.") + self.statusBar().showMessage('') + if shared.statusIconColor == 'red': + self.statusBar().showMessage(_translate( + "MainWindow", "Warning: You are currently not connected. Bitmessage will do the work necessary to send the message but it won\'t send until you connect.")) + ackdata = OpenSSL.rand(32) + 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 + 2, # encodingtype + shared.config.getint('bitmessagesettings', 'ttl') ) - ackdata = helper_sent.insert( - toAddress=toAddress, fromAddress=fromAddress, - subject=subject, message=message, encoding=encoding) + toLabel = '' queryreturn = sqlQuery('''select label from addressbook where address=?''', toAddress) @@ -2279,109 +2063,108 @@ class MyForm(settingsmixin.SMainWindow): self.displayNewSentMessage( toAddress, toLabel, fromAddress, subject, message, ackdata) - queues.workerQueue.put(('sendmessage', toAddress)) + shared.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 - self.updateStatusBar(_translate( + self.statusBar().showMessage(_translate( "MainWindow", "Message queued.")) - # self.ui.tableWidgetInbox.setCurrentCell(0, 0) + #self.ui.tableWidgetInbox.setCurrentCell(0, 0) else: - self.updateStatusBar(_translate( + self.statusBar().showMessage(_translate( "MainWindow", "Your \'To\' field is empty.")) else: # User selected 'Broadcast' if fromAddress == '': - self.updateStatusBar(_translate( - "MainWindow", - "Error: You must specify a From address. If you don\'t" - " have one, go to the \'Your Identities\' tab." - )) + self.statusBar().showMessage(_translate( + "MainWindow", "Error: You must specify a From address. If you don\'t have one, go to the \'Your Identities\' tab.")) else: - self.statusbar.clearMessage() + self.statusBar().showMessage('') # We don't actually need the ackdata for acknowledgement since # this is a broadcast message, but we can use it to update the # user interface when the POW is done generating. + ackdata = OpenSSL.rand(32) 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 + 2, # encoding type + shared.config.getint('bitmessagesettings', 'ttl') + ) + sqlExecute( + '''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''', *t) toLabel = str_broadcast_subscribers - + self.displayNewSentMessage( toAddress, toLabel, fromAddress, subject, message, ackdata) - queues.workerQueue.put(('sendbroadcast', '')) + shared.workerQueue.put(('sendbroadcast', '')) self.ui.comboBoxSendFromBroadcast.setCurrentIndex(0) self.ui.lineEditSubjectBroadcast.setText('') self.ui.textEditMessageBroadcast.reset() - self.ui.tabWidget.setCurrentIndex( - self.ui.tabWidget.indexOf(self.ui.send) - ) + self.ui.tabWidget.setCurrentIndex(1) self.ui.tableWidgetInboxSubscriptions.setCurrentCell(0, 0) - self.updateStatusBar(_translate( + self.statusBar().showMessage(_translate( "MainWindow", "Broadcast queued.")) def click_pushButtonLoadFromAddressBook(self): self.ui.tabWidget.setCurrentIndex(5) for i in range(4): time.sleep(0.1) - self.statusbar.clearMessage() + self.statusBar().showMessage('') time.sleep(0.1) - self.updateStatusBar(_translate( - "MainWindow", - "Right click one or more entries in your address book and" - " select \'Send message to this address\'." - )) + self.statusBar().showMessage(_translate( + "MainWindow", "Right click one or more entries in your address book and select \'Send message to this address\'.")) def click_pushButtonFetchNamecoinID(self): - identities = str(self.ui.lineEditTo.text().toUtf8()).split(";") - err, addr = self.namecoin.query(identities[-1].strip()) + nc = namecoinConnection() + err, addr = nc.query(str(self.ui.lineEditTo.text())) if err is not None: - self.updateStatusBar( - _translate("MainWindow", "Error: %1").arg(err)) + self.statusBar().showMessage(_translate( + "MainWindow", "Error: " + err)) else: - identities[-1] = addr - self.ui.lineEditTo.setText("; ".join(identities)) - self.updateStatusBar(_translate( + self.ui.lineEditTo.setText(addr) + self.statusBar().showMessage(_translate( "MainWindow", "Fetched address from namecoin identity.")) def setBroadcastEnablementDependingOnWhetherThisIsAMailingListAddress(self, address): # If this is a chan then don't let people broadcast because no one # should subscribe to chan addresses. - self.ui.tabWidgetSend.setCurrentIndex( - self.ui.tabWidgetSend.indexOf( - self.ui.sendBroadcast - if config.safeGetBoolean(str(address), 'mailinglist') - else self.ui.sendDirect - )) + if shared.safeConfigGetBoolean(str(address), 'mailinglist'): + self.ui.tabWidgetSend.setCurrentIndex(1) + else: + self.ui.tabWidgetSend.setCurrentIndex(0) 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 = shared.config.getboolean( + addressInKeysFile, 'enabled') # I realize that this is poor programming practice but I don't care. It's easier for others to read. + isMaillinglist = shared.safeConfigGetBoolean(addressInKeysFile, 'mailinglist') if isEnabled and not isMaillinglist: - label = unicode(config.get(addressInKeysFile, 'label'), 'utf-8', 'ignore').strip() - if label == "": - label = addressInKeysFile - self.ui.comboBoxSendFrom.addItem(avatarize(addressInKeysFile), label, addressInKeysFile) + self.ui.comboBoxSendFrom.addItem(avatarize(addressInKeysFile), unicode(shared.config.get( + addressInKeysFile, 'label'), 'utf-8'), addressInKeysFile) # self.ui.comboBoxSendFrom.model().sort(1, Qt.AscendingOrder) for i in range(self.ui.comboBoxSendFrom.count()): - address = str(self.ui.comboBoxSendFrom.itemData( - i, QtCore.Qt.UserRole).toString()) - self.ui.comboBoxSendFrom.setItemData( - i, AccountColor(address).accountColor(), - QtCore.Qt.ForegroundRole) + address = str(self.ui.comboBoxSendFrom.itemData(i, Qt.UserRole).toString()) + self.ui.comboBoxSendFrom.setItemData(i, AccountColor(address).accountColor(), Qt.ForegroundRole) self.ui.comboBoxSendFrom.insertItem(0, '', '') if(self.ui.comboBoxSendFrom.count() == 2): self.ui.comboBoxSendFrom.setCurrentIndex(1) @@ -2390,21 +2173,16 @@ 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 = shared.config.getboolean( + addressInKeysFile, 'enabled') # I realize that this is poor programming practice but I don't care. It's easier for others to read. + isChan = shared.safeConfigGetBoolean(addressInKeysFile, 'chan') if isEnabled and not isChan: - label = unicode(config.get(addressInKeysFile, 'label'), 'utf-8', 'ignore').strip() - if label == "": - label = addressInKeysFile - self.ui.comboBoxSendFromBroadcast.addItem(avatarize(addressInKeysFile), label, addressInKeysFile) + self.ui.comboBoxSendFromBroadcast.addItem(avatarize(addressInKeysFile), unicode(shared.config.get( + addressInKeysFile, 'label'), 'utf-8'), addressInKeysFile) for i in range(self.ui.comboBoxSendFromBroadcast.count()): - address = str(self.ui.comboBoxSendFromBroadcast.itemData( - i, QtCore.Qt.UserRole).toString()) - self.ui.comboBoxSendFromBroadcast.setItemData( - i, AccountColor(address).accountColor(), - QtCore.Qt.ForegroundRole) + address = str(self.ui.comboBoxSendFromBroadcast.itemData(i, Qt.UserRole).toString()) + self.ui.comboBoxSendFromBroadcast.setItemData(i, AccountColor(address).accountColor(), Qt.ForegroundRole) self.ui.comboBoxSendFromBroadcast.insertItem(0, '', '') if(self.ui.comboBoxSendFromBroadcast.count() == 2): self.ui.comboBoxSendFromBroadcast.setCurrentIndex(1) @@ -2415,502 +2193,582 @@ 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( - 'bitmessagesettings', 'showtraynotifications'): - self.notifierShow( - _translate("MainWindow", "New Message"), - _translate("MainWindow", "From %1").arg( - unicode(acct.fromLabel, 'utf-8')), - sound.SOUND_UNKNOWN - ) - if self.getCurrentAccount() is not None and ( - (self.getCurrentFolder(treeWidget) != "inbox" - and self.getCurrentFolder(treeWidget) is not None) - or self.getCurrentAccount(treeWidget) != acct.address): - # Ubuntu should notify of new message irrespective of - # whether it's in current message list or not - self.indicatorUpdate(True, to_label=acct.toLabel) + self.propagateUnreadCount(acct.address) + if shared.config.getboolean('bitmessagesettings', 'showtraynotifications'): + self.notifierShow(unicode(_translate("MainWindow",'New Message').toUtf8(),'utf-8'), unicode(_translate("MainWindow",'From ').toUtf8(),'utf-8') + unicode(acct.fromLabel, 'utf-8'), self.SOUND_UNKNOWN, None) + if self.getCurrentAccount() is not None and ((self.getCurrentFolder(treeWidget) != "inbox" and self.getCurrentFolder(treeWidget) is not None) or self.getCurrentAccount(treeWidget) != acct.address): + # Ubuntu should notify of new message irespective of whether it's in current message list or not + self.ubuntuMessagingMenuUpdate(True, None, acct.toLabel) + if hasattr(acct, "feedback") and acct.feedback != GatewayAccount.ALL_OK: + if acct.feedback == GatewayAccount.REGISTRATION_DENIED: + self.dialog = EmailGatewayRegistrationDialog(self, _translate("EmailGatewayRegistrationDialog", "Registration failed:"), + _translate("EmailGatewayRegistrationDialog", "The requested email address is not available, please try a new one. Fill out the new desired email address (including @mailchuck.com) below:") + ) + if self.dialog.exec_(): + email = str(self.dialog.ui.lineEditEmail.text().toUtf8()) + # register resets address variables + acct.register(email) + shared.config.set(acct.fromAddress, 'label', email) + shared.config.set(acct.fromAddress, 'gateway', 'mailchuck') + shared.writeKeysFile() + self.statusBar().showMessage(_translate( + "MainWindow", "Sending email gateway registration request")) - 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 + def click_pushButtonAddAddressBook(self): + self.AddAddressDialogInstance = AddAddressDialog(self) + if self.AddAddressDialogInstance.exec_(): + if self.AddAddressDialogInstance.ui.labelAddressCheck.text() == _translate("MainWindow", "Address is valid."): + # First we must check to see if the address is already in the + # address book. The user cannot add it again or else it will + # cause problems when updating and deleting the entry. + address = addBMIfNotPresent(str( + self.AddAddressDialogInstance.ui.lineEditAddress.text())) + label = self.AddAddressDialogInstance.ui.newAddressLabel.text().toUtf8() + self.addEntryToAddressBook(address,label) + else: + self.statusBar().showMessage(_translate( + "MainWindow", "The address you entered was invalid. Ignoring it.")) - def click_pushButtonAddAddressBook(self, dialog=None): - if not dialog: - dialog = dialogs.AddAddressDialog(self) - dialog.exec_() - try: - address, label = dialog.data - except AttributeError: - return - - # First we must check to see if the address is already in the - # address book. The user cannot add it again or else it will - # cause problems when updating and deleting the entry. - if shared.isAddressInMyAddressBook(address): - self.updateStatusBar(_translate( - "MainWindow", - "Error: You cannot add the same address to your" - " address book twice. Try renaming the existing one" - " if you want." - )) - return - - if helper_addressbook.insert(label=label, address=address): + def addEntryToAddressBook(self,address,label): + queryreturn = sqlQuery('''select * from addressbook where address=?''', address) + if queryreturn == []: + sqlExecute('''INSERT INTO addressbook VALUES (?,?)''', str(label), address) self.rerenderMessagelistFromLabels() self.rerenderMessagelistToLabels() self.rerenderAddressBook() else: - self.updateStatusBar(_translate( - "MainWindow", - "Error: You cannot add your own address in the address book." - )) + self.statusBar().showMessage(_translate( + "MainWindow", "Error: You cannot add the same address to your address book twice. Try renaming the existing one if you want.")) def addSubscription(self, address, label): - # This should be handled outside of this function, for error displaying - # and such, but it must also be checked here. + address = addBMIfNotPresent(address) + #This should be handled outside of this function, for error displaying and such, but it must also be checked here. if shared.isAddressInMySubscriptionsList(address): return - # Add to database (perhaps this should be separated from the MyForm class) - sqlExecute( - '''INSERT INTO subscriptions VALUES (?,?,?)''', - label, address, True - ) + #Add to database (perhaps this should be separated from the MyForm class) + sqlExecute('''INSERT INTO subscriptions VALUES (?,?,?)''',str(label),address,True) self.rerenderMessagelistFromLabels() shared.reloadBroadcastSendersForWhichImWatching() self.rerenderAddressBook() self.rerenderTabTreeSubscriptions() def click_pushButtonAddSubscription(self): - dialog = dialogs.NewSubscriptionDialog(self) - dialog.exec_() - try: - address, label = dialog.data - except AttributeError: - return - - # We must check to see if the address is already in the - # subscriptions list. The user cannot add it again or else it - # will cause problems when updating and deleting the entry. - if shared.isAddressInMySubscriptionsList(address): - self.updateStatusBar(_translate( - "MainWindow", - "Error: You cannot add the same address to your" - " subscriptions twice. Perhaps rename the existing one" - " if you want." - )) - return - - self.addSubscription(address, label) - # Now, if the user wants to display old broadcasts, let's get - # them out of the inventory and put them - # to the objectProcessorQueue to be processed - if dialog.checkBoxDisplayMessagesAlreadyInInventory.isChecked(): - for value in dialog.recent: - queues.objectProcessorQueue.put(( - value.type, value.payload - )) + self.NewSubscriptionDialogInstance = NewSubscriptionDialog(self) + if self.NewSubscriptionDialogInstance.exec_(): + if self.NewSubscriptionDialogInstance.ui.labelAddressCheck.text() != _translate("MainWindow", "Address is valid."): + self.statusBar().showMessage(_translate("MainWindow", "The address you entered was invalid. Ignoring it.")) + return + address = addBMIfNotPresent(str(self.NewSubscriptionDialogInstance.ui.lineEditSubscriptionAddress.text())) + # We must check to see if the address is already in the subscriptions list. The user cannot add it again or else it will cause problems when updating and deleting the entry. + if shared.isAddressInMySubscriptionsList(address): + self.statusBar().showMessage(_translate("MainWindow", "Error: You cannot add the same address to your subscriptions twice. Perhaps rename the existing one if you want.")) + return + label = self.NewSubscriptionDialogInstance.ui.newsubscriptionlabel.text().toUtf8() + self.addSubscription(address, label) + # Now, if the user wants to display old broadcasts, let's get them out of the inventory and put them + # in the objectProcessorQueue to be processed + if self.NewSubscriptionDialogInstance.ui.checkBoxDisplayMessagesAlreadyInInventory.isChecked(): + status, addressVersion, streamNumber, ripe = decodeAddress(address) + shared.inventory.flush() + doubleHashOfAddressData = hashlib.sha512(hashlib.sha512(encodeVarint( + addressVersion) + encodeVarint(streamNumber) + ripe).digest()).digest() + tag = doubleHashOfAddressData[32:] + for value in shared.inventory.by_type_and_tag(3, tag): + shared.objectProcessorQueue.put((value.type, value.payload)) def click_pushButtonStatusIcon(self): - dialogs.IconGlossaryDialog(self, config=config).exec_() + logger.debug('click_pushButtonStatusIcon') + self.iconGlossaryInstance = iconGlossaryDialog(self) + if self.iconGlossaryInstance.exec_(): + pass def click_actionHelp(self): - dialogs.HelpDialog(self).exec_() + self.helpDialogInstance = helpDialog(self) + self.helpDialogInstance.exec_() def click_actionSupport(self): support.createSupportMessage(self) def click_actionAbout(self): - dialogs.AboutDialog(self).exec_() + self.aboutDialogInstance = aboutDialog(self) + self.aboutDialogInstance.exec_() def click_actionSettings(self): - dialogs.SettingsDialog(self, firstrun=self._firstrun).exec_() + self.settingsDialogInstance = settingsDialog(self) + if self.settingsDialogInstance.exec_(): + shared.config.set('bitmessagesettings', 'startonlogon', str( + self.settingsDialogInstance.ui.checkBoxStartOnLogon.isChecked())) + shared.config.set('bitmessagesettings', 'minimizetotray', str( + self.settingsDialogInstance.ui.checkBoxMinimizeToTray.isChecked())) + shared.config.set('bitmessagesettings', 'trayonclose', str( + self.settingsDialogInstance.ui.checkBoxTrayOnClose.isChecked())) + shared.config.set('bitmessagesettings', 'showtraynotifications', str( + self.settingsDialogInstance.ui.checkBoxShowTrayNotifications.isChecked())) + shared.config.set('bitmessagesettings', 'startintray', str( + self.settingsDialogInstance.ui.checkBoxStartInTray.isChecked())) + shared.config.set('bitmessagesettings', 'willinglysendtomobile', str( + self.settingsDialogInstance.ui.checkBoxWillinglySendToMobile.isChecked())) + shared.config.set('bitmessagesettings', 'useidenticons', str( + self.settingsDialogInstance.ui.checkBoxUseIdenticons.isChecked())) + shared.config.set('bitmessagesettings', 'replybelow', str( + self.settingsDialogInstance.ui.checkBoxReplyBelow.isChecked())) + + lang = str(self.settingsDialogInstance.ui.languageComboBox.itemData(self.settingsDialogInstance.ui.languageComboBox.currentIndex()).toString()) + shared.config.set('bitmessagesettings', 'userlocale', lang) + logger.debug("Setting locale to %s", lang) + change_translation(lang) + + if int(shared.config.get('bitmessagesettings', 'port')) != int(self.settingsDialogInstance.ui.lineEditTCPPort.text()): + if not shared.safeConfigGetBoolean('bitmessagesettings', 'dontconnect'): + QMessageBox.about(self, _translate("MainWindow", "Restart"), _translate( + "MainWindow", "You must restart Bitmessage for the port number change to take effect.")) + shared.config.set('bitmessagesettings', 'port', str( + self.settingsDialogInstance.ui.lineEditTCPPort.text())) + if self.settingsDialogInstance.ui.checkBoxUPnP.isChecked() != shared.safeConfigGetBoolean('bitmessagesettings', 'upnp'): + shared.config.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 shared.config.get('bitmessagesettings', 'socksproxytype') == 'none' and self.settingsDialogInstance.ui.comboBoxProxyType.currentText()[0:5] == 'SOCKS': + if shared.statusIconColor != 'red': + 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 shared.config.get('bitmessagesettings', 'socksproxytype')[0:5] == 'SOCKS' and self.settingsDialogInstance.ui.comboBoxProxyType.currentText()[0:5] != 'SOCKS': + self.statusBar().showMessage('') + if self.settingsDialogInstance.ui.comboBoxProxyType.currentText()[0:5] == 'SOCKS': + shared.config.set('bitmessagesettings', 'socksproxytype', str( + self.settingsDialogInstance.ui.comboBoxProxyType.currentText())) + else: + shared.config.set('bitmessagesettings', 'socksproxytype', 'none') + shared.config.set('bitmessagesettings', 'socksauthentication', str( + self.settingsDialogInstance.ui.checkBoxAuthentication.isChecked())) + shared.config.set('bitmessagesettings', 'sockshostname', str( + self.settingsDialogInstance.ui.lineEditSocksHostname.text())) + shared.config.set('bitmessagesettings', 'socksport', str( + self.settingsDialogInstance.ui.lineEditSocksPort.text())) + shared.config.set('bitmessagesettings', 'socksusername', str( + self.settingsDialogInstance.ui.lineEditSocksUsername.text())) + shared.config.set('bitmessagesettings', 'sockspassword', str( + self.settingsDialogInstance.ui.lineEditSocksPassword.text())) + shared.config.set('bitmessagesettings', 'sockslisten', str( + self.settingsDialogInstance.ui.checkBoxSocksListen.isChecked())) + try: + # Rounding to integers just for aesthetics + shared.config.set('bitmessagesettings', 'maxdownloadrate', str( + int(float(self.settingsDialogInstance.ui.lineEditMaxDownloadRate.text())))) + shared.config.set('bitmessagesettings', 'maxuploadrate', str( + int(float(self.settingsDialogInstance.ui.lineEditMaxUploadRate.text())))) + except: + QMessageBox.about(self, _translate("MainWindow", "Number needed"), _translate( + "MainWindow", "Your maximum download and upload rate must be numbers. Ignoring what you typed.")) - 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) - ) + shared.config.set('bitmessagesettings', 'namecoinrpctype', + self.settingsDialogInstance.getNamecoinType()) + shared.config.set('bitmessagesettings', 'namecoinrpchost', str( + self.settingsDialogInstance.ui.lineEditNamecoinHost.text())) + shared.config.set('bitmessagesettings', 'namecoinrpcport', str( + self.settingsDialogInstance.ui.lineEditNamecoinPort.text())) + shared.config.set('bitmessagesettings', 'namecoinrpcuser', str( + self.settingsDialogInstance.ui.lineEditNamecoinUser.text())) + shared.config.set('bitmessagesettings', 'namecoinrpcpassword', str( + self.settingsDialogInstance.ui.lineEditNamecoinPassword.text())) + + # Demanded difficulty tab + if float(self.settingsDialogInstance.ui.lineEditTotalDifficulty.text()) >= 1: + shared.config.set('bitmessagesettings', 'defaultnoncetrialsperbyte', str(int(float( + self.settingsDialogInstance.ui.lineEditTotalDifficulty.text()) * shared.networkDefaultProofOfWorkNonceTrialsPerByte))) + if float(self.settingsDialogInstance.ui.lineEditSmallMessageDifficulty.text()) >= 1: + shared.config.set('bitmessagesettings', 'defaultpayloadlengthextrabytes', str(int(float( + self.settingsDialogInstance.ui.lineEditSmallMessageDifficulty.text()) * shared.networkDefaultPayloadLengthExtraBytes))) + + if openclpow.has_opencl() and self.settingsDialogInstance.ui.checkBoxOpenCL.isChecked() != shared.safeConfigGetBoolean("bitmessagesettings", "opencl"): + shared.config.set('bitmessagesettings', 'opencl', str(self.settingsDialogInstance.ui.checkBoxOpenCL.isChecked())) + + acceptableDifficultyChanged = False + + if float(self.settingsDialogInstance.ui.lineEditMaxAcceptableTotalDifficulty.text()) >= 1 or float(self.settingsDialogInstance.ui.lineEditMaxAcceptableTotalDifficulty.text()) == 0: + if shared.config.get('bitmessagesettings','maxacceptablenoncetrialsperbyte') != str(int(float( + self.settingsDialogInstance.ui.lineEditMaxAcceptableTotalDifficulty.text()) * shared.networkDefaultProofOfWorkNonceTrialsPerByte)): + # the user changed the max acceptable total difficulty + acceptableDifficultyChanged = True + shared.config.set('bitmessagesettings', 'maxacceptablenoncetrialsperbyte', str(int(float( + self.settingsDialogInstance.ui.lineEditMaxAcceptableTotalDifficulty.text()) * shared.networkDefaultProofOfWorkNonceTrialsPerByte))) + if float(self.settingsDialogInstance.ui.lineEditMaxAcceptableSmallMessageDifficulty.text()) >= 1 or float(self.settingsDialogInstance.ui.lineEditMaxAcceptableSmallMessageDifficulty.text()) == 0: + if shared.config.get('bitmessagesettings','maxacceptablepayloadlengthextrabytes') != str(int(float( + self.settingsDialogInstance.ui.lineEditMaxAcceptableSmallMessageDifficulty.text()) * shared.networkDefaultPayloadLengthExtraBytes)): + # the user changed the max acceptable small message difficulty + acceptableDifficultyChanged = True + shared.config.set('bitmessagesettings', 'maxacceptablepayloadlengthextrabytes', str(int(float( + self.settingsDialogInstance.ui.lineEditMaxAcceptableSmallMessageDifficulty.text()) * shared.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' ''') + shared.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 + shared.config.set('bitmessagesettings', 'stopresendingafterxdays', '') + shared.config.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. + 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.")) + shared.config.set('bitmessagesettings', 'stopresendingafterxdays', '0') + shared.config.set('bitmessagesettings', 'stopresendingafterxmonths', '0') + shared.maximumLengthOfTimeToBotherResendingMessages = 0 + else: + shared.config.set('bitmessagesettings', 'stopresendingafterxdays', str(float( + self.settingsDialogInstance.ui.lineEditDays.text()))) + shared.config.set('bitmessagesettings', 'stopresendingafterxmonths', str(float( + self.settingsDialogInstance.ui.lineEditMonths.text()))) + + shared.writeKeysFile() + + if 'win32' in sys.platform or 'win64' in sys.platform: + # Auto-startup for Windows + RUN_PATH = "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run" + self.settings = QSettings(RUN_PATH, QSettings.NativeFormat) + if shared.config.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 shared.appdata != shared.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(shared.lookupExeFolder() + 'keys.dat', 'wb') as configfile: + shared.config.write(configfile) + # Write the knownnodes.dat file to disk in the new location + shared.knownNodesLock.acquire() + output = open(shared.lookupExeFolder() + 'knownnodes.dat', 'wb') + pickle.dump(shared.knownNodes, output) + output.close() + shared.knownNodesLock.release() + os.remove(shared.appdata + 'keys.dat') + os.remove(shared.appdata + 'knownnodes.dat') + previousAppdataLocation = shared.appdata + shared.appdata = shared.lookupExeFolder() + debug.restartLoggingInUpdatedAppdataLocation() + try: + os.remove(previousAppdataLocation + 'debug.log') + os.remove(previousAppdataLocation + 'debug.log.1') + except: + pass + + if shared.appdata == shared.lookupExeFolder() and not self.settingsDialogInstance.ui.checkBoxPortableMode.isChecked(): # If we ARE using portable mode now but the user selected that we shouldn't... + shared.appdata = shared.lookupAppdataFolder() + if not os.path.exists(shared.appdata): + os.makedirs(shared.appdata) + sqlStoredProcedure('movemessagstoappdata') + # Write the keys.dat file to disk in the new location + shared.writeKeysFile() + # Write the knownnodes.dat file to disk in the new location + shared.knownNodesLock.acquire() + output = open(shared.appdata + 'knownnodes.dat', 'wb') + pickle.dump(shared.knownNodes, output) + output.close() + shared.knownNodesLock.release() + os.remove(shared.lookupExeFolder() + 'keys.dat') + os.remove(shared.lookupExeFolder() + 'knownnodes.dat') + debug.restartLoggingInUpdatedAppdataLocation() + try: + os.remove(shared.lookupExeFolder() + 'debug.log') + os.remove(shared.lookupExeFolder() + 'debug.log.1') + except: + pass def on_action_SpecialAddressBehaviorDialog(self): - """Show SpecialAddressBehaviorDialog""" - dialogs.SpecialAddressBehaviorDialog(self, config) + self.dialog = SpecialAddressBehaviorDialog(self) + # For Modal dialogs + if self.dialog.exec_(): + addressAtCurrentRow = self.getCurrentAccount() + if shared.safeConfigGetBoolean(addressAtCurrentRow, 'chan'): + return + if self.dialog.ui.radioButtonBehaveNormalAddress.isChecked(): + shared.config.set(str( + addressAtCurrentRow), 'mailinglist', 'false') + # Set the color to either black or grey + if shared.config.getboolean(addressAtCurrentRow, 'enabled'): + self.setCurrentItemColor(QApplication.palette() + .text().color()) + else: + self.setCurrentItemColor(QtGui.QColor(128, 128, 128)) + else: + shared.config.set(str( + addressAtCurrentRow), 'mailinglist', 'true') + shared.config.set(str(addressAtCurrentRow), 'mailinglistname', str( + self.dialog.ui.lineEditMailingListName.text().toUtf8())) + self.setCurrentItemColor(QtGui.QColor(137, 04, 177)) #magenta + self.rerenderComboBoxSendFrom() + self.rerenderComboBoxSendFromBroadcast() + shared.writeKeysFile() + self.rerenderMessagelistToLabels() def on_action_EmailGatewayDialog(self): - dialog = dialogs.EmailGatewayDialog(self, config=config) + self.dialog = EmailGatewayDialog(self) # For Modal dialogs - dialog.exec_() - try: - acct = dialog.data - except AttributeError: - return - - # Only settings remain here - acct.settings() - for i in range(self.ui.comboBoxSendFrom.count()): - if str(self.ui.comboBoxSendFrom.itemData(i).toPyObject()) \ - == acct.fromAddress: - self.ui.comboBoxSendFrom.setCurrentIndex(i) - break - else: - self.ui.comboBoxSendFrom.setCurrentIndex(0) - - self.ui.lineEditTo.setText(acct.toAddress) - self.ui.lineEditSubject.setText(acct.subject) - self.ui.textEditMessage.setText(acct.message) - self.ui.tabWidgetSend.setCurrentIndex( - self.ui.tabWidgetSend.indexOf(self.ui.sendDirect) - ) - self.ui.tabWidget.setCurrentIndex( - self.ui.tabWidget.indexOf(self.ui.send) - ) - self.ui.textEditMessage.setFocus() - - def on_action_MarkAllRead(self): - if QtGui.QMessageBox.question( - self, "Marking all messages as read?", - _translate( - "MainWindow", - "Are you sure you would like to mark all messages read?" - ), QtGui.QMessageBox.Yes | QtGui.QMessageBox.No - ) != QtGui.QMessageBox.Yes: - return - tableWidget = self.getCurrentMessagelist() - - idCount = tableWidget.rowCount() - if idCount == 0: - return - - 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) - - markread = sqlExecuteChunked( - "UPDATE inbox SET read = 1 WHERE msgid IN({0}) AND read=0", - idCount, *msgids - ) - - if markread > 0: - self.propagateUnreadCount() - + if self.dialog.exec_(): + addressAtCurrentRow = self.getCurrentAccount() + acct = accountClass(addressAtCurrentRow) + # no chans / mailinglists + if acct.type != AccountMixin.NORMAL: + return + if self.dialog.ui.radioButtonUnregister.isChecked() and isinstance(acct, GatewayAccount): + acct.unregister() + shared.config.remove_option(addressAtCurrentRow, 'gateway') + shared.writeKeysFile() + self.statusBar().showMessage(_translate( + "MainWindow", "Sending email gateway unregistration request")) + elif self.dialog.ui.radioButtonStatus.isChecked() and isinstance(acct, GatewayAccount): + acct.status() + self.statusBar().showMessage(_translate( + "MainWindow", "Sending email gateway status request")) + elif self.dialog.ui.radioButtonSettings.isChecked() and isinstance(acct, GatewayAccount): + acct.settings() + listOfAddressesInComboBoxSendFrom = [str(self.ui.comboBoxSendFrom.itemData(i).toPyObject()) for i in range(self.ui.comboBoxSendFrom.count())] + if acct.fromAddress in listOfAddressesInComboBoxSendFrom: + currentIndex = listOfAddressesInComboBoxSendFrom.index(acct.fromAddress) + self.ui.comboBoxSendFrom.setCurrentIndex(currentIndex) + else: + self.ui.comboBoxSendFrom.setCurrentIndex(0) + self.ui.lineEditTo.setText(acct.toAddress) + self.ui.lineEditSubject.setText(acct.subject) + self.ui.textEditMessage.setText(acct.message) + self.ui.tabWidgetSend.setCurrentIndex(0) + self.ui.tabWidget.setCurrentIndex(1) + self.ui.textEditMessage.setFocus() + elif self.dialog.ui.radioButtonRegister.isChecked(): + email = str(self.dialog.ui.lineEditEmail.text().toUtf8()) + acct = MailchuckAccount(addressAtCurrentRow) + acct.register(email) + shared.config.set(addressAtCurrentRow, 'label', email) + shared.config.set(addressAtCurrentRow, 'gateway', 'mailchuck') + shared.writeKeysFile() + self.statusBar().showMessage(_translate( + "MainWindow", "Sending email gateway registration request")) + else: + pass + #print "well nothing" +# shared.writeKeysFile() +# self.rerenderInboxToLabels() + def click_NewAddressDialog(self): - dialogs.NewAddressDialog(self) - - def network_switch(self): - dontconnect_option = not config.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( - 'bitmessagesettings', 'dontconnect', str(dontconnect_option)) - config.save() - self.ui.updateNetworkSwitchMenuLabel(dontconnect_option) - - self.ui.pushButtonFetchNamecoinID.setHidden( - dontconnect_option or self.namecoin.test()[0] == 'failed' - ) + addresses = [] + for addressInKeysFile in getSortedAccounts(): + addresses.append(addressInKeysFile) +# self.dialog = Ui_NewAddressWizard(addresses) +# self.dialog.exec_() +# print "Name: " + self.dialog.field("name").toString() +# print "Email: " + self.dialog.field("email").toString() +# return + self.dialog = NewAddressDialog(self) + # For Modal dialogs + if self.dialog.exec_(): + # self.dialog.ui.buttonBox.enabled = False + if self.dialog.ui.radioButtonRandomAddress.isChecked(): + if self.dialog.ui.radioButtonMostAvailable.isChecked(): + streamNumberForAddress = 1 + else: + # User selected 'Use the same stream as an existing + # address.' + streamNumberForAddress = decodeAddress( + self.dialog.ui.comboBoxExisting.currentText())[2] + shared.addressGeneratorQueue.put(('createRandomAddress', 4, streamNumberForAddress, str( + self.dialog.ui.newaddresslabel.text().toUtf8()), 1, "", self.dialog.ui.checkBoxEighteenByteRipe.isChecked())) + else: + if self.dialog.ui.lineEditPassphrase.text() != self.dialog.ui.lineEditPassphraseAgain.text(): + QMessageBox.about(self, _translate("MainWindow", "Passphrase mismatch"), _translate( + "MainWindow", "The passphrase you entered twice doesn\'t match. Try again.")) + elif self.dialog.ui.lineEditPassphrase.text() == "": + QMessageBox.about(self, _translate( + "MainWindow", "Choose a passphrase"), _translate("MainWindow", "You really do need a passphrase.")) + else: + streamNumberForAddress = 1 # this will eventually have to be replaced by logic to determine the most available stream number. + shared.addressGeneratorQueue.put(('createDeterministicAddresses', 4, streamNumberForAddress, "unused deterministic address", self.dialog.ui.spinBoxNumberOfAddressesToMake.value( + ), self.dialog.ui.lineEditPassphrase.text().toUtf8(), self.dialog.ui.checkBoxEighteenByteRipe.isChecked())) + else: + logger.debug('new address dialog box rejected') # 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 + ''' - self.show() - self.raise_() - self.activateWindow() - - waitForPow = True - waitForConnection = False - 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 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 reply == QtGui.QMessageBox.Yes: - self.wait = waitForSync = True - elif reply == QtGui.QMessageBox.Cancel: - return - - if state.statusIconColor == 'red' and not config.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) - if reply == QtGui.QMessageBox.Yes: - waitForConnection = True - self.wait = waitForSync = True - elif reply == QtGui.QMessageBox.Cancel: - return - - self.quitAccepted = True - - self.updateStatusBar(_translate( - "MainWindow", "Shutting down PyBitmessage... %1%").arg(0)) - - if waitForConnection: - self.updateStatusBar(_translate( - "MainWindow", "Waiting for network connection...")) - while state.statusIconColor == 'red': + self.statusBar().showMessage(_translate( + "MainWindow", "Shutting down PyBitmessage... %1%").arg(str(0))) + + # check if PoW queue empty + maxWorkerQueue = 0 + curWorkerQueue = 1 + while curWorkerQueue > 0: + # worker queue size + curWorkerQueue = shared.workerQueue.qsize() + # if worker is busy add 1 + for thread in threading.enumerate(): + try: + if isinstance(thread, singleWorker): + curWorkerQueue += thread.busy + except: + pass + if curWorkerQueue > maxWorkerQueue: + maxWorkerQueue = curWorkerQueue + if curWorkerQueue > 0: + self.statusBar().showMessage(_translate("MainWindow", "Waiting for PoW to finish... %1%").arg(str(50 * (maxWorkerQueue - curWorkerQueue) / maxWorkerQueue))) time.sleep(0.5) - QtCore.QCoreApplication.processEvents( - QtCore.QEventLoop.AllEvents, 1000 - ) + QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents, 1000) - # this probably will not work correctly, because there is a delay - # between the status icon turning red and inventory exchange, - # but it's better than nothing. - if waitForSync: - self.updateStatusBar(_translate( - "MainWindow", "Waiting for finishing synchronisation...")) - while pendingDownload() > 0: + self.statusBar().showMessage(_translate("MainWindow", "Shutting down Pybitmessage... %1%").arg(str(50))) + + QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents, 1000) + if maxWorkerQueue > 0: + time.sleep(0.5) # a bit of time so that the hashHolder is populated + QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents, 1000) + + # check if objectHashHolder empty + self.statusBar().showMessage(_translate("MainWindow", "Waiting for objects to be sent... %1%").arg(str(50))) + maxWaitingObjects = 0 + curWaitingObjects = 1 + while curWaitingObjects > 0: + curWaitingObjects = 0 + for thread in threading.enumerate(): + try: + if isinstance(thread, objectHashHolder): + curWaitingObjects += thread.hashCount() + except: + pass + if curWaitingObjects > maxWaitingObjects: + maxWaitingObjects = curWaitingObjects + if curWaitingObjects > 0: + self.statusBar().showMessage(_translate("MainWindow", "Waiting for objects to be sent... %1%").arg(str(50 + 20 * (maxWaitingObjects - curWaitingObjects) / maxWaitingObjects))) time.sleep(0.5) - QtCore.QCoreApplication.processEvents( - QtCore.QEventLoop.AllEvents, 1000 - ) + QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents, 1000) - if waitForPow: - # check if PoW queue empty - maxWorkerQueue = 0 - curWorkerQueue = powQueueSize() - while curWorkerQueue > 0: - # worker queue size - curWorkerQueue = powQueueSize() - if curWorkerQueue > maxWorkerQueue: - maxWorkerQueue = curWorkerQueue - if curWorkerQueue > 0: - self.updateStatusBar(_translate( - "MainWindow", "Waiting for PoW to finish... %1%" - ).arg(50 * (maxWorkerQueue - curWorkerQueue) / - maxWorkerQueue)) - time.sleep(0.5) - QtCore.QCoreApplication.processEvents( - QtCore.QEventLoop.AllEvents, 1000 - ) - - self.updateStatusBar(_translate( - "MainWindow", "Shutting down Pybitmessage... %1%").arg(50)) - - QtCore.QCoreApplication.processEvents( - QtCore.QEventLoop.AllEvents, 1000 - ) - if maxWorkerQueue > 0: - # a bit of time so that the hashHolder is populated - time.sleep(0.5) - QtCore.QCoreApplication.processEvents( - QtCore.QEventLoop.AllEvents, 1000 - ) - - # check if upload (of objects created locally) pending - self.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 - ) - - QtCore.QCoreApplication.processEvents( - QtCore.QEventLoop.AllEvents, 1000 - ) - QtCore.QCoreApplication.processEvents( - QtCore.QEventLoop.AllEvents, 1000 - ) + QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents, 1000) + if maxWorkerQueue > 0 or maxWaitingObjects > 0: + time.sleep(10) # a bit of time so that the other nodes retrieve the objects + QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents, 1000) # save state and geometry self and all widgets - self.updateStatusBar(_translate( - "MainWindow", "Saving settings... %1%").arg(70)) - QtCore.QCoreApplication.processEvents( - QtCore.QEventLoop.AllEvents, 1000 - ) + self.statusBar().showMessage(_translate("MainWindow", "Saving settings... %1%").arg(str(70))) + QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents, 1000) self.saveSettings() for attr, obj in self.ui.__dict__.iteritems(): - if hasattr(obj, "__class__") \ - and isinstance(obj, settingsmixin.SettingsMixin): + if hasattr(obj, "__class__") and isinstance(obj, settingsmixin.SettingsMixin): saveMethod = getattr(obj, "saveSettings", None) - if callable(saveMethod): + if callable (saveMethod): obj.saveSettings() - self.updateStatusBar(_translate( - "MainWindow", "Shutting down core... %1%").arg(80)) - QtCore.QCoreApplication.processEvents( - QtCore.QEventLoop.AllEvents, 1000 - ) - shutdown.doCleanShutdown() - - self.updateStatusBar(_translate( - "MainWindow", "Stopping notifications... %1%").arg(90)) + self.statusBar().showMessage(_translate("MainWindow", "Shutting down core... %1%").arg(str(80))) + QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents, 1000) + shared.doCleanShutdown() + self.statusBar().showMessage(_translate("MainWindow", "Stopping notifications... %1%").arg(str(90))) self.tray.hide() + # unregister the messaging system + if self.mmapp is not None: + self.mmapp.unregister() - self.updateStatusBar(_translate( - "MainWindow", "Shutdown imminent... %1%").arg(100)) - + self.statusBar().showMessage(_translate("MainWindow", "Shutdown imminent... %1%").arg(str(100))) + shared.thisapp.cleanup() logger.info("Shutdown complete") - self.close() - # FIXME: rewrite loops with timer instead - if self.wait: - self.destroy() - app.quit() + os._exit(0) + # window close event def closeEvent(self, event): - """window close event""" - event.ignore() - trayonclose = config.safeGetBoolean( - 'bitmessagesettings', 'trayonclose') + self.appIndicatorHide() + trayonclose = False + + try: + trayonclose = shared.config.getboolean( + 'bitmessagesettings', 'trayonclose') + except Exception: + pass + if trayonclose: - self.appIndicatorHide() + # minimize the application + event.ignore() else: - # custom quit method + # quit the application + event.accept() self.quit() def on_action_InboxMessageForceHtml(self): @@ -2944,44 +2802,47 @@ class MyForm(settingsmixin.SMainWindow): tableWidget = self.getCurrentMessagelist() if not tableWidget: return - - msgids = set() - # modified = 0 + font = QFont() + font.setBold(True) + inventoryHashesToMarkUnread = [] + modified = 0 for row in tableWidget.selectedIndexes(): currentRow = row.row() - msgid = tableWidget.item(currentRow, 3).data() - msgids.add(msgid) - # if not tableWidget.item(currentRow, 0).unread: - # modified += 1 - self.updateUnreadStatus(tableWidget, currentRow, msgid, False) - - # for 1081 - idCount = len(msgids) - # rowcount = - sqlExecuteChunked( - '''UPDATE inbox SET read=0 WHERE msgid IN ({0}) AND read=1''', - idCount, *msgids - ) - - self.propagateUnreadCount() - # 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. + inventoryHashToMarkUnread = str(tableWidget.item( + currentRow, 3).data(Qt.UserRole).toPyObject()) + if inventoryHashToMarkUnread in inventoryHashesToMarkUnread: + # it returns columns as separate items, so we skip dupes + continue + if not tableWidget.item(currentRow, 0).unread: + modified += 1 + inventoryHashesToMarkUnread.append(inventoryHashToMarkUnread) + tableWidget.item(currentRow, 0).setUnread(True) + tableWidget.item(currentRow, 1).setUnread(True) + tableWidget.item(currentRow, 2).setUnread(True) + tableWidget.item(currentRow, 3).setFont(font) + #sqlite requires the exact number of ?s to prevent injection + rowcount = sqlExecute('''UPDATE inbox SET read=0 WHERE msgid IN (%s) AND read=1''' % ( + "?," * len(inventoryHashesToMarkUnread))[:-1], *inventoryHashesToMarkUnread) + if rowcount == 1: + # performance optimisation + self.propagateUnreadCount(tableWidget.item(currentRow, 1 if tableWidget.item(currentRow, 1).type == AccountMixin.SUBSCRIPTION else 0).data(Qt.UserRole), self.getCurrentFolder()) + else: + self.propagateUnreadCount(tableWidget.item(currentRow, 1 if tableWidget.item(currentRow, 1).type == AccountMixin.SUBSCRIPTION else 0).data(Qt.UserRole), self.getCurrentFolder(), self.getCurrentTreeWidget(), 0) + # 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 shared.safeConfigGetBoolean('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(): @@ -2994,143 +2855,96 @@ 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(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) - ) + self.ui.tabWidgetSend.setCurrentIndex(0) # 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 shared.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), QMessageBox.Ok) + elif not shared.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."), QMessageBox.Ok) else: self.setBroadcastEnablementDependingOnWhetherThisIsAMailingListAddress(toAddressAtCurrentInboxRow) - broadcast_tab_index = self.ui.tabWidgetSend.indexOf( - self.ui.sendBroadcast - ) - if self.ui.tabWidgetSend.currentIndex() == broadcast_tab_index: + if self.ui.tabWidgetSend.currentIndex() == 1: widget = { 'subject': self.ui.lineEditSubjectBroadcast, 'from': self.ui.comboBoxSendFromBroadcast, 'message': self.ui.textEditMessageBroadcast } - self.ui.tabWidgetSend.setCurrentIndex(broadcast_tab_index) + self.ui.tabWidgetSend.setCurrentIndex(1) 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) - self.ui.tabWidget.setCurrentIndex( - self.ui.tabWidget.indexOf(self.ui.send) - ) + widget['subject'].setText('Re: ' + tableWidget.item(currentInboxRow, 2).label) + self.ui.tabWidget.setCurrentIndex(1) widget['message'].setFocus() def on_action_InboxAddSenderToAddressBook(self): @@ -3138,65 +2952,64 @@ 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( - self.ui.tabWidget.indexOf(self.ui.send) - ) - self.click_pushButtonAddAddressBook( - dialogs.AddAddressDialog(self, addressAtCurrentInboxRow)) + currentInboxRow, 1).data(Qt.UserRole) + # Let's make sure that it isn't already in the address book + queryreturn = sqlQuery('''select * from addressbook where address=?''', + addressAtCurrentInboxRow) + if queryreturn == []: + sqlExecute('''INSERT INTO addressbook VALUES (?,?)''', + '--New entry. Change label in Address Book.--', + addressAtCurrentInboxRow) + self.rerenderAddressBook() + self.statusBar().showMessage(_translate( + "MainWindow", "Entry added to the Address Book. Edit the label to your liking.")) + else: + self.statusBar().showMessage(_translate( + "MainWindow", "Error: You cannot add the same address to your address book twice. Try renaming the existing one if you want.")) def on_action_InboxAddSenderToBlackList(self): tableWidget = self.getCurrentMessagelist() if not tableWidget: return currentInboxRow = tableWidget.currentRow() + # tableWidget.item(currentRow,1).data(Qt.UserRole).toPyObject() addressAtCurrentInboxRow = tableWidget.item( - currentInboxRow, 1).data(QtCore.Qt.UserRole) + currentInboxRow, 1).data(Qt.UserRole) recipientAddress = tableWidget.item( - currentInboxRow, 0).data(QtCore.Qt.UserRole) + currentInboxRow, 0).data(Qt.UserRole) # Let's make sure that it isn't already in the address book 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 " + shared.config.get(recipientAddress, "label") sqlExecute('''INSERT INTO blacklist VALUES (?,?, ?)''', label, addressAtCurrentInboxRow, True) self.ui.blackwhitelist.rerenderBlackWhiteList() - self.updateStatusBar(_translate( - "MainWindow", - "Entry added to the blacklist. Edit the label to your liking.") - ) + self.statusBar().showMessage(_translate( + "MainWindow", "Entry added to the blacklist. Edit the label to your liking.")) else: - self.updateStatusBar(_translate( - "MainWindow", - "Error: You cannot add the same address to your blacklist" - " twice. Try renaming the existing one if you want.")) + self.statusBar().showMessage(_translate( + "MainWindow", "Error: You cannot add the same address to your blacklist twice. Try renaming the existing one if you want.")) - def deleteRowFromMessagelist( - self, row=None, inventoryHash=None, ackData=None, messageLists=None - ): + def deleteRowFromMessagelist(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(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(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(Qt.UserRole).toPyObject() == ackData: messageList.removeRow(i) # Send item on the Inbox tab to trash @@ -3204,60 +3017,54 @@ class MyForm(settingsmixin.SMainWindow): tableWidget = self.getCurrentMessagelist() if not tableWidget: return + unread = False currentRow = 0 folder = self.getCurrentFolder() - shifted = QtGui.QApplication.queryKeyboardModifiers() \ - & QtCore.Qt.ShiftModifier - tableWidget.setUpdatesEnabled(False) - inventoryHashesToTrash = set() - # 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()) - currentRow = r.topRow() - self.getCurrentMessageTextedit().setText("") - 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) - tableWidget.selectRow(0 if currentRow == 0 else currentRow - 1) - tableWidget.setUpdatesEnabled(True) - self.propagateUnreadCount(folder) - self.updateStatusBar(_translate("MainWindow", "Moved items to trash.")) + shifted = QtGui.QApplication.queryKeyboardModifiers() & QtCore.Qt.ShiftModifier + while tableWidget.selectedIndexes(): + currentRow = tableWidget.selectedIndexes()[0].row() + inventoryHashToTrash = str(tableWidget.item( + currentRow, 3).data(Qt.UserRole).toPyObject()) + if folder == "trash" or shifted: + sqlExecute('''DELETE FROM inbox WHERE msgid=?''', inventoryHashToTrash) + else: + sqlExecute('''UPDATE inbox SET folder='trash' WHERE msgid=?''', inventoryHashToTrash) + if tableWidget.item(currentRow, 0).unread: + self.propagateUnreadCount(tableWidget.item(currentRow, 1 if tableWidget.item(currentRow, 1).type == AccountMixin.SUBSCRIPTION else 0).data(Qt.UserRole), folder, self.getCurrentTreeWidget(), -1) + if folder != "trash" and not shifted: + self.propagateUnreadCount(tableWidget.item(currentRow, 1 if tableWidget.item(currentRow, 1).type == AccountMixin.SUBSCRIPTION else 0).data(Qt.UserRole), "trash", self.getCurrentTreeWidget(), 1) + self.getCurrentMessageTextedit().setText("") + tableWidget.removeRow(currentRow) + self.statusBar().showMessage(_translate( + "MainWindow", "Moved items to trash.")) + if currentRow == 0: + tableWidget.selectRow(currentRow) + else: + tableWidget.selectRow(currentRow - 1) + def on_action_TrashUndelete(self): tableWidget = self.getCurrentMessagelist() if not tableWidget: return + unread = False currentRow = 0 - tableWidget.setUpdatesEnabled(False) - inventoryHashesToTrash = set() - # 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()) - currentRow = r.topRow() + while tableWidget.selectedIndexes(): + currentRow = tableWidget.selectedIndexes()[0].row() + inventoryHashToTrash = str(tableWidget.item( + currentRow, 3).data(Qt.UserRole).toPyObject()) + sqlExecute('''UPDATE inbox SET folder='inbox' WHERE msgid=?''', inventoryHashToTrash) + if tableWidget.item(currentRow, 0).unread: + self.propagateUnreadCount(tableWidget.item(currentRow, 1 if tableWidget.item(currentRow, 1).type == AccountMixin.SUBSCRIPTION else 0).data(Qt.UserRole), "inbox", self.getCurrentTreeWidget(), 1) + self.propagateUnreadCount(tableWidget.item(currentRow, 1 if tableWidget.item(currentRow, 1).type == AccountMixin.SUBSCRIPTION else 0).data(Qt.UserRole), "trash", self.getCurrentTreeWidget(), -1) self.getCurrentMessageTextedit().setText("") - tableWidget.model().removeRows( - r.topRow(), r.bottomRow() - r.topRow() + 1) - tableWidget.selectRow(0 if currentRow == 0 else currentRow - 1) - idCount = len(inventoryHashesToTrash) - sqlExecuteChunked( - "UPDATE inbox SET folder='inbox' WHERE msgid IN({0})", - idCount, *inventoryHashesToTrash) - tableWidget.selectRow(0 if currentRow == 0 else currentRow - 1) - tableWidget.setUpdatesEnabled(True) - self.propagateUnreadCount() - self.updateStatusBar(_translate("MainWindow", "Undeleted item.")) + tableWidget.removeRow(currentRow) + self.statusBar().showMessage(_translate( + "MainWindow", "Undeleted item.")) + if currentRow == 0: + tableWidget.selectRow(currentRow) + else: + tableWidget.selectRow(currentRow - 1) def on_action_InboxSaveMessageAs(self): tableWidget = self.getCurrentMessagelist() @@ -3265,13 +3072,13 @@ class MyForm(settingsmixin.SMainWindow): return currentInboxRow = tableWidget.currentRow() try: - subjectAtCurrentInboxRow = str(tableWidget.item( - currentInboxRow, 2).data(QtCore.Qt.UserRole)) + subjectAtCurrentInboxRow = str(tableWidget.item(currentInboxRow,2).data(Qt.UserRole)) except: subjectAtCurrentInboxRow = '' # Retrieve the message data out of the SQL database - msgid = tableWidget.item(currentInboxRow, 3).data() + msgid = str(tableWidget.item( + currentInboxRow, 3).data(Qt.UserRole).toPyObject()) queryreturn = sqlQuery( '''select message from inbox where msgid=?''', msgid) if queryreturn != []: @@ -3279,48 +3086,49 @@ 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 = QFileDialog.getSaveFileName(self, _translate("MainWindow","Save As..."), defaultFilename, "Text files (*.txt);;All files (*.*)") if filename == '': return try: f = open(filename, 'w') f.write(message) f.close() - except Exception: + except Exception, e: logger.exception('Message not saved', exc_info=True) - self.updateStatusBar(_translate("MainWindow", "Write error.")) + self.statusBar().showMessage(_translate("MainWindow", "Write error.")) # 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(Qt.UserRole).toPyObject()) + if folder == "trash" or shifted: + sqlExecute('''DELETE FROM sent WHERE ackdata=?''', ackdataToTrash) + else: + sqlExecute('''UPDATE sent SET folder='trash' WHERE ackdata=?''', ackdataToTrash) + if tableWidget.item(currentRow, 0).unread: + self.propagateUnreadCount(tableWidget.item(currentRow, 1 if tableWidget.item(currentRow, 1).type == AccountMixin.SUBSCRIPTION else 0).data(Qt.UserRole), folder, self.getCurrentTreeWidget(), -1) self.getCurrentMessageTextedit().setPlainText("") tableWidget.removeRow(currentRow) - self.updateStatusBar(_translate( + self.statusBar().showMessage(_translate( "MainWindow", "Moved items to trash.")) - - self.ui.tableWidgetInbox.selectRow( - currentRow if currentRow == 0 else currentRow - 1) + if currentRow == 0: + self.ui.tableWidgetInbox.selectRow(currentRow) + else: + self.ui.tableWidgetInbox.selectRow(currentRow - 1) def on_action_ForceSend(self): currentRow = self.ui.tableWidgetInbox.currentRow() addressAtCurrentRow = self.ui.tableWidgetInbox.item( - currentRow, 0).data(QtCore.Qt.UserRole) + currentRow, 0).data(Qt.UserRole) toRipe = decodeAddress(addressAtCurrentRow)[3] sqlExecute( '''UPDATE sent SET status='forcepow' WHERE toripe=? AND status='toodifficult' and folder='sent' ''', @@ -3328,14 +3136,14 @@ class MyForm(settingsmixin.SMainWindow): queryreturn = sqlQuery('''select ackdata FROM sent WHERE status='forcepow' ''') for row in queryreturn: ackdata, = row - queues.UISignalQueue.put(('updateSentItemStatusByAckdata', ( + shared.UISignalQueue.put(('updateSentItemStatusByAckdata', ( ackdata, 'Overriding maximum-difficulty setting. Work queued.'))) - queues.workerQueue.put(('sendmessage', '')) + shared.workerQueue.put(('sendmessage', '')) def on_action_SentClipboard(self): currentRow = self.ui.tableWidgetInbox.currentRow() addressAtCurrentRow = self.ui.tableWidgetInbox.item( - currentRow, 0).data(QtCore.Qt.UserRole) + currentRow, 0).data(Qt.UserRole) clipboard = QtGui.QApplication.clipboard() clipboard.setText(str(addressAtCurrentRow)) @@ -3347,60 +3155,68 @@ 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.statusBar().showMessage(_translate( + "MainWindow", "No addresses selected.")) + else: + self.statusBar().showMessage('') + self.ui.tabWidget.setCurrentIndex(1) 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): - self.updateStatusBar(_translate( - "MainWindow", - "Error: You cannot add the same address to your" - " subscriptions twice. Perhaps rename the existing" - " one if you want.")) + 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.statusBar().showMessage(QtGui.QApplication.translate("MainWindow", "Error: You cannot add the same address to your subscriptions twice. Perhaps rename the existing one if you want.")) continue - self.addSubscription(item.address, item.label) - self.ui.tabWidget.setCurrentIndex( - self.ui.tabWidget.indexOf(self.ui.subscriptions) - ) + labelAtCurrentRow = self.ui.tableWidgetAddressBook.item(currentRow,0).text().toUtf8() + self.addSubscription(addressAtCurrentRow, labelAtCurrentRow) + self.ui.tabWidget.setCurrentIndex(4) def on_context_menuAddressBook(self, point): self.popMenuAddressBook = QtGui.QMenu(self) @@ -3408,44 +3224,27 @@ class MyForm(settingsmixin.SMainWindow): self.popMenuAddressBook.addAction(self.actionAddressBookClipboard) self.popMenuAddressBook.addAction(self.actionAddressBookSubscribe) self.popMenuAddressBook.addAction(self.actionAddressBookSetAvatar) - self.popMenuAddressBook.addAction(self.actionAddressBookSetSound) self.popMenuAddressBook.addSeparator() self.popMenuAddressBook.addAction(self.actionAddressBookNew) normal = True - 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)) # Group of functions for the Subscriptions dialog box def on_action_SubscriptionsNew(self): self.click_pushButtonAddSubscription() - + def on_action_SubscriptionsDelete(self): - if QtGui.QMessageBox.question( - self, "Delete subscription?", - _translate( - "MainWindow", - "If you delete the subscription, messages that you" - " already received will become inaccessible. Maybe" - " you can consider disabling the subscription instead." - " Disabled subscriptions will not receive new" - " messages, but you can still view messages you" - " already received.\n\nAre you sure you want to" - " delete the subscription?" - ), QtGui.QMessageBox.Yes | QtGui.QMessageBox.No - ) != QtGui.QMessageBox.Yes: + if QtGui.QMessageBox.question(self, "Delete subscription?", _translate("MainWindow", "If you delete the subscription, messages that you already received will become inaccessible. Maybe you can consider disabling the subscription instead. Disabled subscriptions will not receive new messages, but you can still view messages you already received.\n\nAre you sure you want to delete the subscription?"), QMessageBox.Yes|QMessageBox.No) != QMessageBox.Yes: return address = self.getCurrentAccount() sqlExecute('''DELETE FROM subscriptions WHERE address=?''', @@ -3482,34 +3281,23 @@ class MyForm(settingsmixin.SMainWindow): def on_context_menuSubscriptions(self, point): currentItem = self.getCurrentItem() - self.popMenuSubscriptions = QtGui.QMenu(self) - if isinstance(currentItem, Ui_AddressWidget): - self.popMenuSubscriptions.addAction(self.actionsubscriptionsNew) - self.popMenuSubscriptions.addAction(self.actionsubscriptionsDelete) - self.popMenuSubscriptions.addSeparator() - if currentItem.isEnabled: - self.popMenuSubscriptions.addAction(self.actionsubscriptionsDisable) - else: - self.popMenuSubscriptions.addAction(self.actionsubscriptionsEnable) - 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(): + if not isinstance(currentItem, Ui_AddressWidget): return + self.popMenuSubscriptions = QtGui.QMenu(self) + self.popMenuSubscriptions.addAction(self.actionsubscriptionsNew) + self.popMenuSubscriptions.addAction(self.actionsubscriptionsDelete) + self.popMenuSubscriptions.addSeparator() + if currentItem.isEnabled: + self.popMenuSubscriptions.addAction(self.actionsubscriptionsDisable) + else: + self.popMenuSubscriptions.addAction(self.actionsubscriptionsEnable) + self.popMenuSubscriptions.addAction(self.actionsubscriptionsSetAvatar) + self.popMenuSubscriptions.addSeparator() + self.popMenuSubscriptions.addAction(self.actionsubscriptionsClipboard) 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: @@ -3526,13 +3314,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: @@ -3550,16 +3338,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: @@ -3576,18 +3366,23 @@ 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(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 = ( + currentIndex = self.ui.tabWidget.currentIndex(); + 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,96 +3395,93 @@ class MyForm(settingsmixin.SMainWindow): except: return self.ui.textEditInboxMessage - def getCurrentSearchLine(self, currentIndex=None, retObj=False): + def getCurrentSearchLine(self, currentIndex = None, retObj = False): if currentIndex is None: - currentIndex = self.ui.tabWidget.currentIndex() - messagelistList = ( + currentIndex = self.ui.tabWidget.currentIndex(); + 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): + def getCurrentSearchOption(self, currentIndex = None): if currentIndex is None: - currentIndex = self.ui.tabWidget.currentIndex() - messagelistList = ( + currentIndex = self.ui.tabWidget.currentIndex(); + 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): + def getCurrentItem(self, treeWidget = None): if treeWidget is None: treeWidget = self.getCurrentTreeWidget() if treeWidget: - return treeWidget.currentItem() - - def getCurrentAccount(self, treeWidget=None): + 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 + def getCurrentFolder(self, treeWidget = None): + 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() def on_action_YourIdentitiesDelete(self): account = self.getCurrentItem() if account.type == AccountMixin.NORMAL: - return # maybe in the future + return # maybe in the future elif account.type == AccountMixin.CHAN: - if QtGui.QMessageBox.question( - self, "Delete channel?", - _translate( - "MainWindow", - "If you delete the channel, messages that you" - " already received will become inaccessible." - " Maybe you can consider disabling the channel" - " instead. Disabled channels will not receive new" - " messages, but you can still view messages you" - " already received.\n\nAre you sure you want to" - " delete the channel?" - ), QtGui.QMessageBox.Yes | QtGui.QMessageBox.No - ) == QtGui.QMessageBox.Yes: - config.remove_section(str(account.address)) + if QtGui.QMessageBox.question(self, "Delete channel?", _translate("MainWindow", "If you delete the channel, messages that you already received will become inaccessible. Maybe you can consider disabling the channel instead. Disabled channels will not receive new messages, but you can still view messages you already received.\n\nAre you sure you want to delete the channel?"), QMessageBox.Yes|QMessageBox.No) == QMessageBox.Yes: + shared.config.remove_section(str(account.address)) else: return else: return - config.save() + shared.writeKeysFile() shared.reloadMyAddressHashes() self.rerenderAddressBook() - self.rerenderComboBoxSendFrom() if account.type == AccountMixin.NORMAL: self.rerenderTabTreeMessages() elif account.type == AccountMixin.CHAN: @@ -3702,8 +3494,8 @@ class MyForm(settingsmixin.SMainWindow): account.setEnabled(True) def enableIdentity(self, address): - config.set(address, 'enabled', 'true') - config.save() + shared.config.set(address, 'enabled', 'true') + shared.writeKeysFile() shared.reloadMyAddressHashes() self.rerenderAddressBook() @@ -3714,8 +3506,8 @@ class MyForm(settingsmixin.SMainWindow): account.setEnabled(False) def disableIdentity(self, address): - config.set(str(address), 'enabled', 'false') - config.save() + shared.config.set(str(address), 'enabled', 'false') + shared.writeKeysFile() shared.reloadMyAddressHashes() self.rerenderAddressBook() @@ -3723,40 +3515,41 @@ class MyForm(settingsmixin.SMainWindow): address = self.getCurrentAccount() clipboard = QtGui.QApplication.clipboard() clipboard.setText(str(address)) - + def on_action_ClipboardMessagelist(self): tableWidget = self.getCurrentMessagelist() currentColumn = tableWidget.currentColumn() 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": - myAddress = tableWidget.item(currentRow, 1).data(QtCore.Qt.UserRole) - otherAddress = tableWidget.item(currentRow, 0).data(QtCore.Qt.UserRole) + 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(Qt.UserRole) + otherAddress = tableWidget.item(currentRow, 0).data(Qt.UserRole) else: - myAddress = tableWidget.item(currentRow, 0).data(QtCore.Qt.UserRole) - otherAddress = tableWidget.item(currentRow, 1).data(QtCore.Qt.UserRole) + myAddress = tableWidget.item(currentRow, 0).data(Qt.UserRole) + otherAddress = tableWidget.item(currentRow, 1).data(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 = tableWidget.item(currentRow, currentColumn).data(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( @@ -3766,74 +3559,48 @@ 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/') + if not os.path.exists(shared.appdata + 'avatars/'): + os.makedirs(shared.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()] - upper = state.appdata + 'avatars/' + hash + '.' + ext.upper() - lower = state.appdata + 'avatars/' + hash + '.' + ext.lower() + filters += [ names[ext] + ' (*.' + ext.lower() + ')' ] + all_images_filter += [ '*.' + ext.lower() ] + upper = shared.appdata + 'avatars/' + hash + '.' + ext.upper() + lower = shared.appdata + 'avatars/' + hash + '.' + ext.lower() if os.path.isfile(lower): current_files += [lower] elif os.path.isfile(upper): current_files += [upper] filters[0:0] = ['Image files (' + ' '.join(all_images_filter) + ')'] filters[1:1] = ['All files (*.*)'] - sourcefile = QtGui.QFileDialog.getOpenFileName( - self, _translate("MainWindow", "Set avatar..."), - filter=';;'.join(filters) - ) + sourcefile = QFileDialog.getOpenFileName(self, _translate("MainWindow","Set avatar..."), filter = ';;'.join(filters)) # determine the correct filename (note that avatars don't use the suffix) - destination = state.appdata + 'avatars/' + hash + '.' + sourcefile.split('.')[-1] + destination = shared.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: @@ -3858,168 +3625,92 @@ class MyForm(settingsmixin.SMainWindow): return False return True - - def on_action_AddressBookSetSound(self): - widget = self.ui.tableWidgetAddressBook - self.setAddressSound(widget.item(widget.currentRow(), 0).text()) - - def setAddressSound(self, addr): - filters = [unicode(_translate( - "MainWindow", "Sound files (%s)" % - ' '.join(['*%s%s' % (os.extsep, ext) for ext in sound.extensions]) - ))] - sourcefile = unicode(QtGui.QFileDialog.getOpenFileName( - self, _translate("MainWindow", "Set notification sound..."), - filter=';;'.join(filters) - )) - - if not sourcefile: - return - - destdir = os.path.join(state.appdata, 'sounds') - destfile = unicode(addr) + os.path.splitext(sourcefile)[-1] - destination = os.path.join(destdir, destfile) - - if sourcefile == destination: - return - - pattern = destfile.lower() - for item in os.listdir(destdir): - if item.lower() == pattern: - overwrite = QtGui.QMessageBox.question( - self, _translate("MainWindow", "Message"), - _translate( - "MainWindow", - "You have already set a notification sound" - " for this address book entry." - " Do you really want to overwrite it?"), - QtGui.QMessageBox.Yes, QtGui.QMessageBox.No - ) == QtGui.QMessageBox.Yes - if overwrite: - QtCore.QFile.remove(os.path.join(destdir, item)) - break - - if not QtCore.QFile.copy(sourcefile, destination): - logger.error( - 'couldn\'t copy %s to %s', sourcefile, destination) - + def on_context_menuYourIdentities(self, point): currentItem = self.getCurrentItem() - self.popMenuYourIdentities = QtGui.QMenu(self) - if isinstance(currentItem, Ui_AddressWidget): - self.popMenuYourIdentities.addAction(self.actionNewYourIdentities) - self.popMenuYourIdentities.addSeparator() - self.popMenuYourIdentities.addAction(self.actionClipboardYourIdentities) - self.popMenuYourIdentities.addSeparator() - if currentItem.isEnabled: - self.popMenuYourIdentities.addAction(self.actionDisableYourIdentities) - else: - self.popMenuYourIdentities.addAction(self.actionEnableYourIdentities) - self.popMenuYourIdentities.addAction(self.actionSetAvatarYourIdentities) - 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(): + if not isinstance(currentItem, Ui_AddressWidget): return + self.popMenuYourIdentities = QtGui.QMenu(self) + self.popMenuYourIdentities.addAction(self.actionNewYourIdentities) + self.popMenuYourIdentities.addSeparator() + self.popMenuYourIdentities.addAction(self.actionClipboardYourIdentities) + self.popMenuYourIdentities.addSeparator() + if currentItem.isEnabled: + self.popMenuYourIdentities.addAction(self.actionDisableYourIdentities) + else: + self.popMenuYourIdentities.addAction(self.actionEnableYourIdentities) + self.popMenuYourIdentities.addAction(self.actionSetAvatarYourIdentities) + self.popMenuYourIdentities.addAction(self.actionSpecialAddressBehaviorYourIdentities) + self.popMenuYourIdentities.addAction(self.actionEmailGateway) self.popMenuYourIdentities.exec_( self.ui.treeWidgetYourIdentities.mapToGlobal(point)) # TODO make one popMenu def on_context_menuChan(self, point): currentItem = self.getCurrentItem() - self.popMenu = QtGui.QMenu(self) - if isinstance(currentItem, Ui_AddressWidget): - self.popMenu.addAction(self.actionNew) - self.popMenu.addAction(self.actionDelete) - 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(): + if not isinstance(currentItem, Ui_AddressWidget): return + self.popMenu = QtGui.QMenu(self) + 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.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(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(Qt.UserRole).toPyObject()) queryreturn = sqlQuery('''SELECT status FROM sent where ackdata=?''', ackData) for row in queryreturn: status, = row @@ -4030,55 +3721,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(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(): @@ -4086,7 +3773,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() @@ -4111,42 +3798,58 @@ class MyForm(settingsmixin.SMainWindow): self.recurDepth -= 1 def tableWidgetInboxItemClicked(self): + folder = self.getCurrentFolder() messageTextedit = self.getCurrentMessageTextedit() if not messageTextedit: return + queryreturn = [] + message = "" - msgid = self.getCurrentMessageId() - folder = self.getCurrentFolder() - if msgid: - queryreturn = sqlQuery( - '''SELECT message FROM %s WHERE %s=?''' % ( - ('sent', 'ackdata') if folder == 'sent' - else ('inbox', 'msgid') - ), msgid - ) - - try: - message = queryreturn[-1][0] - except NameError: - message = "" - except IndexError: - message = _translate( - "MainWindow", - "Error occurred: could not load message from disk." - ) + if folder == 'sent': + ackdata = self.getCurrentMessageId() + if ackdata and messageTextedit: + queryreturn = sqlQuery( + '''select message, 1 from sent where ackdata=?''', ackdata) else: + msgid = self.getCurrentMessageId() + if msgid and messageTextedit: + queryreturn = sqlQuery( + '''select message, read from inbox where msgid=?''', msgid) + + if queryreturn != []: + refresh = False + propagate = False tableWidget = self.getCurrentMessagelist() currentRow = tableWidget.currentRow() - # refresh - if tableWidget.item(currentRow, 0).unread is True: - self.updateUnreadStatus(tableWidget, currentRow, msgid) - # propagate - if folder != 'sent' and sqlExecute( - '''UPDATE inbox SET read=1 WHERE msgid=? AND read=0''', - msgid - ) > 0: - self.propagateUnreadCount() + for row in queryreturn: + message, read = row + if tableWidget.item(currentRow, 0).unread == True: + refresh = True + if folder != 'sent': + markread = sqlExecute( + '''UPDATE inbox SET read = 1 WHERE msgid = ? AND read=0''', msgid) + if markread > 0: + propagate = True + if refresh: + if not tableWidget: + return + font = QFont() + font.setBold(False) +# inventoryHashesToMarkRead = [] +# inventoryHashToMarkRead = str(tableWidget.item( +# currentRow, 3).data(Qt.UserRole).toPyObject()) +# inventoryHashesToMarkRead.append(inventoryHashToMarkRead) + tableWidget.item(currentRow, 0).setUnread(False) + tableWidget.item(currentRow, 1).setUnread(False) + tableWidget.item(currentRow, 2).setUnread(False) + tableWidget.item(currentRow, 3).setFont(font) + if propagate: + self.propagateUnreadCount(tableWidget.item(currentRow, 1 if tableWidget.item(currentRow, 1).type == AccountMixin.SUBSCRIPTION else 0).data(Qt.UserRole), folder, self.getCurrentTreeWidget(), -1) + else: + data = self.getCurrentMessageId() + if data != False: + message = "Error occurred: could not load message from disk." messageTextedit.setCurrentFont(QtGui.QFont()) messageTextedit.setTextColor(QtGui.QColor()) messageTextedit.setContent(message) @@ -4156,17 +3859,6 @@ class MyForm(settingsmixin.SMainWindow): self.rerenderComboBoxSendFrom() self.rerenderMessagelistFromLabels() self.rerenderMessagelistToLabels() - completerList = self.ui.lineEditTo.completer().model().stringList() - for i in range(len(completerList)): - if unicode(completerList[i]).endswith(" <" + item.address + ">"): - completerList[i] = item.label + " <" + item.address + ">" - self.ui.lineEditTo.completer().model().setStringList(completerList) - - def tabWidgetCurrentChanged(self, n): - if n == self.ui.tabWidget.indexOf(self.ui.networkstatus): - self.ui.networkstatus.startUpdate() - else: - self.ui.networkstatus.stopUpdate() def writeNewAddressToTable(self, label, address, streamNumber): self.rerenderTabTreeMessages() @@ -4174,57 +3866,470 @@ class MyForm(settingsmixin.SMainWindow): self.rerenderTabTreeChans() self.rerenderComboBoxSendFrom() self.rerenderComboBoxSendFromBroadcast() - self.rerenderAddressBook() def updateStatusBar(self, data): - try: - message, option = data - except ValueError: - option = 0 - message = data - except TypeError: - logger.debug( - 'Invalid argument for updateStatusBar!', exc_info=True) + if data != "": + logger.info('Status bar: ' + data) - if message != "": - logger.info('Status bar: ' + message) - - if option == 1: - self.statusbar.addImportant(message) - 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() + self.statusBar().showMessage(data) 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 \ - isinstance(obj, settingsmixin.SettingsMixin): + if hasattr(obj, "__class__") and isinstance(obj, settingsmixin.SettingsMixin): loadMethod = getattr(obj, "loadSettings", None) - if callable(loadMethod): + if callable (loadMethod): obj.loadSettings() + + +class helpDialog(QtGui.QDialog): + + def __init__(self, parent): + QtGui.QWidget.__init__(self, parent) + self.ui = Ui_helpDialog() + self.ui.setupUi(self) + self.parent = parent + self.ui.labelHelpURI.setOpenExternalLinks(True) + QtGui.QWidget.resize(self, QtGui.QWidget.sizeHint(self)) + +class connectDialog(QtGui.QDialog): + + def __init__(self, parent): + QtGui.QWidget.__init__(self, parent) + self.ui = Ui_connectDialog() + self.ui.setupUi(self) + self.parent = parent + QtGui.QWidget.resize(self, QtGui.QWidget.sizeHint(self)) + +class aboutDialog(QtGui.QDialog): + + def __init__(self, parent): + QtGui.QWidget.__init__(self, parent) + self.ui = Ui_aboutDialog() + self.ui.setupUi(self) + self.parent = parent + self.ui.labelVersion.setText('version ' + shared.softwareVersion) + + +class regenerateAddressesDialog(QtGui.QDialog): + + def __init__(self, parent): + QtGui.QWidget.__init__(self, parent) + self.ui = Ui_regenerateAddressesDialog() + self.ui.setupUi(self) + self.parent = parent + QtGui.QWidget.resize(self, QtGui.QWidget.sizeHint(self)) + +class settingsDialog(QtGui.QDialog): + + def __init__(self, parent): + QtGui.QWidget.__init__(self, parent) + self.ui = Ui_settingsDialog() + self.ui.setupUi(self) + self.parent = parent + self.ui.checkBoxStartOnLogon.setChecked( + shared.config.getboolean('bitmessagesettings', 'startonlogon')) + self.ui.checkBoxMinimizeToTray.setChecked( + shared.config.getboolean('bitmessagesettings', 'minimizetotray')) + self.ui.checkBoxTrayOnClose.setChecked( + shared.safeConfigGetBoolean('bitmessagesettings', 'trayonclose')) + self.ui.checkBoxShowTrayNotifications.setChecked( + shared.config.getboolean('bitmessagesettings', 'showtraynotifications')) + self.ui.checkBoxStartInTray.setChecked( + shared.config.getboolean('bitmessagesettings', 'startintray')) + self.ui.checkBoxWillinglySendToMobile.setChecked( + shared.safeConfigGetBoolean('bitmessagesettings', 'willinglysendtomobile')) + self.ui.checkBoxUseIdenticons.setChecked( + shared.safeConfigGetBoolean('bitmessagesettings', 'useidenticons')) + self.ui.checkBoxReplyBelow.setChecked( + shared.safeConfigGetBoolean('bitmessagesettings', 'replybelow')) + + if shared.appdata == shared.lookupExeFolder(): + self.ui.checkBoxPortableMode.setChecked(True) + else: + try: + import tempfile + file = tempfile.NamedTemporaryFile(dir=shared.lookupExeFolder(), delete=True) + file.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( + shared.config.get('bitmessagesettings', 'port'))) + self.ui.checkBoxUPnP.setChecked( + shared.safeConfigGetBoolean('bitmessagesettings', 'upnp')) + self.ui.checkBoxAuthentication.setChecked(shared.config.getboolean( + 'bitmessagesettings', 'socksauthentication')) + self.ui.checkBoxSocksListen.setChecked(shared.config.getboolean( + 'bitmessagesettings', 'sockslisten')) + if str(shared.config.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(shared.config.get('bitmessagesettings', 'socksproxytype')) == 'SOCKS4a': + self.ui.comboBoxProxyType.setCurrentIndex(1) + self.ui.lineEditTCPPort.setEnabled(False) + elif str(shared.config.get('bitmessagesettings', 'socksproxytype')) == 'SOCKS5': + self.ui.comboBoxProxyType.setCurrentIndex(2) + self.ui.lineEditTCPPort.setEnabled(False) + + self.ui.lineEditSocksHostname.setText(str( + shared.config.get('bitmessagesettings', 'sockshostname'))) + self.ui.lineEditSocksPort.setText(str( + shared.config.get('bitmessagesettings', 'socksport'))) + self.ui.lineEditSocksUsername.setText(str( + shared.config.get('bitmessagesettings', 'socksusername'))) + self.ui.lineEditSocksPassword.setText(str( + shared.config.get('bitmessagesettings', 'sockspassword'))) + QtCore.QObject.connect(self.ui.comboBoxProxyType, QtCore.SIGNAL( + "currentIndexChanged(int)"), self.comboBoxProxyTypeChanged) + self.ui.lineEditMaxDownloadRate.setText(str( + shared.config.get('bitmessagesettings', 'maxdownloadrate'))) + self.ui.lineEditMaxUploadRate.setText(str( + shared.config.get('bitmessagesettings', 'maxuploadrate'))) + + # Demanded difficulty tab + self.ui.lineEditTotalDifficulty.setText(str((float(shared.config.getint( + 'bitmessagesettings', 'defaultnoncetrialsperbyte')) / shared.networkDefaultProofOfWorkNonceTrialsPerByte))) + self.ui.lineEditSmallMessageDifficulty.setText(str((float(shared.config.getint( + 'bitmessagesettings', 'defaultpayloadlengthextrabytes')) / shared.networkDefaultPayloadLengthExtraBytes))) + + # Max acceptable difficulty tab + self.ui.lineEditMaxAcceptableTotalDifficulty.setText(str((float(shared.config.getint( + 'bitmessagesettings', 'maxacceptablenoncetrialsperbyte')) / shared.networkDefaultProofOfWorkNonceTrialsPerByte))) + self.ui.lineEditMaxAcceptableSmallMessageDifficulty.setText(str((float(shared.config.getint( + 'bitmessagesettings', 'maxacceptablepayloadlengthextrabytes')) / shared.networkDefaultPayloadLengthExtraBytes))) + + # OpenCL + if openclpow.has_opencl(): + self.ui.checkBoxOpenCL.setEnabled(True) + else: + self.ui.checkBoxOpenCL.setEnabled(False) + if shared.safeConfigGetBoolean("bitmessagesettings", "opencl"): + self.ui.checkBoxOpenCL.setChecked(True) + else: + self.ui.checkBoxOpenCL.setChecked(False) + + # Namecoin integration tab + nmctype = shared.config.get('bitmessagesettings', 'namecoinrpctype') + self.ui.lineEditNamecoinHost.setText(str( + shared.config.get('bitmessagesettings', 'namecoinrpchost'))) + self.ui.lineEditNamecoinPort.setText(str( + shared.config.get('bitmessagesettings', 'namecoinrpcport'))) + self.ui.lineEditNamecoinUser.setText(str( + shared.config.get('bitmessagesettings', 'namecoinrpcuser'))) + self.ui.lineEditNamecoinPassword.setText(str( + shared.config.get('bitmessagesettings', 'namecoinrpcpassword'))) + + if nmctype == "namecoind": + self.ui.radioButtonNamecoinNamecoind.setChecked(True) + elif nmctype == "nmcontrol": + self.ui.radioButtonNamecoinNmcontrol.setChecked(True) + self.ui.lineEditNamecoinUser.setEnabled(False) + self.ui.labelNamecoinUser.setEnabled(False) + self.ui.lineEditNamecoinPassword.setEnabled(False) + self.ui.labelNamecoinPassword.setEnabled(False) + else: + assert False + + QtCore.QObject.connect(self.ui.radioButtonNamecoinNamecoind, QtCore.SIGNAL( + "toggled(bool)"), self.namecoinTypeChanged) + QtCore.QObject.connect(self.ui.radioButtonNamecoinNmcontrol, QtCore.SIGNAL( + "toggled(bool)"), self.namecoinTypeChanged) + QtCore.QObject.connect(self.ui.pushButtonNamecoinTest, QtCore.SIGNAL( + "clicked()"), self.click_pushButtonNamecoinTest) + + #Message Resend tab + self.ui.lineEditDays.setText(str( + shared.config.get('bitmessagesettings', 'stopresendingafterxdays'))) + self.ui.lineEditMonths.setText(str( + shared.config.get('bitmessagesettings', 'stopresendingafterxmonths'))) + + + #'System' tab removed for now. + """try: + maxCores = shared.config.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) + self.ui.lineEditTCPPort.setEnabled(True) + 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) + self.ui.lineEditTCPPort.setEnabled(False) + + # Check status of namecoin integration radio buttons and translate + # it to a string as in the options. + def getNamecoinType(self): + if self.ui.radioButtonNamecoinNamecoind.isChecked(): + return "namecoind" + if self.ui.radioButtonNamecoinNmcontrol.isChecked(): + return "nmcontrol" + assert False + + # Namecoin connection type was changed. + def namecoinTypeChanged(self, checked): + nmctype = self.getNamecoinType() + assert nmctype == "namecoind" or nmctype == "nmcontrol" + + isNamecoind = (nmctype == "namecoind") + self.ui.lineEditNamecoinUser.setEnabled(isNamecoind) + self.ui.labelNamecoinUser.setEnabled(isNamecoind) + self.ui.lineEditNamecoinPassword.setEnabled(isNamecoind) + self.ui.labelNamecoinPassword.setEnabled(isNamecoind) + + if isNamecoind: + self.ui.lineEditNamecoinPort.setText(shared.namecoinDefaultRpcPort) + else: + self.ui.lineEditNamecoinPort.setText("9000") + + # Test the namecoin settings specified in the settings dialog. + def click_pushButtonNamecoinTest(self): + self.ui.labelNamecoinTestResult.setText(_translate( + "MainWindow", "Testing...")) + options = {} + options["type"] = self.getNamecoinType() + options["host"] = self.ui.lineEditNamecoinHost.text() + options["port"] = self.ui.lineEditNamecoinPort.text() + options["user"] = self.ui.lineEditNamecoinUser.text() + options["password"] = self.ui.lineEditNamecoinPassword.text() + nc = namecoinConnection(options) + response = nc.test() + responseStatus = response[0] + responseText = response[1] + self.ui.labelNamecoinTestResult.setText(responseText) + if responseStatus== 'success': + self.parent.ui.pushButtonFetchNamecoinID.show() + + +class SpecialAddressBehaviorDialog(QtGui.QDialog): + + def __init__(self, parent): + QtGui.QWidget.__init__(self, parent) + self.ui = Ui_SpecialAddressBehaviorDialog() + self.ui.setupUi(self) + self.parent = parent + addressAtCurrentRow = parent.getCurrentAccount() + if not shared.safeConfigGetBoolean(addressAtCurrentRow, 'chan'): + if shared.safeConfigGetBoolean(addressAtCurrentRow, 'mailinglist'): + self.ui.radioButtonBehaviorMailingList.click() + else: + self.ui.radioButtonBehaveNormalAddress.click() + try: + mailingListName = shared.config.get( + addressAtCurrentRow, 'mailinglistname') + except: + mailingListName = '' + self.ui.lineEditMailingListName.setText( + unicode(mailingListName, 'utf-8')) + else: # if addressAtCurrentRow is a chan address + self.ui.radioButtonBehaviorMailingList.setDisabled(True) + self.ui.lineEditMailingListName.setText(_translate( + "MainWindow", "This is a chan address. You cannot use it as a pseudo-mailing list.")) + + QtGui.QWidget.resize(self, QtGui.QWidget.sizeHint(self)) + +class EmailGatewayDialog(QtGui.QDialog): + + def __init__(self, parent): + QtGui.QWidget.__init__(self, parent) + self.ui = Ui_EmailGatewayDialog() + self.ui.setupUi(self) + self.parent = parent + addressAtCurrentRow = parent.getCurrentAccount() + acct = accountClass(addressAtCurrentRow) + if isinstance(acct, GatewayAccount): + self.ui.radioButtonUnregister.setEnabled(True) + self.ui.radioButtonStatus.setEnabled(True) + self.ui.radioButtonStatus.setChecked(True) + self.ui.radioButtonSettings.setEnabled(True) + else: + self.ui.radioButtonStatus.setEnabled(False) + self.ui.radioButtonSettings.setEnabled(False) + self.ui.radioButtonUnregister.setEnabled(False) + label = shared.config.get(addressAtCurrentRow, 'label') + if label.find("@mailchuck.com") > -1: + self.ui.lineEditEmail.setText(label) + + QtGui.QWidget.resize(self, QtGui.QWidget.sizeHint(self)) + + +class EmailGatewayRegistrationDialog(QtGui.QDialog): + + def __init__(self, parent, title, label): + QtGui.QWidget.__init__(self, parent) + self.ui = Ui_EmailGatewayRegistrationDialog() + self.ui.setupUi(self) + self.parent = parent + self.setWindowTitle(title) + self.ui.label.setText(label) + + QtGui.QWidget.resize(self, QtGui.QWidget.sizeHint(self)) + + +class NewSubscriptionDialog(QtGui.QDialog): + + def __init__(self, parent): + QtGui.QWidget.__init__(self, parent) + self.ui = Ui_NewSubscriptionDialog() + self.ui.setupUi(self) + self.parent = parent + QtCore.QObject.connect(self.ui.lineEditSubscriptionAddress, QtCore.SIGNAL( + "textChanged(QString)"), self.addressChanged) + self.ui.checkBoxDisplayMessagesAlreadyInInventory.setText( + _translate("MainWindow", "Enter an address above.")) + + def addressChanged(self, QString): + self.ui.checkBoxDisplayMessagesAlreadyInInventory.setEnabled(False) + self.ui.checkBoxDisplayMessagesAlreadyInInventory.setChecked(False) + status, addressVersion, streamNumber, ripe = decodeAddress(str(QString)) + if status == 'missingbm': + self.ui.labelAddressCheck.setText(_translate( + "MainWindow", "The address should start with ''BM-''")) + elif status == 'checksumfailed': + self.ui.labelAddressCheck.setText(_translate( + "MainWindow", "The address is not typed or copied correctly (the checksum failed).")) + elif status == 'versiontoohigh': + self.ui.labelAddressCheck.setText(_translate( + "MainWindow", "The version number of this address is higher than this software can support. Please upgrade Bitmessage.")) + elif status == 'invalidcharacters': + self.ui.labelAddressCheck.setText(_translate( + "MainWindow", "The address contains invalid characters.")) + elif status == 'ripetooshort': + self.ui.labelAddressCheck.setText(_translate( + "MainWindow", "Some data encoded in the address is too short.")) + elif status == 'ripetoolong': + self.ui.labelAddressCheck.setText(_translate( + "MainWindow", "Some data encoded in the address is too long.")) + elif status == 'varintmalformed': + self.ui.labelAddressCheck.setText(_translate( + "MainWindow", "Some data encoded in the address is malformed.")) + elif status == 'success': + self.ui.labelAddressCheck.setText( + _translate("MainWindow", "Address is valid.")) + if addressVersion <= 3: + self.ui.checkBoxDisplayMessagesAlreadyInInventory.setText( + _translate("MainWindow", "Address is an old type. We cannot display its past broadcasts.")) + else: + shared.inventory.flush() + doubleHashOfAddressData = hashlib.sha512(hashlib.sha512(encodeVarint( + addressVersion) + encodeVarint(streamNumber) + ripe).digest()).digest() + tag = doubleHashOfAddressData[32:] + count = len(shared.inventory.by_type_and_tag(3, tag)) + if count == 0: + self.ui.checkBoxDisplayMessagesAlreadyInInventory.setText( + _translate("MainWindow", "There are no recent broadcasts from this address to display.")) + else: + self.ui.checkBoxDisplayMessagesAlreadyInInventory.setEnabled(True) + self.ui.checkBoxDisplayMessagesAlreadyInInventory.setText( + _translate("MainWindow", "Display the %1 recent broadcast(s) from this address.").arg(count)) + + +class NewAddressDialog(QtGui.QDialog): + + def __init__(self, parent): + QtGui.QWidget.__init__(self, parent) + self.ui = Ui_NewAddressDialog() + self.ui.setupUi(self) + self.parent = parent + row = 1 + # Let's fill out the 'existing address' combo box with addresses from + # the 'Your Identities' tab. + for addressInKeysFile in getSortedAccounts(): + self.ui.radioButtonExisting.click() + self.ui.comboBoxExisting.addItem( + addressInKeysFile) + row += 1 + self.ui.groupBoxDeterministic.setHidden(True) + QtGui.QWidget.resize(self, QtGui.QWidget.sizeHint(self)) + +class newChanDialog(QtGui.QDialog): + + def __init__(self, parent): + QtGui.QWidget.__init__(self, parent) + self.ui = Ui_newChanDialog() + self.ui.setupUi(self) + self.parent = parent + self.ui.groupBoxCreateChan.setHidden(True) + QtGui.QWidget.resize(self, QtGui.QWidget.sizeHint(self)) + + +class iconGlossaryDialog(QtGui.QDialog): + + def __init__(self, parent): + QtGui.QWidget.__init__(self, parent) + self.ui = Ui_iconGlossaryDialog() + self.ui.setupUi(self) + self.parent = parent + self.ui.labelPortNumber.setText(_translate( + "MainWindow", "You are using TCP port %1. (This can be changed in the settings).").arg(str(shared.config.getint('bitmessagesettings', 'port')))) + QtGui.QWidget.resize(self, QtGui.QWidget.sizeHint(self)) + + +# In order for the time columns on the Inbox and Sent tabs to be sorted +# correctly (rather than alphabetically), we need to overload the < +# operator and use this class instead of QTableWidgetItem. +class myTableWidgetItem(QTableWidgetItem): + + def __lt__(self, other): + return int(self.data(33).toPyObject()) < int(other.data(33).toPyObject()) + +from uisignaler import UISignaler app = None myapp = None - -class BitmessageQtApplication(QtGui.QApplication): +class MySingleApplication(QApplication): """ Listener to allow our Qt form to get focus when another instance of the application is open. @@ -4236,29 +4341,9 @@ class BitmessageQtApplication(QtGui.QApplication): # Unique identifier for this application uuid = '6ec0149b-96e1-4be1-93ab-1465fb3ebf7c' - @staticmethod - def get_windowstyle(): - """Get window style set in config or default""" - return config.safeGet( - 'bitmessagesettings', 'windowstyle', - 'Windows' if is_windows else 'GTK+' - ) - 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") - - self.setStyle(self.get_windowstyle()) - - font = config.safeGet('bitmessagesettings', 'font') - if font: - # family, size, weight = font.split(',') - family, size = font.split(',') - self.setFont(QtGui.QFont(family, int(size))) + super(MySingleApplication, self).__init__(*argv) + id = MySingleApplication.uuid self.server = None self.is_running = False @@ -4277,7 +4362,7 @@ class BitmessageQtApplication(QtGui.QApplication): # Checks if there's an instance of the local server id running if self.is_running: - # This should be ignored, singleinstance.py will take care of exiting me. + # This should be ignored, singleton.py will take care of exiting me. pass else: # Nope, create a local server with this id and assign on_new_connection @@ -4286,44 +4371,42 @@ 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() def on_new_connection(self): + global myapp if myapp: myapp.appIndicatorShow() - 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.appIndicatorInit(app) - - if myapp._firstrun: - myapp.showConnectDialog() # ask the user if we may connect - + myapp.ubuntuMessagingMenuInit() + myapp.notifierInit() + if shared.safeConfigGetBoolean('bitmessagesettings', 'dontconnect'): + myapp.showConnectDialog() # ask the user if we may connect + # try: -# if config.get('bitmessagesettings', 'mailchuck') < 1: -# myapp.showMigrationWizard(config.get('bitmessagesettings', 'mailchuck')) +# if shared.config.get('bitmessagesettings', 'mailchuck') < 1: +# myapp.showMigrationWizard(shared.config.get('bitmessagesettings', 'mailchuck')) # except: # myapp.showMigrationWizard(0) - + # only show after wizards and connect dialogs have completed - if not config.getboolean('bitmessagesettings', 'startintray'): + if not shared.config.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.py b/src/bitmessageqt/about.py new file mode 100644 index 00000000..b778510e --- /dev/null +++ b/src/bitmessageqt/about.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'about.ui' +# +# Created: Tue Jan 21 22:29:38 2014 +# by: PyQt4 UI code generator 4.10.3 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + def _fromUtf8(s): + return s + +try: + _encoding = QtGui.QApplication.UnicodeUTF8 + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig, _encoding) +except AttributeError: + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig) + + +class Ui_aboutDialog(object): + @staticmethod + def __trUtf8(context, text): + return _translate(context, text, None) + + def setupUi(self, aboutDialog): + aboutDialog.setObjectName(_fromUtf8("aboutDialog")) + aboutDialog.resize(360, 315) + self.buttonBox = QtGui.QDialogButtonBox(aboutDialog) + self.buttonBox.setGeometry(QtCore.QRect(20, 280, 311, 32)) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Ok) + self.buttonBox.setObjectName(_fromUtf8("buttonBox")) + self.label = QtGui.QLabel(aboutDialog) + self.label.setGeometry(QtCore.QRect(70, 126, 111, 20)) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.label.setFont(font) + self.label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.label.setObjectName(_fromUtf8("label")) + self.labelVersion = QtGui.QLabel(aboutDialog) + self.labelVersion.setGeometry(QtCore.QRect(190, 126, 161, 20)) + self.labelVersion.setObjectName(_fromUtf8("labelVersion")) + self.label_2 = QtGui.QLabel(aboutDialog) + self.label_2.setGeometry(QtCore.QRect(10, 150, 341, 41)) + self.label_2.setAlignment(QtCore.Qt.AlignCenter) + self.label_2.setObjectName(_fromUtf8("label_2")) + self.label_3 = QtGui.QLabel(aboutDialog) + self.label_3.setGeometry(QtCore.QRect(20, 200, 331, 71)) + self.label_3.setWordWrap(True) + self.label_3.setOpenExternalLinks(True) + self.label_3.setObjectName(_fromUtf8("label_3")) + self.label_5 = QtGui.QLabel(aboutDialog) + self.label_5.setGeometry(QtCore.QRect(10, 190, 341, 20)) + self.label_5.setAlignment(QtCore.Qt.AlignCenter) + self.label_5.setObjectName(_fromUtf8("label_5")) + + self.retranslateUi(aboutDialog) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), aboutDialog.accept) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("rejected()")), aboutDialog.reject) + QtCore.QMetaObject.connectSlotsByName(aboutDialog) + + def retranslateUi(self, aboutDialog): + aboutDialog.setWindowTitle(_translate("aboutDialog", "About", None)) + self.label.setText(_translate("aboutDialog", "PyBitmessage", None)) + self.labelVersion.setText(_translate("aboutDialog", "version ?", None)) + self.label_2.setText(Ui_aboutDialog.__trUtf8("aboutDialog", "

Copyright © 2012-2016 Jonathan Warren
Copyright © 2013-2016 The Bitmessage Developers

")) + self.label_3.setText(_translate("aboutDialog", "

Distributed under the MIT/X11 software license; see http://www.opensource.org/licenses/mit-license.php

", None)) + self.label_5.setText(_translate("aboutDialog", "This is Beta software.", None)) + diff --git a/src/bitmessageqt/about.ui b/src/bitmessageqt/about.ui index 49bd4eca..3deab41b 100644 --- a/src/bitmessageqt/about.ui +++ b/src/bitmessageqt/about.ui @@ -6,91 +6,117 @@ 0 0 - 430 - 340 + 360 + 315 About - - - - - - - - :/newPrefix/images/can-icon-24px.png - - - Qt::AlignCenter - - - - - - - - 75 - true - - - - <html><head/><body><p><a href="https://github.com/Bitmessage/PyBitmessage/tree/:branch:"><span style="text-decoration:none; color:#0000ff;">PyBitmessage :version:</span></a></p></body></html> - - - Qt::AlignCenter - - - - - - - <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2012-2022 The Bitmessage Developers</p></body></html> - - - Qt::AlignLeft - - - - - - - This is Beta software. - - - Qt::AlignCenter - - - - - - - <html><head/><body><p>Distributed under the MIT/X11 software license; see <a href="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> - - - true - - - true - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Ok - - - - + + + + 20 + 280 + 311 + 32 + + + + Qt::Horizontal + + + QDialogButtonBox::Ok + + + + + + 70 + 126 + 111 + 20 + + + + + 75 + true + + + + PyBitmessage + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 190 + 126 + 161 + 20 + + + + version ? + + + + + + 10 + 150 + 341 + 41 + + + + <html><head/><body><p>Copyright © 2012-2014 Jonathan Warren<br/>Copyright © 2013-2014 The Bitmessage Developers</p></body></html> + + + Qt::AlignCenter + + + + + + 20 + 200 + 331 + 71 + + + + <html><head/><body><p>Distributed under the MIT/X11 software license; see <a href="http://www.opensource.org/licenses/mit-license.php"><span style=" text-decoration: underline; color:#0000ff;">http://www.opensource.org/licenses/mit-license.php</span></a></p></body></html> + + + true + + + true + + + + + + 10 + 190 + 341 + 20 + + + + This is Beta software. + + + Qt::AlignCenter + + - - - + buttonBox diff --git a/src/bitmessageqt/account.py b/src/bitmessageqt/account.py index 8c82c6f6..dc6bf6b8 100644 --- a/src/bitmessageqt/account.py +++ b/src/bitmessageqt/account.py @@ -1,39 +1,24 @@ -# pylint: disable=too-many-instance-attributes,attribute-defined-outside-init -""" -account.py -========== +from PyQt4 import QtCore, QtGui -Account related functions. - -""" - -from __future__ import absolute_import - -import inspect +import shared import re import sys +import inspect +from helper_sql import * +from addresses import decodeAddress +from foldertree import AccountMixin +from pyelliptic.openssl import OpenSSL +from utils import str_broadcast_subscribers import time -from PyQt4 import QtGui +def getSortedAccounts(): + configSections = filter(lambda x: x != 'bitmessagesettings', shared.config.sections()) + configSections.sort(cmp = + lambda x,y: cmp(unicode(shared.config.get(x, 'label'), 'utf-8').lower(), unicode(shared.config.get(y, 'label'), 'utf-8').lower()) + ) + return configSections -import queues -from addresses import decodeAddress -from bmconfigparser import config -from helper_ackPayload import genAckPayload -from helper_sql import sqlQuery, sqlExecute -from .foldertree import AccountMixin -from .utils import str_broadcast_subscribers - - -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 +35,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 +43,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 shared.config.has_section(address): + # FIXME: This BROADCAST section makes no sense if address == str_broadcast_subscribers: subscription = BroadcastAccount(address) if subscription.type != AccountMixin.BROADCAST: @@ -70,12 +53,12 @@ def accountClass(address): else: subscription = SubscriptionAccount(address) if subscription.type != AccountMixin.SUBSCRIPTION: - # e.g. deleted chan - return NoAccount(address) + return None return subscription try: - gateway = config.get(address, "gateway") - for _, cls in inspect.getmembers(sys.modules[__name__], inspect.isclass): + gateway = shared.config.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 +67,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 shared.safeConfigGetBoolean(self.address, 'mailinglist'): self.type = AccountMixin.MAILINGLIST - elif config.safeGetBoolean(self.address, 'chan'): + elif shared.safeConfigGetBoolean(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 shared.config.has_section(address): + if shared.safeConfigGetBoolean(self.address, 'chan'): self.type = AccountMixin.CHAN - elif config.safeGetBoolean(self.address, 'mailinglist'): + elif shared.safeConfigGetBoolean(self.address, 'mailinglist'): self.type = AccountMixin.MAILINGLIST elif self.address == str_broadcast_subscribers: self.type = AccountMixin.BROADCAST @@ -127,11 +105,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 shared.config.has_section(address): + label = shared.config.get(address, 'label') queryreturn = sqlQuery( '''select label from addressbook where address=?''', address) if queryreturn != []: @@ -144,61 +123,35 @@ 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): - self.subject = str(subject) - else: - self.subject = subject + self.subject = subject self.message = message self.fromLabel = self.getLabel(fromAddress) self.toLabel = self.getLabel(toAddress) - -class NoAccount(BMAccount): - """Override the __init__ method on a BMAccount""" - - def __init__(self, address=None): # pylint: disable=super-init-not-called - self.address = address - self.type = AccountMixin.NORMAL - - 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) - + super(BMAccount, 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') - ackdata = genAckPayload(streamNumber, stealthLevel) + ackdata = OpenSSL.rand(32) + t = () sqlExecute( '''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''', '', @@ -208,52 +161,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(shared.config.getint('bitmessagesettings', 'ttl'), 86400 * 2) # not necessary to have a TTL higher than 2 days ) - queues.workerQueue.put(('sendmessage', self.toAddress)) - + shared.workerQueue.put(('sendmessage', self.toAddress)) + + def parseMessage(self, toAddress, fromAddress, subject, message): + super(BMAccount, 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) + super(GatewayAccount, 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 +209,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 +216,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 +237,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 +261,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) + super(GatewayAccount, 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 +275,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/addaddressdialog.py b/src/bitmessageqt/addaddressdialog.py new file mode 100644 index 00000000..5ed19e0a --- /dev/null +++ b/src/bitmessageqt/addaddressdialog.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'addaddressdialog.ui' +# +# Created: Sat Nov 30 20:35:38 2013 +# by: PyQt4 UI code generator 4.10.3 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + def _fromUtf8(s): + return s + +try: + _encoding = QtGui.QApplication.UnicodeUTF8 + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig, _encoding) +except AttributeError: + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig) + +class Ui_AddAddressDialog(object): + def setupUi(self, AddAddressDialog): + AddAddressDialog.setObjectName(_fromUtf8("AddAddressDialog")) + AddAddressDialog.resize(368, 162) + self.formLayout = QtGui.QFormLayout(AddAddressDialog) + self.formLayout.setFieldGrowthPolicy(QtGui.QFormLayout.AllNonFixedFieldsGrow) + self.formLayout.setObjectName(_fromUtf8("formLayout")) + self.label_2 = QtGui.QLabel(AddAddressDialog) + self.label_2.setObjectName(_fromUtf8("label_2")) + self.formLayout.setWidget(0, QtGui.QFormLayout.SpanningRole, self.label_2) + self.newAddressLabel = QtGui.QLineEdit(AddAddressDialog) + self.newAddressLabel.setObjectName(_fromUtf8("newAddressLabel")) + self.formLayout.setWidget(2, QtGui.QFormLayout.SpanningRole, self.newAddressLabel) + self.label = QtGui.QLabel(AddAddressDialog) + self.label.setObjectName(_fromUtf8("label")) + self.formLayout.setWidget(4, QtGui.QFormLayout.LabelRole, self.label) + self.lineEditAddress = QtGui.QLineEdit(AddAddressDialog) + self.lineEditAddress.setObjectName(_fromUtf8("lineEditAddress")) + self.formLayout.setWidget(5, QtGui.QFormLayout.SpanningRole, self.lineEditAddress) + self.labelAddressCheck = QtGui.QLabel(AddAddressDialog) + self.labelAddressCheck.setText(_fromUtf8("")) + self.labelAddressCheck.setWordWrap(True) + self.labelAddressCheck.setObjectName(_fromUtf8("labelAddressCheck")) + self.formLayout.setWidget(6, QtGui.QFormLayout.SpanningRole, self.labelAddressCheck) + self.buttonBox = QtGui.QDialogButtonBox(AddAddressDialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok) + self.buttonBox.setObjectName(_fromUtf8("buttonBox")) + self.formLayout.setWidget(7, QtGui.QFormLayout.FieldRole, self.buttonBox) + + self.retranslateUi(AddAddressDialog) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), AddAddressDialog.accept) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("rejected()")), AddAddressDialog.reject) + QtCore.QMetaObject.connectSlotsByName(AddAddressDialog) + + def retranslateUi(self, AddAddressDialog): + AddAddressDialog.setWindowTitle(_translate("AddAddressDialog", "Add new entry", None)) + self.label_2.setText(_translate("AddAddressDialog", "Label", None)) + self.label.setText(_translate("AddAddressDialog", "Address", None)) + diff --git a/src/bitmessageqt/addaddressdialog.ui b/src/bitmessageqt/addaddressdialog.ui index 09701fa4..d7963e0a 100644 --- a/src/bitmessageqt/addaddressdialog.ui +++ b/src/bitmessageqt/addaddressdialog.ui @@ -7,15 +7,9 @@ 0 0 368 - 232 + 162 - - - 368 - 200 - - Add new entry @@ -31,7 +25,7 @@ - + @@ -53,7 +47,7 @@ - + Qt::Horizontal @@ -63,19 +57,6 @@ - - - - Qt::Vertical - - - - 20 - 40 - - - - diff --git a/src/bitmessageqt/address_dialogs.py b/src/bitmessageqt/address_dialogs.py deleted file mode 100644 index bf571041..00000000 --- a/src/bitmessageqt/address_dialogs.py +++ /dev/null @@ -1,373 +0,0 @@ -""" -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 - -import queues -import widgets -import state -from account import AccountMixin, GatewayAccount, MailchuckAccount, accountClass -from addresses import addBMIfNotPresent, decodeAddress, encodeVarint -from bmconfigparser import config as global_config -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) - - 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' - if self.valid: - self.labelAddressCheck.setText( - _translate("MainWindow", "Address is valid.")) - self._onSuccess(addressVersion, streamNumber, ripe) - elif status == 'missingbm': - self.labelAddressCheck.setText(_translate( - "MainWindow", # dialog name should be here - "The address should start with ''BM-''" - )) - elif status == 'checksumfailed': - self.labelAddressCheck.setText(_translate( - "MainWindow", - "The address is not typed or copied correctly" - " (the checksum failed)." - )) - elif status == 'versiontoohigh': - self.labelAddressCheck.setText(_translate( - "MainWindow", - "The version number of this address is higher than this" - " software can support. Please upgrade Bitmessage." - )) - elif status == 'invalidcharacters': - self.labelAddressCheck.setText(_translate( - "MainWindow", - "The address contains invalid characters." - )) - elif status == 'ripetooshort': - self.labelAddressCheck.setText(_translate( - "MainWindow", - "Some data encoded in the address is too short." - )) - elif status == 'ripetoolong': - self.labelAddressCheck.setText(_translate( - "MainWindow", - "Some data encoded in the address is too long." - )) - elif status == 'varintmalformed': - self.labelAddressCheck.setText(_translate( - "MainWindow", - "Some data encoded in the address is malformed." - )) - - -class AddressDataDialog(QtGui.QDialog, AddressCheckMixin): - """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())), - str(self.lineEditLabel.text().toUtf8()) - ) - else: - queues.UISignalQueue.put(('updateStatusBar', _translate( - "MainWindow", - "The address you entered was invalid. Ignoring it." - ))) - super(AddressDataDialog, self).accept() - - -class AddAddressDialog(AddressDataDialog): - """QDialog for adding a new address""" - - def __init__(self, parent=None, address=None): - super(AddAddressDialog, self).__init__(parent) - widgets.load('addaddressdialog.ui', self) - AddressCheckMixin.__init__(self) - if address: - self.lineEditAddress.setText(address) - - -class NewAddressDialog(QtGui.QDialog): - """QDialog for generating a new address""" - - def __init__(self, parent=None): - super(NewAddressDialog, self).__init__(parent) - widgets.load('newaddressdialog.ui', self) - - # Let's fill out the 'existing address' combo box with addresses - # from the 'Your Identities' tab. - for address in global_config.addresses(True): - self.radioButtonExisting.click() - self.comboBoxExisting.addItem(address) - self.groupBoxDeterministic.setHidden(True) - QtGui.QWidget.resize(self, QtGui.QWidget.sizeHint(self)) - self.show() - - def accept(self): - """accept callback""" - self.hide() - # self.buttonBox.enabled = False - if self.radioButtonRandomAddress.isChecked(): - if self.radioButtonMostAvailable.isChecked(): - streamNumberForAddress = 1 - else: - # User selected 'Use the same stream as an existing - # address.' - streamNumberForAddress = decodeAddress( - self.comboBoxExisting.currentText())[2] - queues.addressGeneratorQueue.put(( - 'createRandomAddress', 4, streamNumberForAddress, - str(self.newaddresslabel.text().toUtf8()), 1, "", - self.checkBoxEighteenByteRipe.isChecked() - )) - else: - if self.lineEditPassphrase.text() != \ - self.lineEditPassphraseAgain.text(): - QtGui.QMessageBox.about( - self, _translate("MainWindow", "Passphrase mismatch"), - _translate( - "MainWindow", - "The passphrase you entered twice doesn\'t" - " match. Try again.") - ) - elif self.lineEditPassphrase.text() == "": - QtGui.QMessageBox.about( - self, _translate("MainWindow", "Choose a passphrase"), - _translate( - "MainWindow", "You really do need a passphrase.") - ) - else: - # this will eventually have to be replaced by logic - # to determine the most available stream number. - streamNumberForAddress = 1 - queues.addressGeneratorQueue.put(( - 'createDeterministicAddresses', 4, streamNumberForAddress, - "unused deterministic address", - self.spinBoxNumberOfAddressesToMake.value(), - self.lineEditPassphrase.text().toUtf8(), - self.checkBoxEighteenByteRipe.isChecked() - )) - - -class NewSubscriptionDialog(AddressDataDialog): - """QDialog for subscribing to an address""" - - def __init__(self, parent=None): - super(NewSubscriptionDialog, self).__init__(parent) - widgets.load('newsubscriptiondialog.ui', self) - AddressCheckMixin.__init__(self) - - def _onSuccess(self, addressVersion, streamNumber, ripe): - if addressVersion <= 3: - self.checkBoxDisplayMessagesAlreadyInInventory.setText(_translate( - "MainWindow", - "Address is an old type. We cannot display its past" - " broadcasts." - )) - else: - state.Inventory.flush() - doubleHashOfAddressData = hashlib.sha512(hashlib.sha512( - encodeVarint(addressVersion) - + encodeVarint(streamNumber) + ripe - ).digest()).digest() - tag = doubleHashOfAddressData[32:] - self.recent = state.Inventory.by_type_and_tag(3, tag) - count = len(self.recent) - if count == 0: - self.checkBoxDisplayMessagesAlreadyInInventory.setText( - _translate( - "MainWindow", - "There are no recent broadcasts from this address" - " to display." - )) - else: - self.checkBoxDisplayMessagesAlreadyInInventory.setEnabled(True) - self.checkBoxDisplayMessagesAlreadyInInventory.setText( - _translate( - "MainWindow", - "Display the %n recent broadcast(s) from this address.", - None, - QtCore.QCoreApplication.CodecForTr, - count - )) - - -class RegenerateAddressesDialog(QtGui.QDialog): - """QDialog for regenerating deterministic addresses""" - def __init__(self, parent=None): - super(RegenerateAddressesDialog, self).__init__(parent) - widgets.load('regenerateaddresses.ui', self) - self.groupBox.setTitle('') - QtGui.QWidget.resize(self, QtGui.QWidget.sizeHint(self)) - - -class SpecialAddressBehaviorDialog(QtGui.QDialog): - """ - QDialog for special address behaviour (e.g. mailing list functionality) - """ - - def __init__(self, parent=None, config=global_config): - super(SpecialAddressBehaviorDialog, self).__init__(parent) - widgets.load('specialaddressbehavior.ui', self) - self.address = parent.getCurrentAccount() - self.parent = parent - self.config = config - - try: - self.address_is_chan = config.safeGetBoolean( - self.address, 'chan' - ) - except AttributeError: - pass - else: - if self.address_is_chan: # address is a chan address - self.radioButtonBehaviorMailingList.setDisabled(True) - self.lineEditMailingListName.setText(_translate( - "SpecialAddressBehaviorDialog", - "This is a chan address. You cannot use it as a" - " pseudo-mailing list." - )) - else: - if config.safeGetBoolean(self.address, 'mailinglist'): - self.radioButtonBehaviorMailingList.click() - else: - self.radioButtonBehaveNormalAddress.click() - mailingListName = config.safeGet(self.address, 'mailinglistname', '') - self.lineEditMailingListName.setText( - unicode(mailingListName, 'utf-8') - ) - - QtGui.QWidget.resize(self, QtGui.QWidget.sizeHint(self)) - self.show() - - def accept(self): - """Accept callback""" - self.hide() - if self.address_is_chan: - return - if self.radioButtonBehaveNormalAddress.isChecked(): - self.config.set(str(self.address), 'mailinglist', 'false') - # Set the color to either black or grey - if self.config.getboolean(self.address, 'enabled'): - self.parent.setCurrentItemColor( - QtGui.QApplication.palette().text().color() - ) - else: - self.parent.setCurrentItemColor(QtGui.QColor(128, 128, 128)) - else: - self.config.set(str(self.address), 'mailinglist', 'true') - self.config.set(str(self.address), 'mailinglistname', str( - self.lineEditMailingListName.text().toUtf8())) - self.parent.setCurrentItemColor( - QtGui.QColor(137, 4, 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): - super(EmailGatewayDialog, self).__init__(parent) - widgets.load('emailgateway.ui', self) - self.parent = parent - self.config = config - if account: - self.acct = account - self.setWindowTitle(_translate( - "EmailGatewayDialog", "Registration failed:")) - self.label.setText(_translate( - "EmailGatewayDialog", - "The requested email address is not available," - " please try a new one." - )) - self.radioButtonRegister.hide() - self.radioButtonStatus.hide() - self.radioButtonSettings.hide() - self.radioButtonUnregister.hide() - else: - address = parent.getCurrentAccount() - self.acct = accountClass(address) - try: - label = config.get(address, 'label') - except AttributeError: - pass - else: - if "@" in label: - self.lineEditEmail.setText(label) - if isinstance(self.acct, GatewayAccount): - self.radioButtonUnregister.setEnabled(True) - self.radioButtonStatus.setEnabled(True) - self.radioButtonStatus.setChecked(True) - self.radioButtonSettings.setEnabled(True) - self.lineEditEmail.setEnabled(False) - else: - self.acct = MailchuckAccount(address) - self.lineEditEmail.setFocus() - QtGui.QWidget.resize(self, QtGui.QWidget.sizeHint(self)) - - def accept(self): - """Accept callback""" - self.hide() - # no chans / mailinglists - if self.acct.type != AccountMixin.NORMAL: - return - - if not isinstance(self.acct, GatewayAccount): - return - - if self.radioButtonRegister.isChecked() \ - or self.radioButtonRegister.isHidden(): - email = str(self.lineEditEmail.text().toUtf8()) - self.acct.register(email) - self.config.set(self.acct.fromAddress, 'label', email) - self.config.set(self.acct.fromAddress, 'gateway', 'mailchuck') - self.config.save() - queues.UISignalQueue.put(('updateStatusBar', _translate( - "EmailGatewayDialog", - "Sending email gateway registration request" - ))) - elif self.radioButtonUnregister.isChecked(): - self.acct.unregister() - self.config.remove_option(self.acct.fromAddress, 'gateway') - self.config.save() - queues.UISignalQueue.put(('updateStatusBar', _translate( - "EmailGatewayDialog", - "Sending email gateway unregistration request" - ))) - elif self.radioButtonStatus.isChecked(): - self.acct.status() - queues.UISignalQueue.put(('updateStatusBar', _translate( - "EmailGatewayDialog", - "Sending email gateway status request" - ))) - elif self.radioButtonSettings.isChecked(): - self.data = self.acct - - super(EmailGatewayDialog, self).accept() diff --git a/src/bitmessageqt/addressvalidator.py b/src/bitmessageqt/addressvalidator.py deleted file mode 100644 index dc61b41c..00000000 --- a/src/bitmessageqt/addressvalidator.py +++ /dev/null @@ -1,181 +0,0 @@ -""" -Address validator module. -""" -# pylint: disable=too-many-branches,too-many-arguments - -from Queue import Empty - -from PyQt4 import QtGui - -from addresses import decodeAddress, addBMIfNotPresent -from bmconfigparser import config -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""" - self.addressObject = addressObject - self.passPhraseObject = passPhraseObject - self.feedBackObject = feedBackObject - self.buttonBox = buttonBox - self.addressMandatory = addressMandatory - self.isValid = False - # save default text - self.okButtonLabel = self.buttonBox.button(QtGui.QDialogButtonBox.Ok).text() - - def setError(self, string): - """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) - self.feedBackObject.setFont(font) - self.feedBackObject.setStyleSheet("QLabel { color : red; }") - self.feedBackObject.setText(string) - self.isValid = False - if self.buttonBox: - self.buttonBox.button(QtGui.QDialogButtonBox.Ok).setEnabled(False) - if string is not None and self.feedBackObject is not None: - self.buttonBox.button(QtGui.QDialogButtonBox.Ok).setText( - _translate("AddressValidator", "Invalid")) - else: - self.buttonBox.button(QtGui.QDialogButtonBox.Ok).setText( - _translate("AddressValidator", "Validating...")) - - def setOK(self, string): - """Indicate that the validation succeeded""" - if string is not None and self.feedBackObject is not None: - font = QtGui.QFont() - font.setBold(False) - self.feedBackObject.setFont(font) - self.feedBackObject.setStyleSheet("QLabel { }") - self.feedBackObject.setText(string) - self.isValid = True - if self.buttonBox: - self.buttonBox.button(QtGui.QDialogButtonBox.Ok).setEnabled(True) - self.buttonBox.button(QtGui.QDialogButtonBox.Ok).setText(self.okButtonLabel) - - def checkQueue(self): - """Validator queue loop""" - gotOne = False - - # wait until processing is done - if not addressGeneratorQueue.empty(): - self.setError(None) - return None - - while True: - try: - addressGeneratorReturnValue = apiAddressGeneratorReturnQueue.get(False) - except Empty: - if gotOne: - break - else: - return None - else: - gotOne = True - - if not addressGeneratorReturnValue: - 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.")) - 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 - - def validate(self, s, pos): - """Top level validator method""" - if self.addressObject is None: - address = None - else: - address = str(self.addressObject.text().toUtf8()) - if address == "": - address = None - if self.passPhraseObject is None: - passPhrase = "" - else: - passPhrase = str(self.passPhraseObject.text().toUtf8()) - if passPhrase == "": - passPhrase = None - - # no chan name - if passPhrase is None: - self.setError(_translate("AddressValidator", "Chan name/passphrase needed. You didn't enter a chan name.")) - return (QtGui.QValidator.Intermediate, pos) - - if self.addressMandatory or address is not None: - # check if address already exists: - if address in config.addresses(): - 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.")) - return (QtGui.QValidator.Intermediate, pos) - - # invalid - if decodeAddress(address)[0] != 'success': - self.setError(_translate("AddressValidator", "The Bitmessage address is not valid.")) - return (QtGui.QValidator.Intermediate, pos) - - # this just disables the OK button without changing the feedback text - # but only if triggered by textEdited, not by clicking the Ok button - if not self.buttonBox.button(QtGui.QDialogButtonBox.Ok).hasFocus(): - self.setError(None) - - # check through generator - 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)) - - if self.buttonBox.button(QtGui.QDialogButtonBox.Ok).hasFocus(): - return (self.returnValid(), pos) - 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 bee8fd57..1761dfe3 100644 --- a/src/bitmessageqt/bitmessageui.py +++ b/src/bitmessageqt/bitmessageui.py @@ -8,13 +8,13 @@ # WARNING! All changes made in this file will be lost! from PyQt4 import QtCore, QtGui -from bmconfigparser import config from foldertree import AddressBookCompleter from messageview import MessageView from messagecompose import MessageCompose import settingsmixin from networkstatus import NetworkStatus from blacklist import Blacklist +import shared try: _fromUtf8 = QtCore.QString.fromUtf8 @@ -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) @@ -61,8 +57,7 @@ class Ui_MainWindow(object): self.tabWidget.setMinimumSize(QtCore.QSize(0, 0)) self.tabWidget.setBaseSize(QtCore.QSize(0, 0)) font = QtGui.QFont() - base_size = QtGui.QApplication.instance().font().pointSize() - font.setPointSize(int(base_size * 0.75)) + font.setPointSize(9) self.tabWidget.setFont(font) self.tabWidget.setTabPosition(QtGui.QTabWidget.North) self.tabWidget.setTabShape(QtGui.QTabWidget.Rounded) @@ -80,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) @@ -111,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) @@ -183,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() @@ -207,6 +197,9 @@ class Ui_MainWindow(object): self.verticalSplitter_2.addWidget(self.pushButtonAddAddressBook) self.pushButtonFetchNamecoinID = QtGui.QPushButton(self.send) self.pushButtonFetchNamecoinID.resize(200, self.pushButtonFetchNamecoinID.height()) + font = QtGui.QFont() + font.setPointSize(9) + self.pushButtonFetchNamecoinID.setFont(font) self.pushButtonFetchNamecoinID.setObjectName(_fromUtf8("pushButtonFetchNamecoinID")) self.verticalSplitter_2.addWidget(self.pushButtonFetchNamecoinID) self.verticalSplitter_2.setStretchFactor(0, 1) @@ -332,7 +325,7 @@ class Ui_MainWindow(object): self.pushButtonTTL.setObjectName(_fromUtf8("pushButtonTTL")) self.horizontalLayout_5.addWidget(self.pushButtonTTL, 0, QtCore.Qt.AlignRight) self.horizontalSliderTTL = QtGui.QSlider(self.send) - self.horizontalSliderTTL.setMinimumSize(QtCore.QSize(70, 0)) + self.horizontalSliderTTL.setMinimumSize(QtCore.QSize(35, 0)) self.horizontalSliderTTL.setOrientation(QtCore.Qt.Horizontal) self.horizontalSliderTTL.setInvertedAppearance(False) self.horizontalSliderTTL.setInvertedControls(False) @@ -347,13 +340,10 @@ class Ui_MainWindow(object): self.labelHumanFriendlyTTLDescription.setMinimumSize(QtCore.QSize(45, 0)) self.labelHumanFriendlyTTLDescription.setObjectName(_fromUtf8("labelHumanFriendlyTTLDescription")) self.horizontalLayout_5.addWidget(self.labelHumanFriendlyTTLDescription, 1, QtCore.Qt.AlignLeft) - self.pushButtonClear = QtGui.QPushButton(self.send) - self.pushButtonClear.setObjectName(_fromUtf8("pushButtonClear")) - self.horizontalLayout_5.addWidget(self.pushButtonClear, 0, QtCore.Qt.AlignRight) self.pushButtonSend = QtGui.QPushButton(self.send) self.pushButtonSend.setObjectName(_fromUtf8("pushButtonSend")) self.horizontalLayout_5.addWidget(self.pushButtonSend, 0, QtCore.Qt.AlignRight) - self.horizontalSliderTTL.setMaximumSize(QtCore.QSize(105, self.pushButtonSend.height())) + self.horizontalSliderTTL.setMaximumSize(QtCore.QSize(70, self.pushButtonSend.height())) self.verticalSplitter.addWidget(self.tTLContainer) self.tTLContainer.adjustSize() self.verticalSplitter.setStretchFactor(1, 0) @@ -386,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) @@ -415,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) @@ -467,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")) @@ -489,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) @@ -520,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) @@ -571,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 shared.config.get('bitmessagesettings', 'blackwhitelist') == 'white': self.blackwhitelist.radioButtonWhitelist.click() self.blackwhitelist.rerenderBlackWhiteList() @@ -608,8 +589,6 @@ class Ui_MainWindow(object): icon = QtGui.QIcon.fromTheme(_fromUtf8("dialog-password")) self.actionManageKeys.setIcon(icon) self.actionManageKeys.setObjectName(_fromUtf8("actionManageKeys")) - self.actionNetworkSwitch = QtGui.QAction(MainWindow) - self.actionNetworkSwitch.setObjectName(_fromUtf8("actionNetworkSwitch")) self.actionExit = QtGui.QAction(MainWindow) icon = QtGui.QIcon.fromTheme(_fromUtf8("application-exit")) self.actionExit.setIcon(icon) @@ -645,7 +624,6 @@ class Ui_MainWindow(object): self.menuFile.addAction(self.actionManageKeys) self.menuFile.addAction(self.actionDeleteAllTrashedMessages) self.menuFile.addAction(self.actionRegenerateDeterministicAddresses) - self.menuFile.addAction(self.actionNetworkSwitch) self.menuFile.addAction(self.actionExit) self.menuSettings.addAction(self.actionSettings) self.menuHelp.addAction(self.actionHelp) @@ -656,12 +634,8 @@ class Ui_MainWindow(object): self.menubar.addAction(self.menuHelp.menuAction()) self.retranslateUi(MainWindow) - self.tabWidget.setCurrentIndex( - self.tabWidget.indexOf(self.inbox) - ) - self.tabWidgetSend.setCurrentIndex( - self.tabWidgetSend.indexOf(self.sendDirect) - ) + self.tabWidget.setCurrentIndex(0) + self.tabWidgetSend.setCurrentIndex(0) QtCore.QMetaObject.connectSlotsByName(MainWindow) MainWindow.setTabOrder(self.tableWidgetInbox, self.textEditInboxMessage) MainWindow.setTabOrder(self.textEditInboxMessage, self.comboBoxSendFrom) @@ -670,24 +644,6 @@ 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( - 'bitmessagesettings', 'dontconnect') - self.actionNetworkSwitch.setText( - _translate("MainWindow", "Go online", None) - if dontconnect else - _translate("MainWindow", "Go offline", None) - ) - def retranslateUi(self, MainWindow): MainWindow.setWindowTitle(_translate("MainWindow", "Bitmessage", None)) self.treeWidgetYourIdentities.headerItem().setText(0, _translate("MainWindow", "Identities", None)) @@ -718,34 +674,29 @@ 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(shared.config.getint('bitmessagesettings', 'ttl')/60/60) except: pass - self.labelHumanFriendlyTTLDescription.setText( - _translate("MainWindow", "%n hour(s)", None, QtCore.QCoreApplication.CodecForTr, hours) - ) - self.pushButtonClear.setText(_translate("MainWindow", "Clear", None)) + self.labelHumanFriendlyTTLDescription.setText(_translate("MainWindow", "%n hour(s)", None, QtCore.QCoreApplication.CodecForTr, hours)) self.pushButtonSend.setText(_translate("MainWindow", "Send", None)) self.tabWidget.setTabText(self.tabWidget.indexOf(self.send), _translate("MainWindow", "Send", None)) self.treeWidgetSubscriptions.headerItem().setText(0, _translate("MainWindow", "Subscriptions", None)) 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)) @@ -755,10 +706,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)) @@ -778,15 +726,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)) @@ -799,20 +741,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..1e4be35a 100644 --- a/src/bitmessageqt/blacklist.py +++ b/src/bitmessageqt/blacklist.py @@ -1,15 +1,15 @@ from PyQt4 import QtCore, QtGui - +import shared +from tr import _translate +import l10n +from uisignaler import UISignaler import widgets from addresses import addBMIfNotPresent -from bmconfigparser import config 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 +39,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 shared.config.get('bitmessagesettings', 'blackwhitelist') == 'white': + shared.config.set('bitmessagesettings', 'blackwhitelist', 'black') + shared.writeKeysFile() # 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 shared.config.get('bitmessagesettings', 'blackwhitelist') == 'black': + shared.config.set('bitmessagesettings', 'blackwhitelist', 'white') + shared.writeKeysFile() # self.tableWidgetBlacklist.clearContents() self.tableWidgetBlacklist.setRowCount(0) self.rerenderBlackWhiteList() @@ -57,15 +57,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 shared.config.get('bitmessagesettings', 'blackwhitelist') == 'black': sql = '''select * from blacklist where address=?''' else: sql = '''select * from whitelist where address=?''' @@ -74,7 +73,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 +81,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 shared.config.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 +147,12 @@ class Blacklist(QtGui.QWidget, RetranslateMixin): def rerenderBlackWhiteList(self): tabs = self.parent().parent() - if config.get('bitmessagesettings', 'blackwhitelist') == 'black': + if shared.config.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 = shared.config.get('bitmessagesettings', 'blackwhitelist') if listType == 'black': queryreturn = sqlQuery('''SELECT label, address, enabled FROM blacklist''') else: @@ -195,7 +184,7 @@ class Blacklist(QtGui.QWidget, RetranslateMixin): currentRow, 0).text().toUtf8() addressAtCurrentRow = self.tableWidgetBlacklist.item( currentRow, 1).text() - if config.get('bitmessagesettings', 'blackwhitelist') == 'black': + if shared.config.get('bitmessagesettings', 'blackwhitelist') == 'black': sqlExecute( '''DELETE FROM blacklist WHERE label=? AND address=?''', str(labelAtCurrentRow), str(addressAtCurrentRow)) @@ -224,7 +213,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 shared.config.get('bitmessagesettings', 'blackwhitelist') == 'black': sqlExecute( '''UPDATE blacklist SET enabled=1 WHERE address=?''', str(addressAtCurrentRow)) @@ -241,7 +230,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 shared.config.get('bitmessagesettings', 'blackwhitelist') == 'black': sqlExecute( '''UPDATE blacklist SET enabled=0 WHERE address=?''', str(addressAtCurrentRow)) else: @@ -250,3 +239,4 @@ class Blacklist(QtGui.QWidget, RetranslateMixin): def on_action_BlacklistSetAvatar(self): self.window().on_action_SetAvatar(self.tableWidgetBlacklist) + diff --git a/src/bitmessageqt/connect.py b/src/bitmessageqt/connect.py new file mode 100644 index 00000000..1e224afb --- /dev/null +++ b/src/bitmessageqt/connect.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'connect.ui' +# +# Created: Wed Jul 24 12:42:01 2013 +# by: PyQt4 UI code generator 4.10 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + def _fromUtf8(s): + return s + +try: + _encoding = QtGui.QApplication.UnicodeUTF8 + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig, _encoding) +except AttributeError: + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig) + +class Ui_connectDialog(object): + def setupUi(self, connectDialog): + connectDialog.setObjectName(_fromUtf8("connectDialog")) + connectDialog.resize(400, 124) + self.gridLayout = QtGui.QGridLayout(connectDialog) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.label = QtGui.QLabel(connectDialog) + self.label.setObjectName(_fromUtf8("label")) + self.gridLayout.addWidget(self.label, 0, 0, 1, 2) + self.radioButtonConnectNow = QtGui.QRadioButton(connectDialog) + self.radioButtonConnectNow.setChecked(True) + self.radioButtonConnectNow.setObjectName(_fromUtf8("radioButtonConnectNow")) + self.gridLayout.addWidget(self.radioButtonConnectNow, 1, 0, 1, 2) + self.radioButtonConfigureNetwork = QtGui.QRadioButton(connectDialog) + self.radioButtonConfigureNetwork.setObjectName(_fromUtf8("radioButtonConfigureNetwork")) + self.gridLayout.addWidget(self.radioButtonConfigureNetwork, 2, 0, 1, 2) + spacerItem = QtGui.QSpacerItem(185, 24, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout.addItem(spacerItem, 3, 0, 1, 1) + self.buttonBox = QtGui.QDialogButtonBox(connectDialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Ok) + self.buttonBox.setObjectName(_fromUtf8("buttonBox")) + self.gridLayout.addWidget(self.buttonBox, 3, 1, 1, 1) + + self.retranslateUi(connectDialog) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), connectDialog.accept) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("rejected()")), connectDialog.reject) + QtCore.QMetaObject.connectSlotsByName(connectDialog) + + def retranslateUi(self, connectDialog): + connectDialog.setWindowTitle(_translate("connectDialog", "Bitmessage", None)) + self.label.setText(_translate("connectDialog", "Bitmessage won\'t connect to anyone until you let it. ", None)) + self.radioButtonConnectNow.setText(_translate("connectDialog", "Connect now", None)) + self.radioButtonConfigureNetwork.setText(_translate("connectDialog", "Let me configure special network settings first", None)) + diff --git a/src/bitmessageqt/connect.ui b/src/bitmessageqt/connect.ui index 8b76f5ac..74173860 100644 --- a/src/bitmessageqt/connect.ui +++ b/src/bitmessageqt/connect.ui @@ -38,14 +38,7 @@ - - - - Work offline - - - - + Qt::Horizontal @@ -58,7 +51,7 @@ - + Qt::Horizontal diff --git a/src/bitmessageqt/dialogs.py b/src/bitmessageqt/dialogs.py index dc31e266..d8133eb1 100644 --- a/src/bitmessageqt/dialogs.py +++ b/src/bitmessageqt/dialogs.py @@ -1,85 +1,42 @@ -""" -Custom dialog classes -""" -# pylint: disable=too-few-public-methods -from PyQt4 import QtGui - -import paths -import widgets -from address_dialogs import ( - AddAddressDialog, EmailGatewayDialog, NewAddressDialog, - NewSubscriptionDialog, RegenerateAddressesDialog, - SpecialAddressBehaviorDialog -) -from newchandialog import NewChanDialog -from settings import SettingsDialog +from PyQt4 import QtCore, QtGui +from addaddressdialog import Ui_AddAddressDialog +from addresses import decodeAddress from tr import _translate -from version import softwareVersion -__all__ = [ - "NewChanDialog", "AddAddressDialog", "NewAddressDialog", - "NewSubscriptionDialog", "RegenerateAddressesDialog", - "SpecialAddressBehaviorDialog", "EmailGatewayDialog", - "SettingsDialog" -] +class AddAddressDialog(QtGui.QDialog): + def __init__(self, parent): + QtGui.QWidget.__init__(self, parent) + self.ui = Ui_AddAddressDialog() + self.ui.setupUi(self) + self.parent = parent + QtCore.QObject.connect(self.ui.lineEditAddress, QtCore.SIGNAL( + "textChanged(QString)"), self.addressChanged) -class AboutDialog(QtGui.QDialog): - """The `About` dialog""" - def __init__(self, parent=None): - super(AboutDialog, self).__init__(parent) - widgets.load('about.ui', self) - last_commit = paths.lastCommit() - version = softwareVersion - commit = last_commit.get('commit') - if commit: - version += '-' + commit[:7] - self.labelVersion.setText( - self.labelVersion.text().replace( - ':version:', version - ).replace(':branch:', commit or 'v%s' % version) - ) - self.labelVersion.setOpenExternalLinks(True) - - try: - self.label_2.setText( - self.label_2.text().replace( - '2022', str(last_commit.get('time').year) - )) - except AttributeError: - pass - - self.setFixedSize(QtGui.QWidget.sizeHint(self)) - - -class IconGlossaryDialog(QtGui.QDialog): - """The `Icon Glossary` dialog, explaining the status icon colors""" - 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 - self.groupBox.setTitle('') - - self.labelPortNumber.setText(_translate( - "iconGlossaryDialog", - "You are using TCP port %1. (This can be changed in the settings)." - ).arg(config.getint('bitmessagesettings', 'port'))) - self.setFixedSize(QtGui.QWidget.sizeHint(self)) - - -class HelpDialog(QtGui.QDialog): - """The `Help` dialog""" - 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""" - def __init__(self, parent=None): - super(ConnectDialog, self).__init__(parent) - widgets.load('connect.ui', self) - self.setFixedSize(QtGui.QWidget.sizeHint(self)) + def addressChanged(self, QString): + status, a, b, c = decodeAddress(str(QString)) + if status == 'missingbm': + self.ui.labelAddressCheck.setText(_translate( + "MainWindow", "The address should start with ''BM-''")) + elif status == 'checksumfailed': + self.ui.labelAddressCheck.setText(_translate( + "MainWindow", "The address is not typed or copied correctly (the checksum failed).")) + elif status == 'versiontoohigh': + self.ui.labelAddressCheck.setText(_translate( + "MainWindow", "The version number of this address is higher than this software can support. Please upgrade Bitmessage.")) + elif status == 'invalidcharacters': + self.ui.labelAddressCheck.setText(_translate( + "MainWindow", "The address contains invalid characters.")) + elif status == 'ripetooshort': + self.ui.labelAddressCheck.setText(_translate( + "MainWindow", "Some data encoded in the address is too short.")) + elif status == 'ripetoolong': + self.ui.labelAddressCheck.setText(_translate( + "MainWindow", "Some data encoded in the address is too long.")) + elif status == 'varintmalformed': + self.ui.labelAddressCheck.setText(_translate( + "MainWindow", "Some data encoded in the address is malformed.")) + elif status == 'success': + self.ui.labelAddressCheck.setText( + _translate("MainWindow", "Address is valid.")) diff --git a/src/bitmessageqt/emailgateway.py b/src/bitmessageqt/emailgateway.py new file mode 100644 index 00000000..54ca4529 --- /dev/null +++ b/src/bitmessageqt/emailgateway.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'emailgateway.ui' +# +# Created: Fri Apr 26 17:43:31 2013 +# by: PyQt4 UI code generator 4.9.4 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + _fromUtf8 = lambda s: s + +class Ui_EmailGatewayDialog(object): + def setupUi(self, EmailGatewayDialog): + EmailGatewayDialog.setObjectName(_fromUtf8("EmailGatewayDialog")) + EmailGatewayDialog.resize(386, 172) + self.gridLayout = QtGui.QGridLayout(EmailGatewayDialog) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.radioButtonRegister = QtGui.QRadioButton(EmailGatewayDialog) + self.radioButtonRegister.setChecked(True) + self.radioButtonRegister.setObjectName(_fromUtf8("radioButtonRegister")) + self.gridLayout.addWidget(self.radioButtonRegister, 1, 0, 1, 1) + self.radioButtonStatus = QtGui.QRadioButton(EmailGatewayDialog) + self.radioButtonStatus.setObjectName(_fromUtf8("radioButtonStatus")) + self.gridLayout.addWidget(self.radioButtonStatus, 4, 0, 1, 1) + self.radioButtonSettings = QtGui.QRadioButton(EmailGatewayDialog) + self.radioButtonSettings.setObjectName(_fromUtf8("radioButtonSettings")) + self.gridLayout.addWidget(self.radioButtonSettings, 5, 0, 1, 1) + self.radioButtonUnregister = QtGui.QRadioButton(EmailGatewayDialog) + self.radioButtonUnregister.setObjectName(_fromUtf8("radioButtonUnregister")) + self.gridLayout.addWidget(self.radioButtonUnregister, 6, 0, 1, 1) + self.label = QtGui.QLabel(EmailGatewayDialog) + self.label.setWordWrap(True) + self.label.setObjectName(_fromUtf8("label")) + self.gridLayout.addWidget(self.label, 0, 0, 1, 1) + self.label_2 = QtGui.QLabel(EmailGatewayDialog) + self.label_2.setObjectName(_fromUtf8("label_2")) + self.gridLayout.addWidget(self.label_2, 2, 0, 1, 1) + self.lineEditEmail = QtGui.QLineEdit(EmailGatewayDialog) + self.lineEditEmail.setEnabled(True) + self.lineEditEmail.setObjectName(_fromUtf8("lineEditEmail")) + self.gridLayout.addWidget(self.lineEditEmail, 3, 0, 1, 1) + self.buttonBox = QtGui.QDialogButtonBox(EmailGatewayDialog) + self.buttonBox.setMinimumSize(QtCore.QSize(368, 0)) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok) + self.buttonBox.setObjectName(_fromUtf8("buttonBox")) + self.gridLayout.addWidget(self.buttonBox, 7, 0, 1, 1) + + self.retranslateUi(EmailGatewayDialog) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), EmailGatewayDialog.accept) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("rejected()")), EmailGatewayDialog.reject) + QtCore.QObject.connect(self.radioButtonRegister, QtCore.SIGNAL(_fromUtf8("clicked(bool)")), self.lineEditEmail.setEnabled) + QtCore.QObject.connect(self.radioButtonStatus, QtCore.SIGNAL(_fromUtf8("clicked(bool)")), self.lineEditEmail.setDisabled) + QtCore.QObject.connect(self.radioButtonSettings, QtCore.SIGNAL(_fromUtf8("clicked(bool)")), self.lineEditEmail.setDisabled) + QtCore.QObject.connect(self.radioButtonUnregister, QtCore.SIGNAL(_fromUtf8("clicked(bool)")), self.lineEditEmail.setDisabled) + QtCore.QMetaObject.connectSlotsByName(EmailGatewayDialog) + EmailGatewayDialog.setTabOrder(self.radioButtonRegister, self.lineEditEmail) + EmailGatewayDialog.setTabOrder(self.lineEditEmail, self.radioButtonUnregister) + EmailGatewayDialog.setTabOrder(self.radioButtonUnregister, self.buttonBox) + + def retranslateUi(self, EmailGatewayDialog): + EmailGatewayDialog.setWindowTitle(QtGui.QApplication.translate("EmailGatewayDialog", "Email gateway", None, QtGui.QApplication.UnicodeUTF8)) + self.radioButtonRegister.setText(QtGui.QApplication.translate("EmailGatewayDialog", "Register on email gateway", None, QtGui.QApplication.UnicodeUTF8)) + self.radioButtonStatus.setText(QtGui.QApplication.translate("EmailGatewayDialog", "Account status at email gateway", None, QtGui.QApplication.UnicodeUTF8)) + self.radioButtonSettings.setText(QtGui.QApplication.translate("EmailGatewayDialog", "Change account settings at email gateway", None, QtGui.QApplication.UnicodeUTF8)) + self.radioButtonUnregister.setText(QtGui.QApplication.translate("EmailGatewayDialog", "Unregister from email gateway", None, QtGui.QApplication.UnicodeUTF8)) + self.label.setText(QtGui.QApplication.translate("EmailGatewayDialog", "Email gateway allows you to communicate with email users. Currently, only the Mailchuck email gateway (@mailchuck.com) is available.", None, QtGui.QApplication.UnicodeUTF8)) + self.label_2.setText(QtGui.QApplication.translate("EmailGatewayDialog", "Desired email address (including @mailchuck.com):", None, QtGui.QApplication.UnicodeUTF8)) + + +class Ui_EmailGatewayRegistrationDialog(object): + def setupUi(self, EmailGatewayRegistrationDialog): + EmailGatewayRegistrationDialog.setObjectName(_fromUtf8("EmailGatewayRegistrationDialog")) + EmailGatewayRegistrationDialog.resize(386, 172) + self.gridLayout = QtGui.QGridLayout(EmailGatewayRegistrationDialog) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.label = QtGui.QLabel(EmailGatewayRegistrationDialog) + self.label.setWordWrap(True) + self.label.setObjectName(_fromUtf8("label")) + self.gridLayout.addWidget(self.label, 0, 0, 1, 1) + self.lineEditEmail = QtGui.QLineEdit(EmailGatewayRegistrationDialog) + self.lineEditEmail.setObjectName(_fromUtf8("lineEditEmail")) + self.gridLayout.addWidget(self.lineEditEmail, 1, 0, 1, 1) + self.buttonBox = QtGui.QDialogButtonBox(EmailGatewayRegistrationDialog) + self.buttonBox.setMinimumSize(QtCore.QSize(368, 0)) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok) + self.buttonBox.setObjectName(_fromUtf8("buttonBox")) + self.gridLayout.addWidget(self.buttonBox, 7, 0, 1, 1) + + self.retranslateUi(EmailGatewayRegistrationDialog) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), EmailGatewayRegistrationDialog.accept) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("rejected()")), EmailGatewayRegistrationDialog.reject) + QtCore.QMetaObject.connectSlotsByName(EmailGatewayRegistrationDialog) + + def retranslateUi(self, EmailGatewayRegistrationDialog): + EmailGatewayRegistrationDialog.setWindowTitle(QtGui.QApplication.translate("EmailGatewayRegistrationDialog", "Email gateway registration", None, QtGui.QApplication.UnicodeUTF8)) + self.label.setText(QtGui.QApplication.translate("EmailGatewayRegistrationDialog", "Email gateway allows you to communicate with email users. Currently, only the Mailchuck email gateway (@mailchuck.com) is available.\nPlease type the desired email address (including @mailchuck.com) below:", None, QtGui.QApplication.UnicodeUTF8)) diff --git a/src/bitmessageqt/emailgateway.ui b/src/bitmessageqt/emailgateway.ui index 77a66dec..927df46a 100644 --- a/src/bitmessageqt/emailgateway.ui +++ b/src/bitmessageqt/emailgateway.ui @@ -7,20 +7,20 @@ 0 0 386 - 240 + 172 Email gateway - + true - Desired email address (including @mailchuck.com): + Desired email address (including @mailchuck.com) @@ -50,60 +50,28 @@ - - + + true @mailchuck.com - - 0 - - Email gateway allows you to communicate with email users. Currently, only the Mailchuck email gateway (@mailchuck.com) is available. + Email gateway alows you to communicate with email users. Currently, only the Mailchuck email gateway (@mailchuck.com) is available. true - - - - false - - - Account status at email gateway - - - false - - - - - - - false - - - Change account settings at email gateway - - - false - - - - - false - Unregister from email gateway @@ -116,10 +84,7 @@ radioButtonRegister - lineEditEmail - radioButtonStatus - radioButtonSettings - radioButtonUnregister + lineEditEmailAddress buttonBox @@ -159,7 +124,7 @@ radioButtonRegister clicked(bool) - lineEditEmail + lineEditEmailAddress setEnabled(bool) @@ -175,7 +140,7 @@ radioButtonUnregister clicked(bool) - lineEditEmail + lineEditEmailAddress setDisabled(bool) @@ -188,37 +153,5 @@ - - radioButtonStatus - clicked(bool) - lineEditEmail - setDisabled(bool) - - - 20 - 20 - - - 20 - 20 - - - - - radioButtonSettings - clicked(bool) - lineEditEmail - setDisabled(bool) - - - 20 - 20 - - - 20 - 20 - - - diff --git a/src/bitmessageqt/foldertree.py b/src/bitmessageqt/foldertree.py index c50b7d3d..ed76eac2 100644 --- a/src/bitmessageqt/foldertree.py +++ b/src/bitmessageqt/foldertree.py @@ -1,30 +1,12 @@ -""" -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 helper_sql import * +from utils import * +import shared from settingsmixin import SettingsMixin -from tr import _translate -from utils import avatarize -# for pylupdate -_translate("MainWindow", "inbox") -_translate("MainWindow", "new") -_translate("MainWindow", "sent") -_translate("MainWindow", "trash") - -TimestampRole = QtCore.Qt.UserRole + 1 - - -class AccountMixin(object): - """UI-related functionality for accounts""" +class AccountMixin (object): ALL = 0 NORMAL = 1 CHAN = 2 @@ -32,67 +14,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 +65,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 shared.safeConfigGetBoolean(self.address, 'chan'): self.type = self.CHAN - elif config.safeGetBoolean(self.address, 'mailinglist'): + elif shared.safeConfigGetBoolean(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(shared.config.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: @@ -138,70 +97,46 @@ class AccountMixin(object): retval, = row retval = unicode(retval, 'utf-8') 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') + return unicode(str(QtGui.QApplication.translate("MainWindow", "All accounts")), '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) - - def _getAddressBracket(self, unreadCount=False): - return " (" + str(self.unreadCount) + ")" if unreadCount else "" + parent.insertChild(pos, self) + 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) + return QtGui.QApplication.translate("MainWindow", self.folderName) + (" (" + str(self.unreadCount) + ")" if self.unreadCount > 0 else "") elif role == QtCore.Qt.EditRole: - return self._getLabel() - elif role == QtCore.Qt.ToolTipRole: - return self._getLabel() + self._getAddressBracket(False) + return QtGui.QApplication.translate("MainWindow", self.folderName) + elif role == QtCore.Qt.ToolTipRole: + return QtGui.QApplication.translate("MainWindow", self.folderName) + elif role == QtCore.Qt.DecorationRole: + pass 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,101 +145,117 @@ 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(shared.config.getboolean(self.address, 'enabled')) + self.setAddress(address) + self.setEnabled(enabled) + self.setUnreadCount(unreadCount) + self.setType() + def _getLabel(self): if self.address is None: - return unicode(_translate( - "MainWindow", "All accounts").toUtf8(), 'utf-8', 'ignore') + return unicode(QtGui.QApplication.translate("MainWindow", "All accounts").toUtf8(), 'utf-8', 'ignore') else: try: - return unicode( - config.get(self.address, 'label'), - 'utf-8', 'ignore') + return unicode(shared.config.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): + shared.config.set(str(self.address), 'label', str(value.toString().toUtf8())) + else: + shared.config.set(str(self.address), 'label', str(value)) + shared.writeKeysFile() 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(shared.config.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 +264,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 +282,226 @@ 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(shared.config.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(shared.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 is not None: + if queryreturn != []: + for row in queryreturn: + newLabel, = row + 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 shared.safeConfigGetBoolean('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(shared.config.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 shared.safeConfigGetBoolean('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 = shared.config.get(self.address, 'label') + shared.config.set(self.address, 'label', self.label) 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 +510,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 +526,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/help.py b/src/bitmessageqt/help.py new file mode 100644 index 00000000..ff876514 --- /dev/null +++ b/src/bitmessageqt/help.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'help.ui' +# +# Created: Wed Jan 14 22:42:39 2015 +# by: PyQt4 UI code generator 4.9.4 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + _fromUtf8 = lambda s: s + +class Ui_helpDialog(object): + def setupUi(self, helpDialog): + helpDialog.setObjectName(_fromUtf8("helpDialog")) + helpDialog.resize(335, 96) + self.formLayout = QtGui.QFormLayout(helpDialog) + self.formLayout.setObjectName(_fromUtf8("formLayout")) + self.labelHelpURI = QtGui.QLabel(helpDialog) + self.labelHelpURI.setOpenExternalLinks(True) + self.labelHelpURI.setObjectName(_fromUtf8("labelHelpURI")) + self.formLayout.setWidget(1, QtGui.QFormLayout.LabelRole, self.labelHelpURI) + self.label = QtGui.QLabel(helpDialog) + self.label.setWordWrap(True) + self.label.setObjectName(_fromUtf8("label")) + self.formLayout.setWidget(0, QtGui.QFormLayout.SpanningRole, self.label) + spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.formLayout.setItem(2, QtGui.QFormLayout.LabelRole, spacerItem) + self.buttonBox = QtGui.QDialogButtonBox(helpDialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Ok) + self.buttonBox.setObjectName(_fromUtf8("buttonBox")) + self.formLayout.setWidget(2, QtGui.QFormLayout.FieldRole, self.buttonBox) + + self.retranslateUi(helpDialog) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), helpDialog.accept) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("rejected()")), helpDialog.reject) + QtCore.QMetaObject.connectSlotsByName(helpDialog) + + def retranslateUi(self, helpDialog): + helpDialog.setWindowTitle(QtGui.QApplication.translate("helpDialog", "Help", None, QtGui.QApplication.UnicodeUTF8)) + self.labelHelpURI.setText(QtGui.QApplication.translate("helpDialog", "https://bitmessage.org/wiki/PyBitmessage_Help", None, QtGui.QApplication.UnicodeUTF8)) + self.label.setText(QtGui.QApplication.translate("helpDialog", "As Bitmessage is a collaborative project, help can be found online in the Bitmessage Wiki:", None, QtGui.QApplication.UnicodeUTF8)) + diff --git a/src/bitmessageqt/iconglossary.py b/src/bitmessageqt/iconglossary.py new file mode 100644 index 00000000..32d92db6 --- /dev/null +++ b/src/bitmessageqt/iconglossary.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'iconglossary.ui' +# +# Created: Thu Jun 13 20:15:48 2013 +# by: PyQt4 UI code generator 4.10.1 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + def _fromUtf8(s): + return s + +try: + _encoding = QtGui.QApplication.UnicodeUTF8 + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig, _encoding) +except AttributeError: + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig) + +class Ui_iconGlossaryDialog(object): + def setupUi(self, iconGlossaryDialog): + iconGlossaryDialog.setObjectName(_fromUtf8("iconGlossaryDialog")) + iconGlossaryDialog.resize(424, 282) + self.gridLayout = QtGui.QGridLayout(iconGlossaryDialog) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.groupBox = QtGui.QGroupBox(iconGlossaryDialog) + self.groupBox.setObjectName(_fromUtf8("groupBox")) + self.gridLayout_2 = QtGui.QGridLayout(self.groupBox) + self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2")) + self.label = QtGui.QLabel(self.groupBox) + self.label.setText(_fromUtf8("")) + self.label.setPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/redicon.png"))) + self.label.setObjectName(_fromUtf8("label")) + self.gridLayout_2.addWidget(self.label, 0, 0, 1, 1) + self.label_2 = QtGui.QLabel(self.groupBox) + self.label_2.setObjectName(_fromUtf8("label_2")) + self.gridLayout_2.addWidget(self.label_2, 0, 1, 1, 1) + self.label_3 = QtGui.QLabel(self.groupBox) + self.label_3.setText(_fromUtf8("")) + self.label_3.setPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/yellowicon.png"))) + self.label_3.setObjectName(_fromUtf8("label_3")) + self.gridLayout_2.addWidget(self.label_3, 1, 0, 1, 1) + self.label_4 = QtGui.QLabel(self.groupBox) + self.label_4.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) + self.label_4.setWordWrap(True) + self.label_4.setObjectName(_fromUtf8("label_4")) + self.gridLayout_2.addWidget(self.label_4, 1, 1, 2, 1) + spacerItem = QtGui.QSpacerItem(20, 73, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) + self.gridLayout_2.addItem(spacerItem, 2, 0, 2, 1) + self.labelPortNumber = QtGui.QLabel(self.groupBox) + self.labelPortNumber.setObjectName(_fromUtf8("labelPortNumber")) + self.gridLayout_2.addWidget(self.labelPortNumber, 3, 1, 1, 1) + self.label_5 = QtGui.QLabel(self.groupBox) + self.label_5.setText(_fromUtf8("")) + self.label_5.setPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/greenicon.png"))) + self.label_5.setObjectName(_fromUtf8("label_5")) + self.gridLayout_2.addWidget(self.label_5, 4, 0, 1, 1) + self.label_6 = QtGui.QLabel(self.groupBox) + self.label_6.setWordWrap(True) + self.label_6.setObjectName(_fromUtf8("label_6")) + self.gridLayout_2.addWidget(self.label_6, 4, 1, 1, 1) + self.gridLayout.addWidget(self.groupBox, 0, 0, 1, 1) + self.buttonBox = QtGui.QDialogButtonBox(iconGlossaryDialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Ok) + self.buttonBox.setObjectName(_fromUtf8("buttonBox")) + self.gridLayout.addWidget(self.buttonBox, 1, 0, 1, 1) + + self.retranslateUi(iconGlossaryDialog) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), iconGlossaryDialog.accept) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("rejected()")), iconGlossaryDialog.reject) + QtCore.QMetaObject.connectSlotsByName(iconGlossaryDialog) + + def retranslateUi(self, iconGlossaryDialog): + iconGlossaryDialog.setWindowTitle(_translate("iconGlossaryDialog", "Icon Glossary", None)) + self.groupBox.setTitle(_translate("iconGlossaryDialog", "Icon Glossary", None)) + self.label_2.setText(_translate("iconGlossaryDialog", "You have no connections with other peers. ", None)) + self.label_4.setText(_translate("iconGlossaryDialog", "You have made at least one connection to a peer using an outgoing connection but you have not yet received any incoming connections. Your firewall or home router probably isn\'t configured to forward incoming TCP connections to your computer. Bitmessage will work just fine but it would help the Bitmessage network if you allowed for incoming connections and will help you be a better-connected node.", None)) + self.labelPortNumber.setText(_translate("iconGlossaryDialog", "You are using TCP port ?. (This can be changed in the settings).", None)) + self.label_6.setText(_translate("iconGlossaryDialog", "You do have connections with other peers and your firewall is correctly configured.", None)) + +import bitmessage_icons_rc + +if __name__ == "__main__": + import sys + app = QtGui.QApplication(sys.argv) + iconGlossaryDialog = QtGui.QDialog() + ui = Ui_iconGlossaryDialog() + ui.setupUi(iconGlossaryDialog) + iconGlossaryDialog.show() + sys.exit(app.exec_()) + diff --git a/src/bitmessageqt/iconglossary.ui b/src/bitmessageqt/iconglossary.ui index 1bac94c8..870a90ee 100644 --- a/src/bitmessageqt/iconglossary.ui +++ b/src/bitmessageqt/iconglossary.ui @@ -76,7 +76,7 @@ - You are using TCP port ?. (This can be changed in the settings). + You are using TCP port ?. (This can be changed in the settings). diff --git a/src/bitmessageqt/languagebox.py b/src/bitmessageqt/languagebox.py index 34f96b02..2a8cb865 100644 --- a/src/bitmessageqt/languagebox.py +++ b/src/bitmessageqt/languagebox.py @@ -1,48 +1,36 @@ -"""Language Box Module for Locale Settings""" -# pylint: disable=too-few-public-methods,bad-continuation import glob import os - from PyQt4 import QtCore, QtGui -import paths -from bmconfigparser import config - +from shared import codePath, 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 (codePath(), 'translations') + configuredLocale = "system" + try: + configuredLocale = config.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..c322e421 100644 --- a/src/bitmessageqt/messageview.py +++ b/src/bitmessageqt/messageview.py @@ -1,24 +1,19 @@ -""" -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 - +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): + TEXT_PLAIN = "HTML detected, click here to display" + TEXT_HTML = "Click here to disable HTML" + CONFIRM_TITLE = "Follow external link" + CONFIRM_TEXT = "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?" + + 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) @@ -29,17 +24,10 @@ class MessageView(QtGui.QTextBrowser): self.rendering = False self.defaultFontPointSize = self.currentFont().pointSize() self.verticalScrollBar().valueChanged.connect(self.lazyRender) - 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,112 +36,85 @@ 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))) - - def setWrappingWidth(self, width=None): - """Set word-wrapping width""" - self.setLineWrapMode(QtGui.QTextEdit.FixedPixelWidth) - if width is None: - width = self.width() - self.setLineWrapColumnOrWidth(width) + QtGui.QApplication.activeWindow().statusBar().showMessage(QtGui.QApplication.translate("MainWindow", "Zoom level %1%").arg(str(zoom))) 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()) + QtGui.QApplication.activeWindow().ui.lineEditTo.setText(link.path()) if link.hasQueryItem("subject"): - window.ui.lineEditSubject.setText( - link.queryItemValue("subject")) + QtGui.QApplication.activeWindow().ui.lineEditSubject.setText(link.queryItemValue("subject")) if link.hasQueryItem("body"): - window.ui.textEditMessage.setText( - link.queryItemValue("body")) - window.setSendFromComboBox() - window.ui.tabWidgetSend.setCurrentIndex(0) - window.ui.tabWidget.setCurrentIndex( - window.ui.tabWidget.indexOf(window.ui.send) - ) - window.ui.textEditMessage.setFocus() + QtGui.QApplication.activeWindow().ui.textEditMessage.setText(link.queryItemValue("body")) + QtGui.QApplication.activeWindow().setSendFromComboBox() + QtGui.QApplication.activeWindow().ui.tabWidgetSend.setCurrentIndex(0) + QtGui.QApplication.activeWindow().ui.tabWidget.setCurrentIndex(1) + QtGui.QApplication.activeWindow().ui.textEditMessage.setFocus() 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(type(self).__name__, MessageView.CONFIRM_TITLE), + QtGui.QApplication.translate(type(self).__name__, MessageView.CONFIRM_TEXT).arg(str(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 if self.mode == MessageView.MODE_HTML: pos = self.out.find(">", self.outpos) if pos > self.outpos: - self.outpos = pos + 1 + self.outpos = pos cursor.movePosition(QtGui.QTextCursor.End, QtGui.QTextCursor.MoveAnchor) 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 = "
" + str(QtGui.QApplication.translate(type(self).__name__, MessageView.TEXT_PLAIN)) + "

" + 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 = "
" + str(QtGui.QApplication.translate(type(self).__name__, MessageView.TEXT_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 5d669f39..09d05c63 100644 --- a/src/bitmessageqt/networkstatus.py +++ b/src/bitmessageqt/networkstatus.py @@ -1,38 +1,23 @@ -""" -Network status tab widget definition. -""" - -import time - from PyQt4 import QtCore, QtGui - -import l10n -import network.stats -import state -import widgets -from network import connectionpool, knownnodes -from retranslateui import RetranslateMixin +import time +import shared from tr import _translate +import l10n +from retranslateui import RetranslateMixin from uisignaler import UISignaler +import widgets class NetworkStatus(QtGui.QWidget, RetranslateMixin): - """Network status tab""" def __init__(self, parent=None): super(NetworkStatus, self).__init__(parent) widgets.load('networkstatus.ui', self) - header = self.tableWidgetConnectionCount.horizontalHeader() - header.setResizeMode(QtGui.QHeaderView.ResizeToContents) - - # Somehow this value was 5 when I tested - if header.sortIndicatorSection() > 4: - header.setSortIndicator(0, QtCore.Qt.AscendingOrder) - self.startup = time.localtime() - + self.labelStartupTime.setText(_translate("networkstatus", "Since startup on %1").arg( + l10n.formatTimestamp(self.startup))) + 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( @@ -40,209 +25,114 @@ class NetworkStatus(QtGui.QWidget, RetranslateMixin): QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( "updateNumberOfBroadcastsProcessed()"), self.updateNumberOfBroadcastsProcessed) QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( - "updateNetworkStatusTab(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), self.updateNetworkStatusTab) - + "updateNetworkStatusTab()"), self.updateNetworkStatusTab) + + self.totalNumberOfBytesReceived = 0 + self.totalNumberOfBytesSent = 0 + self.timer = QtCore.QTimer() - + self.timer.start(2000) # milliseconds QtCore.QObject.connect(self.timer, QtCore.SIGNAL("timeout()"), self.runEveryTwoSeconds) - # pylint: enable=no-member - - def startUpdate(self): - """Start a timer to update counters every 2 seconds""" - state.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, sum(shared.numberOfObjectsThatWeHaveYetToGetPerPeer.itervalues()))) 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(shared.numberOfBytesReceived/2), self.formatBytes(self.totalNumberOfBytesReceived))) + self.labelBytesSentCount.setText(_translate( + "networkstatus", "Up: %1/s Total: %2").arg(self.formatByteRate(shared.numberOfBytesSent/2), self.formatBytes(self.totalNumberOfBytesSent))) + self.totalNumberOfBytesReceived += shared.numberOfBytesReceived + self.totalNumberOfBytesSent += shared.numberOfBytesSent + shared.numberOfBytesReceived = 0 + shared.numberOfBytesSent = 0 - 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 = connectionpool.pool.outboundConnections[destination] - except KeyError: - if add: - return - else: - try: - c = connectionpool.pool.inboundConnections[destination] - except KeyError: - try: - c = connectionpool.pool.inboundConnections[destination.host] - except KeyError: - if add: - return - - self.tableWidgetConnectionCount.setUpdatesEnabled(False) - self.tableWidgetConnectionCount.setSortingEnabled(False) - - if add: - self.tableWidgetConnectionCount.insertRow(0) - self.tableWidgetConnectionCount.setItem( - 0, 0, - QtGui.QTableWidgetItem("%s:%i" % (destination.host, destination.port)) - ) - self.tableWidgetConnectionCount.setItem( - 0, 2, - QtGui.QTableWidgetItem("%s" % (c.userAgent)) - ) - self.tableWidgetConnectionCount.setItem( - 0, 3, - QtGui.QTableWidgetItem("%s" % (c.tlsVersion)) - ) - self.tableWidgetConnectionCount.setItem( - 0, 4, - QtGui.QTableWidgetItem("%s" % (",".join(map(str, c.streams)))) - ) - try: - # .. todo:: FIXME: hard coded stream no - rating = "%.1f" % (knownnodes.knownNodes[1][destination]['rating']) - except KeyError: - rating = "-" - self.tableWidgetConnectionCount.setItem( - 0, 1, - QtGui.QTableWidgetItem("%s" % (rating)) - ) - if outbound: - brush = QtGui.QBrush(QtGui.QColor("yellow"), QtCore.Qt.SolidPattern) + def updateNetworkStatusTab(self): + totalNumberOfConnectionsFromAllStreams = 0 # One would think we could use len(sendDataQueues) for this but the number doesn't always match: just because we have a sendDataThread running doesn't mean that the connection has been fully established (with the exchange of version messages). + streamNumberTotals = {} + for host, streamNumber in shared.connectedHostsList.items(): + if not streamNumber in streamNumberTotals: + streamNumberTotals[streamNumber] = 1 else: - brush = QtGui.QBrush(QtGui.QColor("green"), QtCore.Qt.SolidPattern) - for j in range(1): - self.tableWidgetConnectionCount.item(0, j).setBackground(brush) - self.tableWidgetConnectionCount.item(0, 0).setData(QtCore.Qt.UserRole, destination) - self.tableWidgetConnectionCount.item(0, 1).setData(QtCore.Qt.UserRole, outbound) - else: - if not connectionpool.pool.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 + streamNumberTotals[streamNumber] += 1 - 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': + while self.tableWidgetConnectionCount.rowCount() > 0: + self.tableWidgetConnectionCount.removeRow(0) + for streamNumber, connectionCount in streamNumberTotals.items(): + self.tableWidgetConnectionCount.insertRow(0) + if streamNumber == 0: + newItem = QtGui.QTableWidgetItem("?") + else: + newItem = QtGui.QTableWidgetItem(str(streamNumber)) + newItem.setFlags( + QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + self.tableWidgetConnectionCount.setItem(0, 0, newItem) + newItem = QtGui.QTableWidgetItem(str(connectionCount)) + newItem.setFlags( + QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + self.tableWidgetConnectionCount.setItem(0, 1, newItem) + """for currentRow in range(self.tableWidgetConnectionCount.rowCount()): + rowStreamNumber = int(self.tableWidgetConnectionCount.item(currentRow,0).text()) + if streamNumber == rowStreamNumber: + foundTheRowThatNeedsUpdating = True + self.tableWidgetConnectionCount.item(currentRow,1).setText(str(connectionCount)) + #totalNumberOfConnectionsFromAllStreams += connectionCount + if foundTheRowThatNeedsUpdating == False: + #Add a line to the table for this stream number and update its count with the current connection count. + self.tableWidgetConnectionCount.insertRow(0) + newItem = QtGui.QTableWidgetItem(str(streamNumber)) + newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled ) + self.tableWidgetConnectionCount.setItem(0,0,newItem) + newItem = QtGui.QTableWidgetItem(str(connectionCount)) + newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled ) + self.tableWidgetConnectionCount.setItem(0,1,newItem) + totalNumberOfConnectionsFromAllStreams += connectionCount""" + self.labelTotalConnections.setText(_translate( + "networkstatus", "Total Connections: %1").arg(str(len(shared.connectedHostsList)))) + if len(shared.connectedHostsList) > 0 and shared.statusIconColor == 'red': # FYI: The 'singlelistener' thread sets the icon color to green when it receives an incoming connection, meaning that the user's firewall is configured correctly. self.window().setStatusIcon('yellow') - elif self.tableWidgetConnectionCount.rowCount() == 0 and state.statusIconColor != "red": + elif len(shared.connectedHostsList) == 0: 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(state.Inventory.numberOfInventoryLookupsPerformed / 2))) - state.Inventory.numberOfInventoryLookupsPerformed = 0 + self.labelLookupsPerSecond.setText(_translate( + "networkstatus", "Inventory lookups per second: %1").arg(str(shared.numberOfInventoryLookupsPerformed/2))) + shared.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() + super(QtGui.QWidget, self).retranslateUi() + self.labelStartupTime.setText(_translate("networkstatus", "Since startup on %1").arg( + l10n.formatTimestamp(self.startup))) diff --git a/src/bitmessageqt/networkstatus.ui b/src/bitmessageqt/networkstatus.ui index e0c01b57..6ae988bf 100644 --- a/src/bitmessageqt/networkstatus.ui +++ b/src/bitmessageqt/networkstatus.ui @@ -7,7 +7,7 @@ 0 0 602 - 254 + 252 @@ -18,12 +18,12 @@ - + 20 - QLayout::SetNoConstraint + QLayout::SetDefaultConstraint @@ -31,7 +31,7 @@ 20 - QLayout::SetMinimumSize + QLayout::SetFixedSize @@ -85,26 +85,17 @@ QFrame::Plain - - QAbstractItemView::NoEditTriggers - false - false + true QAbstractItemView::NoSelection - - true - - true - - - 80 + false false @@ -115,44 +106,14 @@ false - - - Peer - - - IP address or hostname - - - - - Rating - - - PyBitmessage tracks the success rate of connection attempts to individual nodes. The rating ranges from -1 to 1 and affects the likelihood of selecting the node in the future - - - - - User agent - - - Peer's self-reported software - - - - - TLS - - - Connection encryption - - Stream # - - List of streams negotiated between you and the peer + + + + Connections @@ -164,9 +125,6 @@ 4 - - QLayout::SetNoConstraint - diff --git a/src/bitmessageqt/newaddressdialog.py b/src/bitmessageqt/newaddressdialog.py new file mode 100644 index 00000000..afe6fa2d --- /dev/null +++ b/src/bitmessageqt/newaddressdialog.py @@ -0,0 +1,193 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'newaddressdialog.ui' +# +# Created: Sun Sep 15 23:53:31 2013 +# by: PyQt4 UI code generator 4.10.2 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + def _fromUtf8(s): + return s + +try: + _encoding = QtGui.QApplication.UnicodeUTF8 + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig, _encoding) +except AttributeError: + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig) + +class Ui_NewAddressDialog(object): + def setupUi(self, NewAddressDialog): + NewAddressDialog.setObjectName(_fromUtf8("NewAddressDialog")) + NewAddressDialog.resize(723, 704) + self.formLayout = QtGui.QFormLayout(NewAddressDialog) + self.formLayout.setFieldGrowthPolicy(QtGui.QFormLayout.AllNonFixedFieldsGrow) + self.formLayout.setObjectName(_fromUtf8("formLayout")) + self.label = QtGui.QLabel(NewAddressDialog) + self.label.setAlignment(QtCore.Qt.AlignBottom|QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft) + self.label.setWordWrap(True) + self.label.setObjectName(_fromUtf8("label")) + self.formLayout.setWidget(0, QtGui.QFormLayout.SpanningRole, self.label) + self.label_5 = QtGui.QLabel(NewAddressDialog) + self.label_5.setWordWrap(True) + self.label_5.setObjectName(_fromUtf8("label_5")) + self.formLayout.setWidget(2, QtGui.QFormLayout.SpanningRole, self.label_5) + self.line = QtGui.QFrame(NewAddressDialog) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.line.sizePolicy().hasHeightForWidth()) + self.line.setSizePolicy(sizePolicy) + self.line.setMinimumSize(QtCore.QSize(100, 2)) + self.line.setFrameShape(QtGui.QFrame.HLine) + self.line.setFrameShadow(QtGui.QFrame.Sunken) + self.line.setObjectName(_fromUtf8("line")) + self.formLayout.setWidget(4, QtGui.QFormLayout.SpanningRole, self.line) + self.radioButtonRandomAddress = QtGui.QRadioButton(NewAddressDialog) + self.radioButtonRandomAddress.setChecked(True) + self.radioButtonRandomAddress.setObjectName(_fromUtf8("radioButtonRandomAddress")) + self.buttonGroup = QtGui.QButtonGroup(NewAddressDialog) + self.buttonGroup.setObjectName(_fromUtf8("buttonGroup")) + self.buttonGroup.addButton(self.radioButtonRandomAddress) + self.formLayout.setWidget(5, QtGui.QFormLayout.SpanningRole, self.radioButtonRandomAddress) + self.radioButtonDeterministicAddress = QtGui.QRadioButton(NewAddressDialog) + self.radioButtonDeterministicAddress.setObjectName(_fromUtf8("radioButtonDeterministicAddress")) + self.buttonGroup.addButton(self.radioButtonDeterministicAddress) + self.formLayout.setWidget(6, QtGui.QFormLayout.LabelRole, self.radioButtonDeterministicAddress) + self.checkBoxEighteenByteRipe = QtGui.QCheckBox(NewAddressDialog) + self.checkBoxEighteenByteRipe.setObjectName(_fromUtf8("checkBoxEighteenByteRipe")) + self.formLayout.setWidget(9, QtGui.QFormLayout.SpanningRole, self.checkBoxEighteenByteRipe) + self.groupBoxDeterministic = QtGui.QGroupBox(NewAddressDialog) + self.groupBoxDeterministic.setObjectName(_fromUtf8("groupBoxDeterministic")) + self.gridLayout = QtGui.QGridLayout(self.groupBoxDeterministic) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.label_9 = QtGui.QLabel(self.groupBoxDeterministic) + self.label_9.setObjectName(_fromUtf8("label_9")) + self.gridLayout.addWidget(self.label_9, 6, 0, 1, 1) + self.label_8 = QtGui.QLabel(self.groupBoxDeterministic) + self.label_8.setObjectName(_fromUtf8("label_8")) + self.gridLayout.addWidget(self.label_8, 5, 0, 1, 3) + self.spinBoxNumberOfAddressesToMake = QtGui.QSpinBox(self.groupBoxDeterministic) + self.spinBoxNumberOfAddressesToMake.setMinimum(1) + self.spinBoxNumberOfAddressesToMake.setProperty("value", 8) + self.spinBoxNumberOfAddressesToMake.setObjectName(_fromUtf8("spinBoxNumberOfAddressesToMake")) + self.gridLayout.addWidget(self.spinBoxNumberOfAddressesToMake, 4, 3, 1, 1) + self.label_6 = QtGui.QLabel(self.groupBoxDeterministic) + self.label_6.setObjectName(_fromUtf8("label_6")) + self.gridLayout.addWidget(self.label_6, 0, 0, 1, 1) + self.label_11 = QtGui.QLabel(self.groupBoxDeterministic) + self.label_11.setObjectName(_fromUtf8("label_11")) + self.gridLayout.addWidget(self.label_11, 4, 0, 1, 3) + spacerItem = QtGui.QSpacerItem(73, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout.addItem(spacerItem, 6, 1, 1, 1) + self.label_10 = QtGui.QLabel(self.groupBoxDeterministic) + self.label_10.setObjectName(_fromUtf8("label_10")) + self.gridLayout.addWidget(self.label_10, 6, 2, 1, 1) + spacerItem1 = QtGui.QSpacerItem(42, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout.addItem(spacerItem1, 6, 3, 1, 1) + self.label_7 = QtGui.QLabel(self.groupBoxDeterministic) + self.label_7.setObjectName(_fromUtf8("label_7")) + self.gridLayout.addWidget(self.label_7, 2, 0, 1, 1) + self.lineEditPassphraseAgain = QtGui.QLineEdit(self.groupBoxDeterministic) + self.lineEditPassphraseAgain.setEchoMode(QtGui.QLineEdit.Password) + self.lineEditPassphraseAgain.setObjectName(_fromUtf8("lineEditPassphraseAgain")) + self.gridLayout.addWidget(self.lineEditPassphraseAgain, 3, 0, 1, 4) + self.lineEditPassphrase = QtGui.QLineEdit(self.groupBoxDeterministic) + self.lineEditPassphrase.setInputMethodHints(QtCore.Qt.ImhHiddenText|QtCore.Qt.ImhNoAutoUppercase|QtCore.Qt.ImhNoPredictiveText) + self.lineEditPassphrase.setEchoMode(QtGui.QLineEdit.Password) + self.lineEditPassphrase.setObjectName(_fromUtf8("lineEditPassphrase")) + self.gridLayout.addWidget(self.lineEditPassphrase, 1, 0, 1, 4) + self.formLayout.setWidget(8, QtGui.QFormLayout.LabelRole, self.groupBoxDeterministic) + self.groupBox = QtGui.QGroupBox(NewAddressDialog) + self.groupBox.setObjectName(_fromUtf8("groupBox")) + self.gridLayout_2 = QtGui.QGridLayout(self.groupBox) + self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2")) + self.label_2 = QtGui.QLabel(self.groupBox) + self.label_2.setObjectName(_fromUtf8("label_2")) + self.gridLayout_2.addWidget(self.label_2, 0, 0, 1, 2) + self.newaddresslabel = QtGui.QLineEdit(self.groupBox) + self.newaddresslabel.setObjectName(_fromUtf8("newaddresslabel")) + self.gridLayout_2.addWidget(self.newaddresslabel, 1, 0, 1, 2) + self.radioButtonMostAvailable = QtGui.QRadioButton(self.groupBox) + self.radioButtonMostAvailable.setChecked(True) + self.radioButtonMostAvailable.setObjectName(_fromUtf8("radioButtonMostAvailable")) + self.gridLayout_2.addWidget(self.radioButtonMostAvailable, 2, 0, 1, 2) + self.label_3 = QtGui.QLabel(self.groupBox) + self.label_3.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) + self.label_3.setObjectName(_fromUtf8("label_3")) + self.gridLayout_2.addWidget(self.label_3, 3, 1, 1, 1) + self.radioButtonExisting = QtGui.QRadioButton(self.groupBox) + self.radioButtonExisting.setChecked(False) + self.radioButtonExisting.setObjectName(_fromUtf8("radioButtonExisting")) + self.gridLayout_2.addWidget(self.radioButtonExisting, 4, 0, 1, 2) + self.label_4 = QtGui.QLabel(self.groupBox) + self.label_4.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) + self.label_4.setObjectName(_fromUtf8("label_4")) + self.gridLayout_2.addWidget(self.label_4, 5, 1, 1, 1) + spacerItem2 = QtGui.QSpacerItem(13, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_2.addItem(spacerItem2, 6, 0, 1, 1) + self.comboBoxExisting = QtGui.QComboBox(self.groupBox) + self.comboBoxExisting.setEnabled(False) + self.comboBoxExisting.setEditable(True) + self.comboBoxExisting.setObjectName(_fromUtf8("comboBoxExisting")) + self.gridLayout_2.addWidget(self.comboBoxExisting, 6, 1, 1, 1) + self.formLayout.setWidget(7, QtGui.QFormLayout.LabelRole, self.groupBox) + self.buttonBox = QtGui.QDialogButtonBox(NewAddressDialog) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.MinimumExpanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.buttonBox.sizePolicy().hasHeightForWidth()) + self.buttonBox.setSizePolicy(sizePolicy) + self.buttonBox.setMinimumSize(QtCore.QSize(160, 0)) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok) + self.buttonBox.setObjectName(_fromUtf8("buttonBox")) + self.formLayout.setWidget(10, QtGui.QFormLayout.SpanningRole, self.buttonBox) + + self.retranslateUi(NewAddressDialog) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), NewAddressDialog.accept) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("rejected()")), NewAddressDialog.reject) + QtCore.QObject.connect(self.radioButtonExisting, QtCore.SIGNAL(_fromUtf8("toggled(bool)")), self.comboBoxExisting.setEnabled) + QtCore.QObject.connect(self.radioButtonDeterministicAddress, QtCore.SIGNAL(_fromUtf8("toggled(bool)")), self.groupBoxDeterministic.setShown) + QtCore.QObject.connect(self.radioButtonRandomAddress, QtCore.SIGNAL(_fromUtf8("toggled(bool)")), self.groupBox.setShown) + QtCore.QMetaObject.connectSlotsByName(NewAddressDialog) + NewAddressDialog.setTabOrder(self.radioButtonRandomAddress, self.radioButtonDeterministicAddress) + NewAddressDialog.setTabOrder(self.radioButtonDeterministicAddress, self.newaddresslabel) + NewAddressDialog.setTabOrder(self.newaddresslabel, self.radioButtonMostAvailable) + NewAddressDialog.setTabOrder(self.radioButtonMostAvailable, self.radioButtonExisting) + NewAddressDialog.setTabOrder(self.radioButtonExisting, self.comboBoxExisting) + NewAddressDialog.setTabOrder(self.comboBoxExisting, self.lineEditPassphrase) + NewAddressDialog.setTabOrder(self.lineEditPassphrase, self.lineEditPassphraseAgain) + NewAddressDialog.setTabOrder(self.lineEditPassphraseAgain, self.spinBoxNumberOfAddressesToMake) + NewAddressDialog.setTabOrder(self.spinBoxNumberOfAddressesToMake, self.checkBoxEighteenByteRipe) + NewAddressDialog.setTabOrder(self.checkBoxEighteenByteRipe, self.buttonBox) + + def retranslateUi(self, NewAddressDialog): + NewAddressDialog.setWindowTitle(_translate("NewAddressDialog", "Create new Address", None)) + self.label.setText(_translate("NewAddressDialog", "Here you may generate as many addresses as you like. Indeed, creating and abandoning addresses is encouraged. You may generate addresses by using either random numbers or by using a passphrase. If you use a passphrase, the address is called a \"deterministic\" address.\n" +"The \'Random Number\' option is selected by default but deterministic addresses have several pros and cons:", None)) + self.label_5.setText(_translate("NewAddressDialog", "

Pros:
You can recreate your addresses on any computer from memory.
You need-not worry about backing up your keys.dat file as long as you can remember your passphrase.
Cons:
You must remember (or write down) your passphrase if you expect to be able to recreate your keys if they are lost.
You must remember the address version number and the stream number along with your passphrase.
If you choose a weak passphrase and someone on the Internet can brute-force it, they can read your messages and send messages as you.

", None)) + self.radioButtonRandomAddress.setText(_translate("NewAddressDialog", "Use a random number generator to make an address", None)) + self.radioButtonDeterministicAddress.setText(_translate("NewAddressDialog", "Use a passphrase to make addresses", None)) + self.checkBoxEighteenByteRipe.setText(_translate("NewAddressDialog", "Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter", None)) + self.groupBoxDeterministic.setTitle(_translate("NewAddressDialog", "Make deterministic addresses", None)) + self.label_9.setText(_translate("NewAddressDialog", "Address version number: 4", None)) + self.label_8.setText(_translate("NewAddressDialog", "In addition to your passphrase, you must remember these numbers:", None)) + self.label_6.setText(_translate("NewAddressDialog", "Passphrase", None)) + self.label_11.setText(_translate("NewAddressDialog", "Number of addresses to make based on your passphrase:", None)) + self.label_10.setText(_translate("NewAddressDialog", "Stream number: 1", None)) + self.label_7.setText(_translate("NewAddressDialog", "Retype passphrase", None)) + self.groupBox.setTitle(_translate("NewAddressDialog", "Randomly generate address", None)) + self.label_2.setText(_translate("NewAddressDialog", "Label (not shown to anyone except you)", None)) + self.radioButtonMostAvailable.setText(_translate("NewAddressDialog", "Use the most available stream", None)) + self.label_3.setText(_translate("NewAddressDialog", " (best if this is the first of many addresses you will create)", None)) + self.radioButtonExisting.setText(_translate("NewAddressDialog", "Use the same stream as an existing address", None)) + self.label_4.setText(_translate("NewAddressDialog", "(saves you some bandwidth and processing power)", None)) + diff --git a/src/bitmessageqt/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..e54b18c3 --- /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(): + pass + + def createPassphrase(): + pass + + def broadcastAddress(): + pass + + def registerMailchuck(): + pass + + def waitRegistration(): + 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..65da1f1f 100644 --- a/src/bitmessageqt/newchandialog.py +++ b/src/bitmessageqt/newchandialog.py @@ -1,83 +1,107 @@ -""" -src/bitmessageqt/newchandialog.py -================================= +# -*- coding: utf-8 -*- -""" +# Form implementation generated from reading ui file 'newchandialog.ui' +# +# Created: Wed Aug 7 16:51:29 2013 +# by: PyQt4 UI code generator 4.10 +# +# WARNING! All changes made in this file will be lost! from PyQt4 import QtCore, QtGui -import widgets -from addresses import addBMIfNotPresent -from addressvalidator import AddressValidator, PassPhraseValidator -from queues import ( - addressGeneratorQueue, apiAddressGeneratorReturnQueue, UISignalQueue) -from tr import _translate -from utils import str_chan +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + def _fromUtf8(s): + return s +try: + _encoding = QtGui.QApplication.UnicodeUTF8 + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig, _encoding) +except AttributeError: + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig) -class NewChanDialog(QtGui.QDialog): - """The `New Chan` dialog""" - 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)) +class Ui_newChanDialog(object): + def setupUi(self, newChanDialog): + newChanDialog.setObjectName(_fromUtf8("newChanDialog")) + newChanDialog.resize(553, 422) + newChanDialog.setMinimumSize(QtCore.QSize(0, 0)) + self.formLayout = QtGui.QFormLayout(newChanDialog) + self.formLayout.setObjectName(_fromUtf8("formLayout")) + self.radioButtonCreateChan = QtGui.QRadioButton(newChanDialog) + self.radioButtonCreateChan.setObjectName(_fromUtf8("radioButtonCreateChan")) + self.formLayout.setWidget(0, QtGui.QFormLayout.LabelRole, self.radioButtonCreateChan) + self.radioButtonJoinChan = QtGui.QRadioButton(newChanDialog) + self.radioButtonJoinChan.setChecked(True) + self.radioButtonJoinChan.setObjectName(_fromUtf8("radioButtonJoinChan")) + self.formLayout.setWidget(1, QtGui.QFormLayout.LabelRole, self.radioButtonJoinChan) + self.groupBoxCreateChan = QtGui.QGroupBox(newChanDialog) + self.groupBoxCreateChan.setObjectName(_fromUtf8("groupBoxCreateChan")) + self.gridLayout = QtGui.QGridLayout(self.groupBoxCreateChan) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.label_4 = QtGui.QLabel(self.groupBoxCreateChan) + self.label_4.setWordWrap(True) + self.label_4.setObjectName(_fromUtf8("label_4")) + self.gridLayout.addWidget(self.label_4, 0, 0, 1, 1) + self.label_5 = QtGui.QLabel(self.groupBoxCreateChan) + self.label_5.setObjectName(_fromUtf8("label_5")) + self.gridLayout.addWidget(self.label_5, 1, 0, 1, 1) + self.lineEditChanNameCreate = QtGui.QLineEdit(self.groupBoxCreateChan) + self.lineEditChanNameCreate.setObjectName(_fromUtf8("lineEditChanNameCreate")) + self.gridLayout.addWidget(self.lineEditChanNameCreate, 2, 0, 1, 1) + self.formLayout.setWidget(2, QtGui.QFormLayout.SpanningRole, self.groupBoxCreateChan) + self.groupBoxJoinChan = QtGui.QGroupBox(newChanDialog) + self.groupBoxJoinChan.setObjectName(_fromUtf8("groupBoxJoinChan")) + self.gridLayout_2 = QtGui.QGridLayout(self.groupBoxJoinChan) + self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2")) + self.label = QtGui.QLabel(self.groupBoxJoinChan) + self.label.setWordWrap(True) + self.label.setObjectName(_fromUtf8("label")) + self.gridLayout_2.addWidget(self.label, 0, 0, 1, 1) + self.label_2 = QtGui.QLabel(self.groupBoxJoinChan) + self.label_2.setObjectName(_fromUtf8("label_2")) + self.gridLayout_2.addWidget(self.label_2, 1, 0, 1, 1) + self.lineEditChanNameJoin = QtGui.QLineEdit(self.groupBoxJoinChan) + self.lineEditChanNameJoin.setObjectName(_fromUtf8("lineEditChanNameJoin")) + self.gridLayout_2.addWidget(self.lineEditChanNameJoin, 2, 0, 1, 1) + self.label_3 = QtGui.QLabel(self.groupBoxJoinChan) + self.label_3.setObjectName(_fromUtf8("label_3")) + self.gridLayout_2.addWidget(self.label_3, 3, 0, 1, 1) + self.lineEditChanBitmessageAddress = QtGui.QLineEdit(self.groupBoxJoinChan) + self.lineEditChanBitmessageAddress.setObjectName(_fromUtf8("lineEditChanBitmessageAddress")) + self.gridLayout_2.addWidget(self.lineEditChanBitmessageAddress, 4, 0, 1, 1) + self.formLayout.setWidget(3, QtGui.QFormLayout.SpanningRole, self.groupBoxJoinChan) + spacerItem = QtGui.QSpacerItem(389, 2, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) + self.formLayout.setItem(4, QtGui.QFormLayout.FieldRole, spacerItem) + self.buttonBox = QtGui.QDialogButtonBox(newChanDialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok) + self.buttonBox.setObjectName(_fromUtf8("buttonBox")) + self.formLayout.setWidget(5, QtGui.QFormLayout.FieldRole, self.buttonBox) - self.timer = QtCore.QTimer() - QtCore.QObject.connect( # pylint: disable=no-member - self.timer, QtCore.SIGNAL("timeout()"), self.delayedUpdateStatus) - self.timer.start(500) # milliseconds - self.setAttribute(QtCore.Qt.WA_DeleteOnClose) - self.show() + self.retranslateUi(newChanDialog) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), newChanDialog.accept) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("rejected()")), newChanDialog.reject) + QtCore.QObject.connect(self.radioButtonJoinChan, QtCore.SIGNAL(_fromUtf8("toggled(bool)")), self.groupBoxJoinChan.setShown) + QtCore.QObject.connect(self.radioButtonCreateChan, QtCore.SIGNAL(_fromUtf8("toggled(bool)")), self.groupBoxCreateChan.setShown) + QtCore.QMetaObject.connectSlotsByName(newChanDialog) + newChanDialog.setTabOrder(self.radioButtonJoinChan, self.radioButtonCreateChan) + newChanDialog.setTabOrder(self.radioButtonCreateChan, self.lineEditChanNameCreate) + newChanDialog.setTabOrder(self.lineEditChanNameCreate, self.lineEditChanNameJoin) + newChanDialog.setTabOrder(self.lineEditChanNameJoin, self.lineEditChanBitmessageAddress) + newChanDialog.setTabOrder(self.lineEditChanBitmessageAddress, self.buttonBox) - def delayedUpdateStatus(self): - """Related to updating the UI for the chan passphrase validity""" - self.chanPassPhrase.validator().checkQueue() + def retranslateUi(self, newChanDialog): + newChanDialog.setWindowTitle(_translate("newChanDialog", "Dialog", None)) + self.radioButtonCreateChan.setText(_translate("newChanDialog", "Create a new chan", None)) + self.radioButtonJoinChan.setText(_translate("newChanDialog", "Join a chan", None)) + self.groupBoxCreateChan.setTitle(_translate("newChanDialog", "Create a chan", None)) + self.label_4.setText(_translate("newChanDialog", "

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. If you and someone else both create a chan with the same chan name then it is currently very likely that they will be the same chan.

", None)) + self.label_5.setText(_translate("newChanDialog", "Chan name:", None)) + self.groupBoxJoinChan.setTitle(_translate("newChanDialog", "Join a chan", None)) + self.label.setText(_translate("newChanDialog", "

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 normal person-to-person message to the chan address.

Chans are experimental and completely unmoderatable.

", None)) + self.label_2.setText(_translate("newChanDialog", "Chan name:", None)) + self.label_3.setText(_translate("newChanDialog", "Chan bitmessage address:", None)) - 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)) - else: - 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())))) - self.parent.ui.tabWidget.setCurrentIndex( - self.parent.ui.tabWidget.indexOf(self.parent.ui.chans) - ) - self.done(QtGui.QDialog.Accepted) - else: - UISignalQueue.put(('updateStatusBar', _translate("newchandialog", "Chan creation / joining failed"))) - 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"))) - self.done(QtGui.QDialog.Rejected) diff --git a/src/bitmessageqt/newchandialog.ui b/src/bitmessageqt/newchandialog.ui index 59dbb2bb..2d42b3db 100644 --- a/src/bitmessageqt/newchandialog.ui +++ b/src/bitmessageqt/newchandialog.ui @@ -6,16 +6,10 @@ 0 0 - 473 - 444 + 553 + 422 - - - 0 - 0 - - 0 @@ -23,90 +17,127 @@ - Create or join a chan + Dialog - - - - - - - 0 - 0 - - + - <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> + Create a new chan - + + + + + + Join a chan + + true - - - - - - - - - Chan passphrase/name: - - - - - - - Optional, for advanced usage - - - - - - - - 0 - 0 - - - - Chan address - - - - + + + + Create a chan + + + + + + <html><head/><body><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. If you and someone else both create a chan with the same chan name then it is currently very likely that they will be the same chan.</p></body></html> + + + true + + + + + + + Chan name: + + + + + + + + + + + + + Join a chan + + + + + + <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 normal person-to-person message to the chan address.</p><p>Chans are experimental and completely unmoderatable.</p></body></html> + + + true + + + + + + + Chan name: + + + + + + + + + + Chan bitmessage address: + + + + + + + + + + + Qt::Vertical + + + + 389 + 2 + + + + + + + Qt::Horizontal + QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - false - - - - - - - - 0 - 0 - - - - Please input chan name/passphrase: - - - true - + + radioButtonJoinChan + radioButtonCreateChan + lineEditChanNameCreate + lineEditChanNameJoin + lineEditChanBitmessageAddress + buttonBox + @@ -116,12 +147,12 @@ accept() - 240 - 372 + 428 + 454 - 236 - 221 + 157 + 274 @@ -132,12 +163,44 @@ reject() - 240 - 372 + 430 + 460 - 236 - 221 + 286 + 274 + + + + + radioButtonJoinChan + toggled(bool) + groupBoxJoinChan + setShown(bool) + + + 74 + 49 + + + 96 + 227 + + + + + radioButtonCreateChan + toggled(bool) + groupBoxCreateChan + setShown(bool) + + + 72 + 28 + + + 65 + 92 diff --git a/src/bitmessageqt/newsubscriptiondialog.py b/src/bitmessageqt/newsubscriptiondialog.py new file mode 100644 index 00000000..a63cce4a --- /dev/null +++ b/src/bitmessageqt/newsubscriptiondialog.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'newsubscriptiondialog.ui' +# +# Created: Sat Nov 30 21:53:38 2013 +# by: PyQt4 UI code generator 4.10.3 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + def _fromUtf8(s): + return s + +try: + _encoding = QtGui.QApplication.UnicodeUTF8 + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig, _encoding) +except AttributeError: + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig) + +class Ui_NewSubscriptionDialog(object): + def setupUi(self, NewSubscriptionDialog): + NewSubscriptionDialog.setObjectName(_fromUtf8("NewSubscriptionDialog")) + NewSubscriptionDialog.resize(368, 173) + self.formLayout = QtGui.QFormLayout(NewSubscriptionDialog) + self.formLayout.setObjectName(_fromUtf8("formLayout")) + self.label_2 = QtGui.QLabel(NewSubscriptionDialog) + self.label_2.setObjectName(_fromUtf8("label_2")) + self.formLayout.setWidget(0, QtGui.QFormLayout.LabelRole, self.label_2) + self.newsubscriptionlabel = QtGui.QLineEdit(NewSubscriptionDialog) + self.newsubscriptionlabel.setObjectName(_fromUtf8("newsubscriptionlabel")) + self.formLayout.setWidget(1, QtGui.QFormLayout.SpanningRole, self.newsubscriptionlabel) + self.label = QtGui.QLabel(NewSubscriptionDialog) + self.label.setObjectName(_fromUtf8("label")) + self.formLayout.setWidget(2, QtGui.QFormLayout.LabelRole, self.label) + self.lineEditSubscriptionAddress = QtGui.QLineEdit(NewSubscriptionDialog) + self.lineEditSubscriptionAddress.setObjectName(_fromUtf8("lineEditSubscriptionAddress")) + self.formLayout.setWidget(3, QtGui.QFormLayout.SpanningRole, self.lineEditSubscriptionAddress) + self.labelAddressCheck = QtGui.QLabel(NewSubscriptionDialog) + self.labelAddressCheck.setText(_fromUtf8("")) + self.labelAddressCheck.setWordWrap(True) + self.labelAddressCheck.setObjectName(_fromUtf8("labelAddressCheck")) + self.formLayout.setWidget(4, QtGui.QFormLayout.SpanningRole, self.labelAddressCheck) + self.checkBoxDisplayMessagesAlreadyInInventory = QtGui.QCheckBox(NewSubscriptionDialog) + self.checkBoxDisplayMessagesAlreadyInInventory.setEnabled(False) + self.checkBoxDisplayMessagesAlreadyInInventory.setObjectName(_fromUtf8("checkBoxDisplayMessagesAlreadyInInventory")) + self.formLayout.setWidget(5, QtGui.QFormLayout.SpanningRole, self.checkBoxDisplayMessagesAlreadyInInventory) + self.buttonBox = QtGui.QDialogButtonBox(NewSubscriptionDialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok) + self.buttonBox.setObjectName(_fromUtf8("buttonBox")) + self.formLayout.setWidget(6, QtGui.QFormLayout.FieldRole, self.buttonBox) + + self.retranslateUi(NewSubscriptionDialog) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), NewSubscriptionDialog.accept) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("rejected()")), NewSubscriptionDialog.reject) + QtCore.QMetaObject.connectSlotsByName(NewSubscriptionDialog) + + def retranslateUi(self, NewSubscriptionDialog): + NewSubscriptionDialog.setWindowTitle(_translate("NewSubscriptionDialog", "Add new entry", None)) + self.label_2.setText(_translate("NewSubscriptionDialog", "Label", None)) + self.label.setText(_translate("NewSubscriptionDialog", "Address", None)) + self.checkBoxDisplayMessagesAlreadyInInventory.setText(_translate("NewSubscriptionDialog", "Enter an address above.", None)) + diff --git a/src/bitmessageqt/newsubscriptiondialog.ui b/src/bitmessageqt/newsubscriptiondialog.ui index ec67efa3..ed8615f4 100644 --- a/src/bitmessageqt/newsubscriptiondialog.ui +++ b/src/bitmessageqt/newsubscriptiondialog.ui @@ -7,15 +7,9 @@ 0 0 368 - 254 + 173 - - - 368 - 200 - - Add new entry @@ -28,7 +22,7 @@ - + @@ -38,7 +32,7 @@ - + @@ -50,17 +44,17 @@ - + false - Enter an address above. + CheckBox - + Qt::Horizontal @@ -70,19 +64,6 @@ - - - - Qt::Vertical - - - - 20 - 40 - - - - diff --git a/src/bitmessageqt/regenerateaddresses.py b/src/bitmessageqt/regenerateaddresses.py new file mode 100644 index 00000000..7129b632 --- /dev/null +++ b/src/bitmessageqt/regenerateaddresses.py @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'regenerateaddresses.ui' +# +# Created: Sun Sep 15 23:50:23 2013 +# by: PyQt4 UI code generator 4.10.2 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + def _fromUtf8(s): + return s + +try: + _encoding = QtGui.QApplication.UnicodeUTF8 + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig, _encoding) +except AttributeError: + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig) + +class Ui_regenerateAddressesDialog(object): + def setupUi(self, regenerateAddressesDialog): + regenerateAddressesDialog.setObjectName(_fromUtf8("regenerateAddressesDialog")) + regenerateAddressesDialog.resize(532, 332) + self.gridLayout_2 = QtGui.QGridLayout(regenerateAddressesDialog) + self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2")) + self.buttonBox = QtGui.QDialogButtonBox(regenerateAddressesDialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok) + self.buttonBox.setObjectName(_fromUtf8("buttonBox")) + self.gridLayout_2.addWidget(self.buttonBox, 1, 0, 1, 1) + self.groupBox = QtGui.QGroupBox(regenerateAddressesDialog) + self.groupBox.setObjectName(_fromUtf8("groupBox")) + self.gridLayout = QtGui.QGridLayout(self.groupBox) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.label_6 = QtGui.QLabel(self.groupBox) + self.label_6.setObjectName(_fromUtf8("label_6")) + self.gridLayout.addWidget(self.label_6, 1, 0, 1, 1) + self.lineEditPassphrase = QtGui.QLineEdit(self.groupBox) + self.lineEditPassphrase.setInputMethodHints(QtCore.Qt.ImhHiddenText|QtCore.Qt.ImhNoAutoUppercase|QtCore.Qt.ImhNoPredictiveText) + self.lineEditPassphrase.setEchoMode(QtGui.QLineEdit.Password) + self.lineEditPassphrase.setObjectName(_fromUtf8("lineEditPassphrase")) + self.gridLayout.addWidget(self.lineEditPassphrase, 2, 0, 1, 5) + self.label_11 = QtGui.QLabel(self.groupBox) + self.label_11.setObjectName(_fromUtf8("label_11")) + self.gridLayout.addWidget(self.label_11, 3, 0, 1, 3) + self.spinBoxNumberOfAddressesToMake = QtGui.QSpinBox(self.groupBox) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.spinBoxNumberOfAddressesToMake.sizePolicy().hasHeightForWidth()) + self.spinBoxNumberOfAddressesToMake.setSizePolicy(sizePolicy) + self.spinBoxNumberOfAddressesToMake.setMinimum(1) + self.spinBoxNumberOfAddressesToMake.setProperty("value", 8) + self.spinBoxNumberOfAddressesToMake.setObjectName(_fromUtf8("spinBoxNumberOfAddressesToMake")) + self.gridLayout.addWidget(self.spinBoxNumberOfAddressesToMake, 3, 3, 1, 1) + spacerItem = QtGui.QSpacerItem(132, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout.addItem(spacerItem, 3, 4, 1, 1) + self.label_2 = QtGui.QLabel(self.groupBox) + self.label_2.setObjectName(_fromUtf8("label_2")) + self.gridLayout.addWidget(self.label_2, 4, 0, 1, 1) + self.lineEditAddressVersionNumber = QtGui.QLineEdit(self.groupBox) + self.lineEditAddressVersionNumber.setEnabled(True) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.lineEditAddressVersionNumber.sizePolicy().hasHeightForWidth()) + self.lineEditAddressVersionNumber.setSizePolicy(sizePolicy) + self.lineEditAddressVersionNumber.setMaximumSize(QtCore.QSize(31, 16777215)) + self.lineEditAddressVersionNumber.setText(_fromUtf8("")) + self.lineEditAddressVersionNumber.setObjectName(_fromUtf8("lineEditAddressVersionNumber")) + self.gridLayout.addWidget(self.lineEditAddressVersionNumber, 4, 1, 1, 1) + spacerItem1 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout.addItem(spacerItem1, 4, 2, 1, 1) + self.label_3 = QtGui.QLabel(self.groupBox) + self.label_3.setObjectName(_fromUtf8("label_3")) + self.gridLayout.addWidget(self.label_3, 5, 0, 1, 1) + self.lineEditStreamNumber = QtGui.QLineEdit(self.groupBox) + self.lineEditStreamNumber.setEnabled(False) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Ignored, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.lineEditStreamNumber.sizePolicy().hasHeightForWidth()) + self.lineEditStreamNumber.setSizePolicy(sizePolicy) + self.lineEditStreamNumber.setMaximumSize(QtCore.QSize(31, 16777215)) + self.lineEditStreamNumber.setObjectName(_fromUtf8("lineEditStreamNumber")) + self.gridLayout.addWidget(self.lineEditStreamNumber, 5, 1, 1, 1) + spacerItem2 = QtGui.QSpacerItem(325, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout.addItem(spacerItem2, 5, 2, 1, 3) + self.checkBoxEighteenByteRipe = QtGui.QCheckBox(self.groupBox) + self.checkBoxEighteenByteRipe.setObjectName(_fromUtf8("checkBoxEighteenByteRipe")) + self.gridLayout.addWidget(self.checkBoxEighteenByteRipe, 6, 0, 1, 5) + self.label_4 = QtGui.QLabel(self.groupBox) + self.label_4.setWordWrap(True) + self.label_4.setObjectName(_fromUtf8("label_4")) + self.gridLayout.addWidget(self.label_4, 7, 0, 1, 5) + self.label = QtGui.QLabel(self.groupBox) + self.label.setWordWrap(True) + self.label.setObjectName(_fromUtf8("label")) + self.gridLayout.addWidget(self.label, 0, 0, 1, 5) + self.gridLayout_2.addWidget(self.groupBox, 0, 0, 1, 1) + + self.retranslateUi(regenerateAddressesDialog) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), regenerateAddressesDialog.accept) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("rejected()")), regenerateAddressesDialog.reject) + QtCore.QMetaObject.connectSlotsByName(regenerateAddressesDialog) + + def retranslateUi(self, regenerateAddressesDialog): + regenerateAddressesDialog.setWindowTitle(_translate("regenerateAddressesDialog", "Regenerate Existing Addresses", None)) + self.groupBox.setTitle(_translate("regenerateAddressesDialog", "Regenerate existing addresses", None)) + self.label_6.setText(_translate("regenerateAddressesDialog", "Passphrase", None)) + self.label_11.setText(_translate("regenerateAddressesDialog", "Number of addresses to make based on your passphrase:", None)) + self.label_2.setText(_translate("regenerateAddressesDialog", "Address version number:", None)) + self.label_3.setText(_translate("regenerateAddressesDialog", "Stream number:", None)) + self.lineEditStreamNumber.setText(_translate("regenerateAddressesDialog", "1", None)) + self.checkBoxEighteenByteRipe.setText(_translate("regenerateAddressesDialog", "Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter", None)) + self.label_4.setText(_translate("regenerateAddressesDialog", "You must check (or not check) this box just like you did (or didn\'t) when you made your addresses the first time.", None)) + self.label.setText(_translate("regenerateAddressesDialog", "If you have previously made deterministic addresses but lost them due to an accident (like hard drive failure), you can regenerate them here. If you used the random number generator to make your addresses then this form will be of no use to you.", None)) + diff --git a/src/bitmessageqt/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..77fc9d70 100644 --- a/src/bitmessageqt/safehtmlparser.py +++ b/src/bitmessageqt/safehtmlparser.py @@ -1,75 +1,43 @@ -"""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 = [["&", "&"], ["\"", """], ["<", "<"], [">", ">"], ["\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`!()\[\]{};:\'".,<>?]))') 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,66 +45,61 @@ 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 not tag in SafeHTMLParser.acceptable_elements: return self.sanitised += "<" if inspect.stack()[1][3] == "handle_endtag": self.sanitised += "/" self.sanitised += tag - if attrs is not None: + if not attrs is None: for attr, val in attrs: if tag == "img" and attr == "src" and not self.allow_picture: val = "" elif attr == "src" and not self.allow_external_src: url = urlparse(val) - if url.scheme not in self.src_schemes: - val = "" + 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 - + self.sanitised += unicode(data, 'utf-8', 'replace') + def handle_charref(self, name): self.sanitised += "&#" + name + ";" - + def handle_entityref(self, name): self.sanitised += "&" + name + ";" def feed(self, data): - try: - data = unicode(data, 'utf-8') - except UnicodeDecodeError: - 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.replace_post(tmp) + tmp = SafeHTMLParser.multi_replace(data) + tmp = SafeHTMLParser.uriregex1.sub( + r'\1', + unicode(tmp, 'utf-8', 'replace')) + tmp = SafeHTMLParser.uriregex2.sub(r'\1', 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 eeb507c7..6b7dd9b3 100644 --- a/src/bitmessageqt/settings.py +++ b/src/bitmessageqt/settings.py @@ -1,675 +1,491 @@ -""" -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 -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 connectionpool, knownnodes -from network.announcethread import 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 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.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.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.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")) + 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.checkBoxOpenCL = QtGui.QCheckBox(self.tabMaxAcceptableDifficulty) + self.checkBoxOpenCL.setObjectName = (_fromUtf8("checkBoxOpenCL")) + self.gridLayout_7.addWidget(self.checkBoxOpenCL, 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.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) -class SettingsDialog(QtGui.QDialog): - """The "Settings" dialog""" - # pylint: disable=too-many-instance-attributes - def __init__(self, parent=None, firstrun=False): - super(SettingsDialog, self).__init__(parent) - widgets.load('settings.ui', 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.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.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)) - self.app = QtGui.QApplication.instance() - self.parent = parent - self.firstrun = firstrun - self.config = config_obj - self.net_restart_needed = False - self.font_setting = None - self.timer = QtCore.QTimer() - - if self.config.safeGetBoolean('bitmessagesettings', 'dontconnect'): - self.firstrun = False - try: - import pkg_resources - except ImportError: - pass - 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.lineEditMaxOutboundConnections.setValidator( - QtGui.QIntValidator(0, 8, self.lineEditMaxOutboundConnections)) - - 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 adjust_from_config(self, config): - """Adjust all widgets state according to config settings""" - # pylint: disable=too-many-branches,too-many-statements - - current_style = self.app.get_windowstyle() - for i, sk in enumerate(QtGui.QStyleFactory.keys()): - self.comboBoxStyle.addItem(sk) - if sk == current_style: - self.comboBoxStyle.setCurrentIndex(i) - - self.save_font_setting(self.app.font()) - - 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 not sys.platform.startswith('win') 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(connectionpool.pool.streams), []) - ): - 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 save_font_setting(self, font): - """Save user font setting and set the buttonFont text""" - font_setting = (font.family(), font.pointSize()) - self.buttonFont.setText('{} {}'.format(*font_setting)) - self.font_setting = '{},{}'.format(*font_setting) - - def choose_font(self): - """Show the font selection dialog""" - font, valid = QtGui.QFontDialog.getFont() - if valid: - self.save_font_setting(font) - - 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())) - - window_style = str(self.comboBoxStyle.currentText()) - if self.app.get_windowstyle() != window_style or self.config.safeGet( - 'bitmessagesettings', 'font' - ) != self.font_setting: - self.config.set('bitmessagesettings', 'windowstyle', window_style) - self.config.set('bitmessagesettings', 'font', self.font_setting) - queues.UISignalQueue.put(( - 'updateStatusBar', ( - _translate( - "MainWindow", - "You need to restart the application to apply" - " the window style or default font."), 1) - )) - - 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 e7ce1d71..475821f8 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 @@ -147,46 +112,95 @@ - - - - Custom Style - - - - - - - 100 - 0 - - - - - - - - Font - - - - - - Interface Language - - - + + + 100 0 + + + System Settings + + + + + English + + + + + Esperanto + + + + + Français + + + + + Deutsch + + + + + Español + + + + + русский + + + + + Norsk + + + + + العربية + + + + + 简体中文 + + + + + 日本語 + + + + + Nederlands + + + + + Česky + + + + + Pirate English + + + + + Other (set in keys.dat) + + @@ -199,18 +213,6 @@ Network Settings - - 8 - - - 8 - - - 8 - - - 8 - @@ -218,13 +220,26 @@ + + + Qt::Horizontal + + + + 125 + 20 + + + + + Listen for connections on port: - + @@ -234,30 +249,10 @@ - - - - UPnP - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - + Bandwidth limit @@ -322,33 +317,10 @@ - - - - Maximum outbound connections: [0: none] - - - - - - - - 0 - 0 - - - - - 60 - 16777215 - - - - - + Proxy server / Tor @@ -429,13 +401,6 @@ - - - - Only connect to onion services (*.onion) - - - @@ -445,12 +410,12 @@ - SOCKS4a + SOCKS4a - SOCKS5 + SOCKS5 @@ -458,14 +423,7 @@ - - - - Announce self by UDP - - - - + Qt::Vertical @@ -485,18 +443,6 @@ Demanded difficulty - - 8 - - - 8 - - - 8 - - - 8 - @@ -625,18 +571,6 @@ Max acceptable difficulty - - 8 - - - 8 - - - 8 - - - 8 - @@ -741,33 +675,6 @@ - - - - - - Hardware GPU acceleration (OpenCL): - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - @@ -775,18 +682,6 @@ Namecoin integration - - 8 - - - 8 - - - 8 - - - 8 - @@ -970,18 +865,6 @@ Resends Expire - - 8 - - - 8 - - - 8 - - - 8 - @@ -1006,69 +889,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. - - @@ -1089,14 +994,7 @@
- - - - LanguageBox - QComboBox -
bitmessageqt.languagebox
-
-
+ tabWidgetSettings checkBoxStartOnLogon @@ -1180,59 +1078,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 - - - - - buttonFont - clicked() - settingsDialog - choose_font -
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 deleted file mode 100644 index 33b4c500..00000000 --- a/src/bitmessageqt/sound.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- -"""Sound Module""" - -# sound type constants -SOUND_NONE = 0 -SOUND_KNOWN = 1 -SOUND_UNKNOWN = 2 -SOUND_CONNECTED = 3 -SOUND_DISCONNECTED = 4 -SOUND_CONNECTION_GREEN = 5 - - -# returns true if the given sound category is a connection sound -# rather than a received message sound -def is_connection_sound(category): - """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/specialaddressbehavior.py b/src/bitmessageqt/specialaddressbehavior.py new file mode 100644 index 00000000..78ff890d --- /dev/null +++ b/src/bitmessageqt/specialaddressbehavior.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'specialaddressbehavior.ui' +# +# Created: Fri Apr 26 17:43:31 2013 +# by: PyQt4 UI code generator 4.9.4 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + _fromUtf8 = lambda s: s + +class Ui_SpecialAddressBehaviorDialog(object): + def setupUi(self, SpecialAddressBehaviorDialog): + SpecialAddressBehaviorDialog.setObjectName(_fromUtf8("SpecialAddressBehaviorDialog")) + SpecialAddressBehaviorDialog.resize(386, 172) + self.gridLayout = QtGui.QGridLayout(SpecialAddressBehaviorDialog) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.radioButtonBehaveNormalAddress = QtGui.QRadioButton(SpecialAddressBehaviorDialog) + self.radioButtonBehaveNormalAddress.setChecked(True) + self.radioButtonBehaveNormalAddress.setObjectName(_fromUtf8("radioButtonBehaveNormalAddress")) + self.gridLayout.addWidget(self.radioButtonBehaveNormalAddress, 0, 0, 1, 1) + self.radioButtonBehaviorMailingList = QtGui.QRadioButton(SpecialAddressBehaviorDialog) + self.radioButtonBehaviorMailingList.setObjectName(_fromUtf8("radioButtonBehaviorMailingList")) + self.gridLayout.addWidget(self.radioButtonBehaviorMailingList, 1, 0, 1, 1) + self.label = QtGui.QLabel(SpecialAddressBehaviorDialog) + self.label.setWordWrap(True) + self.label.setObjectName(_fromUtf8("label")) + self.gridLayout.addWidget(self.label, 2, 0, 1, 1) + self.label_2 = QtGui.QLabel(SpecialAddressBehaviorDialog) + self.label_2.setObjectName(_fromUtf8("label_2")) + self.gridLayout.addWidget(self.label_2, 3, 0, 1, 1) + self.lineEditMailingListName = QtGui.QLineEdit(SpecialAddressBehaviorDialog) + self.lineEditMailingListName.setEnabled(False) + self.lineEditMailingListName.setObjectName(_fromUtf8("lineEditMailingListName")) + self.gridLayout.addWidget(self.lineEditMailingListName, 4, 0, 1, 1) + self.buttonBox = QtGui.QDialogButtonBox(SpecialAddressBehaviorDialog) + self.buttonBox.setMinimumSize(QtCore.QSize(368, 0)) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok) + self.buttonBox.setObjectName(_fromUtf8("buttonBox")) + self.gridLayout.addWidget(self.buttonBox, 5, 0, 1, 1) + + self.retranslateUi(SpecialAddressBehaviorDialog) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), SpecialAddressBehaviorDialog.accept) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("rejected()")), SpecialAddressBehaviorDialog.reject) + QtCore.QObject.connect(self.radioButtonBehaviorMailingList, QtCore.SIGNAL(_fromUtf8("clicked(bool)")), self.lineEditMailingListName.setEnabled) + QtCore.QObject.connect(self.radioButtonBehaveNormalAddress, QtCore.SIGNAL(_fromUtf8("clicked(bool)")), self.lineEditMailingListName.setDisabled) + QtCore.QMetaObject.connectSlotsByName(SpecialAddressBehaviorDialog) + SpecialAddressBehaviorDialog.setTabOrder(self.radioButtonBehaveNormalAddress, self.radioButtonBehaviorMailingList) + SpecialAddressBehaviorDialog.setTabOrder(self.radioButtonBehaviorMailingList, self.lineEditMailingListName) + SpecialAddressBehaviorDialog.setTabOrder(self.lineEditMailingListName, self.buttonBox) + + def retranslateUi(self, SpecialAddressBehaviorDialog): + SpecialAddressBehaviorDialog.setWindowTitle(QtGui.QApplication.translate("SpecialAddressBehaviorDialog", "Special Address Behavior", None, QtGui.QApplication.UnicodeUTF8)) + self.radioButtonBehaveNormalAddress.setText(QtGui.QApplication.translate("SpecialAddressBehaviorDialog", "Behave as a normal address", None, QtGui.QApplication.UnicodeUTF8)) + self.radioButtonBehaviorMailingList.setText(QtGui.QApplication.translate("SpecialAddressBehaviorDialog", "Behave as a pseudo-mailing-list address", None, QtGui.QApplication.UnicodeUTF8)) + self.label.setText(QtGui.QApplication.translate("SpecialAddressBehaviorDialog", "Mail received to a pseudo-mailing-list address will be automatically broadcast to subscribers (and thus will be public).", None, QtGui.QApplication.UnicodeUTF8)) + self.label_2.setText(QtGui.QApplication.translate("SpecialAddressBehaviorDialog", "Name of the pseudo-mailing-list:", None, QtGui.QApplication.UnicodeUTF8)) + diff --git a/src/bitmessageqt/statusbar.py b/src/bitmessageqt/statusbar.py deleted file mode 100644 index 2add604d..00000000 --- a/src/bitmessageqt/statusbar.py +++ /dev/null @@ -1,39 +0,0 @@ -# pylint: disable=unused-argument -"""Status bar Module""" - -from time import time -from PyQt4 import QtGui - - -class BMStatusBar(QtGui.QStatusBar): - """Status bar with queue and priorities""" - duration = 10000 - deleteAfter = 60 - - def __init__(self, parent=None): - super(BMStatusBar, self).__init__(parent) - self.important = [] - self.timer = self.startTimer(BMStatusBar.duration) - 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: - if time() > self.important[self.iterator][1] + BMStatusBar.deleteAfter: - del self.important[self.iterator] - self.iterator -= 1 - continue - except IndexError: - self.iterator = -1 - continue - super(BMStatusBar, self).showMessage(self.important[self.iterator][0], 0) - break - - def addImportant(self, message): - self.important.append([message, time()]) - self.iterator = len(self.important) - 2 - self.timerEvent(None) diff --git a/src/bitmessageqt/support.py b/src/bitmessageqt/support.py index a84affa4..4742e175 100644 --- a/src/bitmessageqt/support.py +++ b/src/bitmessageqt/support.py @@ -1,43 +1,25 @@ -"""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 -import defaults -import network.stats -import paths -import proofofwork -import queues -import state -from bmconfigparser import config +from debug import logger from foldertree import AccountMixin -from helper_sql import sqlExecute, sqlQuery +from helper_sql import * from l10n import getTranslationLanguage -from openclpow import openclEnabled +from openclpow import has_opencl +from proofofwork import bmpow from pyelliptic.openssl import OpenSSL -from settings import getSOCKSProxyType -from version import softwareVersion -from tr import _translate - +import shared # 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_ADDRESS = 'BM-2cTkCtMYkrSPwFTpgcBrMrf5d8oZwvMZWK' +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,10 +29,9 @@ 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: {} +PyBitmesage version: {} Operating system: {} Architecture: {}bit Python Version: {} @@ -63,62 +44,43 @@ 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) + 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 shared.safeConfigGetBoolean(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 - )) - while state.shutdown == 0 and not checkHasNormalAddress(): + shared.addressGeneratorQueue.put(('createRandomAddress', 4, 1, str(QtGui.QApplication.translate("Support", SUPPORT_MY_LABEL)), 1, "", False, shared.networkDefaultProofOfWorkNonceTrialsPerByte, shared.networkDefaultPayloadLengthExtraBytes)) + while shared.shutdown == 0 and not checkHasNormalAddress(): time.sleep(.2) myapp.rerenderComboBoxSendFrom() return checkHasNormalAddress() - def createSupportMessage(myapp): checkAddressBook(myapp) address = createAddressIfNeeded(myapp) - if state.shutdown: + if shared.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) - - version = softwareVersion - commit = paths.lastCommit().get('commit') - if commit: - version += " GIT " + commit - + + version = shared.softwareVersion os = sys.platform if os == "win32": windowsversion = sys.getwindowsversion() @@ -132,32 +94,35 @@ 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) + + SSLEAY_VERSION = 0 + OpenSSL._lib.SSLeay.restype = ctypes.c_long + OpenSSL._lib.SSLeay_version.restype = ctypes.c_char_p + OpenSSL._lib.SSLeay_version.argtypes = [ctypes.c_int] + opensslversion = "%s (Python internal), %s (external for PyElliptic)" % (ssl.OPENSSL_VERSION, OpenSSL._lib.SSLeay_version(SSLEAY_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" + if shared.frozen: + frozen = shared.frozen + portablemode = "True" if shared.appdata == shared.lookupExeFolder() else "False" + cpow = "True" if bmpow else "False" + #cpow = QtGui.QApplication.translate("Support", cpow) + openclpow = "True" if shared.safeConfigGetBoolean('bitmessagesettings', 'opencl') and has_opencl() else "False" + #openclpow = QtGui.QApplication.translate("Support", openclpow) locale = getTranslationLanguage() - socks = getSOCKSProxyType(config) or "N/A" - upnp = config.safeGet('bitmessagesettings', 'upnp', "N/A") - connectedhosts = len(network.stats.connectedHostsList()) + try: + socks = shared.config.get('bitmessagesettings', 'socksproxytype') + except: + socks = "N/A" + try: + upnp = shared.config.get('bitmessagesettings', 'upnp') + except: + upnp = "N/A" + connectedhosts = len(shared.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( - myapp.ui.tabWidgetSend.indexOf(myapp.ui.sendDirect) - ) + myapp.ui.tabWidgetSend.setCurrentIndex(0) # send tab - myapp.ui.tabWidget.setCurrentIndex( - myapp.ui.tabWidget.indexOf(myapp.ui.send) - ) + myapp.ui.tabWidget.setCurrentIndex(1) diff --git a/src/bitmessageqt/tests/__init__.py b/src/bitmessageqt/tests/__init__.py deleted file mode 100644 index a81ddb04..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 d3fda8aa..00000000 --- a/src/bitmessageqt/tests/main.py +++ /dev/null @@ -1,71 +0,0 @@ -"""Common definitions for bitmessageqt tests""" - -import sys -import unittest - -from PyQt4 import QtCore, QtGui -from six.moves import queue - -import bitmessageqt -from bitmessageqt import _translate, config, queues - - -class TestBase(unittest.TestCase): - """Base class for bitmessageqt test case""" - - @classmethod - def setUpClass(cls): - """Provide the UI test cases with common settings""" - cls.config = config - - 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): - """Search for exceptions in closures called by timer and fail if any""" - # self.app.deleteLater() - concerning = [] - while True: - try: - thread, exc = queues.excQueue.get(block=False) - except queue.Empty: - break - if thread == 'tests': - concerning.append(exc) - if concerning: - self.fail( - 'Exceptions found in the main thread:\n%s' % '\n'.join(( - str(e) for e in concerning - ))) - - -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 bad28ed7..00000000 --- a/src/bitmessageqt/tests/settings.py +++ /dev/null @@ -1,78 +0,0 @@ -"""Tests for PyBitmessage settings""" -import threading -import time - -from PyQt4 import QtCore, QtGui, QtTest - -from bmconfigparser import config -from bitmessageqt import settings - -from .main import TestBase - - -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') - - def test_styling(self): - """Test custom windows style and font""" - style_setting = config.safeGet('bitmessagesettings', 'windowstyle') - font_setting = config.safeGet('bitmessagesettings', 'font') - self.assertIs(style_setting, None) - self.assertIs(font_setting, None) - style_control = self.dialog.comboBoxStyle - self.assertEqual( - style_control.currentText(), self.app.get_windowstyle()) - - def call_font_dialog(): - """A function to get the open font dialog and accept it""" - font_dialog = QtGui.QApplication.activeModalWidget() - self.assertTrue(isinstance(font_dialog, QtGui.QFontDialog)) - selected_font = font_dialog.currentFont() - self.assertEqual( - config.safeGet('bitmessagesettings', 'font'), '{},{}'.format( - selected_font.family(), selected_font.pointSize())) - - font_dialog.accept() - self.dialog.accept() - self.assertEqual( - config.safeGet('bitmessagesettings', 'windowstyle'), - style_control.currentText()) - - def click_font_button(): - """Use QtTest to click the button""" - QtTest.QTest.mouseClick( - self.dialog.buttonFont, QtCore.Qt.LeftButton) - - style_count = style_control.count() - self.assertGreater(style_count, 1) - for i in range(style_count): - if i != style_control.currentIndex(): - style_control.setCurrentIndex(i) - break - - QtCore.QTimer.singleShot(30, click_font_button) - QtCore.QTimer.singleShot(60, call_font_dialog) - time.sleep(2) 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..ea18f0b0 100644 --- a/src/bitmessageqt/uisignaler.py +++ b/src/bitmessageqt/uisignaler.py @@ -1,9 +1,8 @@ from PyQt4.QtCore import QThread, SIGNAL +import shared import sys -import queues - class UISignaler(QThread): _instance = None @@ -19,14 +18,11 @@ class UISignaler(QThread): def run(self): while True: - command, data = queues.UISignalQueue.get() + command, data = shared.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': @@ -48,12 +44,7 @@ class UISignaler(QThread): "displayNewSentMessage(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), toAddress, fromLabel, fromAddress, subject, message, ackdata) elif command == 'updateNetworkStatusTab': - outbound, add, destination = data - self.emit( - SIGNAL("updateNetworkStatusTab(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), - outbound, - add, - destination) + self.emit(SIGNAL("updateNetworkStatusTab()")) elif command == 'updateNumberOfMessagesProcessed': self.emit(SIGNAL("updateNumberOfMessagesProcessed()")) elif command == 'updateNumberOfPubkeysProcessed': @@ -80,11 +71,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..79b0e2a9 100644 --- a/src/bitmessageqt/utils.py +++ b/src/bitmessageqt/utils.py @@ -1,109 +1,99 @@ +from PyQt4 import QtGui import hashlib import os - -from PyQt4 import QtGui - -import state +import shared from addresses import addBMIfNotPresent -from bmconfigparser import config 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 = shared.config.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 = shared.config.get('bitmessagesettings', 'identiconsuffix') + + if not shared.config.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) - pix = QtGui.QPixmap.fromImage(qim) + qim = QImage(data, size, size, QImage.Format_ARGB32) + pix = QPixmap.fromImage(qim) idcon = QtGui.QIcon() 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 = shared.appdata + 'avatars/' + hash + '.' + ext.lower() + upper_hash = shared.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 for ext in extensions: - lower_default = state.appdata + 'avatars/' + 'default.' + ext.lower() - upper_default = state.appdata + 'avatars/' + 'default.' + ext.upper() + lower_default = shared.appdata + 'avatars/' + 'default.' + ext.lower() + upper_default = shared.appdata + 'avatars/' + 'default.' + ext.upper() if os.path.isfile(lower_default): default = lower_default idcon.addFile(lower_default) diff --git a/src/bitmessageqt/widgets.py b/src/bitmessageqt/widgets.py index 8ef807f2..02394296 100644 --- a/src/bitmessageqt/widgets.py +++ b/src/bitmessageqt/widgets.py @@ -1,10 +1,10 @@ from PyQt4 import uic import os.path -import paths import sys +from shared import codePath def resource_path(resFile): - baseDir = paths.codePath() + baseDir = codePath() for subDir in ["ui", "bitmessageqt"]: if os.path.isdir(os.path.join(baseDir, subDir)) and os.path.isfile(os.path.join(baseDir, subDir, resFile)): return os.path.join(baseDir, subDir, resFile) diff --git a/src/bitmsghash/Makefile b/src/bitmsghash/Makefile index 7a494c39..723233ac 100644 --- a/src/bitmsghash/Makefile +++ b/src/bitmsghash/Makefile @@ -1,24 +1,20 @@ UNAME_S := $(shell uname -s) ifeq ($(UNAME_S),Darwin) - CCFLAGS += -I/usr/local/opt/openssl/include - LDFLAGS += -L/usr/local/opt/openssl/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 + CCFLAGS += -I/usr/local/Cellar/openssl/1.0.2d_1/include + LDFLAGS += -L/usr/local/Cellar/openssl/1.0.2d_1/lib endif - + all: bitmsghash.so powtest: ./testpow.py bitmsghash.so: bitmsghash.o - ${CXX} bitmsghash.o -shared -fPIC -lcrypto $(LDFLAGS) + ${CXX} bitmsghash.o -shared -fPIC -lpthread -lcrypto $(LDFLAGS) -o bitmsghash.so bitmsghash.o: ${CXX} -Wall -O3 -march=native -fPIC $(CCFLAGS) -c bitmsghash.cpp clean: - rm -f bitmsghash.o bitmsghash.so bitmsghash*.dll + rm -f bitmsghash.o bitmsghash.so + diff --git a/src/bitmsghash/Makefile.bsd b/src/bitmsghash/Makefile.bsd deleted file mode 100644 index 6e680c86..00000000 --- a/src/bitmsghash/Makefile.bsd +++ /dev/null @@ -1,14 +0,0 @@ -all: bitmsghash.so - -powtest: - ./testpow.py - -bitmsghash.so: bitmsghash.o - ${CXX} bitmsghash.o -shared -fPIC -lpthread -lcrypto $(LDFLAGS) -o bitmsghash.so - -bitmsghash.o: - ${CXX} -Wall -O3 -march=native -fPIC $(CCFLAGS) -c bitmsghash.cpp - -clean: - rm -f bitmsghash.o bitmsghash.so - diff --git a/src/bitmsghash/Makefile.msvc b/src/bitmsghash/Makefile.msvc deleted file mode 100644 index 63482c34..00000000 --- a/src/bitmsghash/Makefile.msvc +++ /dev/null @@ -1,2 +0,0 @@ -all: - cl /I C:\OpenSSL-1.0.2j\include /INCREMENTAL bitmsghash.cpp /MT /link /DLL /OUT:bitmsghash32.dll /LIBPATH:C:\OpenSSL-1.0.2j\lib\ libeay32.lib ws2_32.lib diff --git a/src/bitmsghash/bitmsghash.cpp b/src/bitmsghash/bitmsghash.cpp index 24775475..f4979702 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 @@ -66,11 +66,7 @@ void * threadfunc(void* param) { successval = tmpnonce; } } -#ifdef _WIN32 - return 0; -#else return NULL; -#endif } void getnumthreads() @@ -79,10 +75,6 @@ void getnumthreads() DWORD_PTR dwProcessAffinity, dwSystemAffinity; #elif __linux__ cpu_set_t dwProcessAffinity; -#elif __OpenBSD__ - int mib[2], core_count = 0; - int dwProcessAffinity = 0; - size_t len2; #else int dwProcessAffinity = 0; int32_t core_count = 0; @@ -94,12 +86,6 @@ void getnumthreads() GetProcessAffinityMask(GetCurrentProcess(), &dwProcessAffinity, &dwSystemAffinity); #elif __linux__ sched_getaffinity(0, len, &dwProcessAffinity); -#elif __OpenBSD__ - len2 = sizeof(core_count); - mib[0] = CTL_HW; - mib[1] = HW_NCPU; - if (sysctl(mib, 2, &core_count, &len2, 0, 0) == 0) - numthreads = core_count; #else if (sysctlbyname("hw.logicalcpu", &core_count, &len, 0, 0) == 0) numthreads = core_count; @@ -108,11 +94,7 @@ void getnumthreads() #endif for (unsigned int i = 0; i < len * 8; i++) #if defined(_WIN32) -#if defined(_MSC_VER) if (dwProcessAffinity & (1i64 << i)) -#else // CYGWIN/MINGW - if (dwProcessAffinity & (1ULL << i)) -#endif #elif defined __linux__ if (CPU_ISSET(i, &dwProcessAffinity)) #else diff --git a/src/bmconfigparser.py b/src/bmconfigparser.py deleted file mode 100644 index abf285ad..00000000 --- a/src/bmconfigparser.py +++ /dev/null @@ -1,179 +0,0 @@ -""" -BMConfigParser class definition and default configuration settings -""" - -import os -import shutil -from threading import Event -from datetime import datetime - -from six import string_types -from six.moves import configparser - -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 = {} - - def set(self, section, option, value=None): - if self._optcre is self.OPTCRE or value: - if not isinstance(value, string_types): - 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) - - def get(self, section, option, **kwargs): - """Try returning temporary value before using parent get()""" - try: - return self._temp[section][option] - except KeyError: - pass - return SafeConfigParser.get( - self, section, option, **kwargs) - - def setTemp(self, section, option, value=None): - """Temporary set option to value, not saving.""" - 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 False - - def safeGetInt(self, section, option, default=0): - """Return value as integer, default on exceptions, - 0 if default missing""" - try: - return int(self.get(section, option)) - 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 - """ - try: - return self.get(section, option) - 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) - - 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 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 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 - 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. - fileNameExisted = False - - with open(fileName, 'w') 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) - except AttributeError: - return True - - @staticmethod - def validate_bitmessagesettings_maxoutboundconnections(value): - """Reject maxoutboundconnections that are too high or too low""" - try: - value = int(value) - except ValueError: - return False - 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 33da1371..bca9697d 100644 --- a/src/class_addressGenerator.py +++ b/src/class_addressGenerator.py @@ -1,376 +1,291 @@ -""" -A thread for creating addresses -""" - +import shared +import threading import time +import sys +from pyelliptic.openssl import OpenSSL +import ctypes +import hashlib +import highlevelcrypto +from addresses import * +from debug import logger +from helper_threading import * +from pyelliptic import arithmetic +import tr 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 network import StoppableThread -from tr import _translate - - -class AddressGeneratorException(Exception): - '''Generic AddressGenerator exception''' - pass - - -class addressGenerator(StoppableThread): - """A thread for creating addresses""" - - name = "addressGenerator" +class addressGenerator(threading.Thread, StoppableThread): + 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') - + shared.addressGeneratorQueue.put(("stopThread", "data")) + 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() + while shared.shutdown == 0: + queueValue = shared.addressGeneratorQueue.get() nonceTrialsPerByte = 0 payloadLengthExtraBytes = 0 - live = True if queueValue[0] == 'createChan': - command, addressVersionNumber, streamNumber, label, \ - deterministicPassphrase, live = queueValue + command, addressVersionNumber, streamNumber, label, deterministicPassphrase = queueValue eighteenByteRipe = False numberOfAddressesToMake = 1 numberOfNullBytesDemandedOnFrontOfRipeHash = 1 elif queueValue[0] == 'joinChan': - command, chanAddress, label, deterministicPassphrase, \ - live = queueValue + command, chanAddress, label, deterministicPassphrase = 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 = shared.config.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 = shared.config.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 = shared.config.getint( 'bitmessagesettings', 'defaultnoncetrialsperbyte') - if nonceTrialsPerByte < \ - defaults.networkDefaultProofOfWorkNonceTrialsPerByte: - nonceTrialsPerByte = \ - defaults.networkDefaultProofOfWorkNonceTrialsPerByte + if nonceTrialsPerByte < shared.networkDefaultProofOfWorkNonceTrialsPerByte: + nonceTrialsPerByte = shared.networkDefaultProofOfWorkNonceTrialsPerByte if payloadLengthExtraBytes == 0: - payloadLengthExtraBytes = config.getint( + payloadLengthExtraBytes = shared.config.getint( 'bitmessagesettings', 'defaultpayloadlengthextrabytes') - if payloadLengthExtraBytes < \ - defaults.networkDefaultPayloadLengthExtraBytes: - payloadLengthExtraBytes = \ - defaults.networkDefaultPayloadLengthExtraBytes + if payloadLengthExtraBytes < shared.networkDefaultPayloadLengthExtraBytes: + payloadLengthExtraBytes = shared.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. + shared.UISignalQueue.put(( + '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 - privSigningKey, pubSigningKey = highlevelcrypto.random_keys() + potentialPrivSigningKey = OpenSSL.rand(32) + potentialPubSigningKey = highlevelcrypto.pointMult(potentialPrivSigningKey) while True: numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix += 1 - potentialPrivEncryptionKey, potentialPubEncryptionKey = \ - highlevelcrypto.random_keys() - ripe = highlevelcrypto.to_ripe( - pubSigningKey, potentialPubEncryptionKey) - if ( - ripe[:numberOfNullBytesDemandedOnFrontOfRipeHash] - == b'\x00' * numberOfNullBytesDemandedOnFrontOfRipeHash - ): + potentialPrivEncryptionKey = OpenSSL.rand(32) + potentialPubEncryptionKey = highlevelcrypto.pointMult( + potentialPrivEncryptionKey) + ripe = hashlib.new('ripemd160') + sha = hashlib.new('sha512') + sha.update( + potentialPubSigningKey + potentialPubEncryptionKey) + 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()) - privSigningKeyWIF = highlevelcrypto.encodeWalletImportFormat( - privSigningKey) - privEncryptionKeyWIF = highlevelcrypto.encodeWalletImportFormat( - potentialPrivEncryptionKey) + # 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 = '\x80' + potentialPrivSigningKey + checksum = hashlib.sha256(hashlib.sha256( + privSigningKey).digest()).digest()[0:4] + privSigningKeyWIF = arithmetic.changebase( + privSigningKey + 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( + privEncryptionKey = '\x80' + potentialPrivEncryptionKey + checksum = hashlib.sha256(hashlib.sha256( + privEncryptionKey).digest()).digest()[0:4] + privEncryptionKeyWIF = arithmetic.changebase( + privEncryptionKey + checksum, 256, 58) + + shared.config.add_section(address) + shared.config.set(address, 'label', label) + shared.config.set(address, 'enabled', 'true') + shared.config.set(address, 'decoy', 'false') + shared.config.set(address, 'noncetrialsperbyte', str( nonceTrialsPerByte)) - config.set(address, 'payloadlengthextrabytes', str( + shared.config.set(address, 'payloadlengthextrabytes', str( payloadLengthExtraBytes)) - config.set( - address, 'privsigningkey', privSigningKeyWIF.decode()) - config.set( - address, 'privencryptionkey', - privEncryptionKeyWIF.decode()) - config.save() + shared.config.set( + address, 'privSigningKey', privSigningKeyWIF) + shared.config.set( + address, 'privEncryptionKey', privEncryptionKeyWIF) + shared.writeKeysFile() # The API and the join and create Chan functionality # both need information back from the address generator. - queues.apiAddressGeneratorReturnQueue.put(address) + shared.apiAddressGeneratorReturnQueue.put(address) - queues.UISignalQueue.put(( - 'updateStatusBar', - _translate( - "MainWindow", - "Done generating address. Doing work necessary" - " to broadcast it...") - )) - queues.UISignalQueue.put(('writeNewAddressToTable', ( + shared.UISignalQueue.put(( + 'updateStatusBar', tr._translate("MainWindow", "Done generating address. Doing work necessary to broadcast it..."))) + shared.UISignalQueue.put(('writeNewAddressToTable', ( label, address, streamNumber))) shared.reloadMyAddressHashes() if addressVersionNumber == 3: - queues.workerQueue.put(( - 'sendOutOrStoreMyV3Pubkey', ripe)) + shared.workerQueue.put(( + 'sendOutOrStoreMyV3Pubkey', ripe.digest())) elif addressVersionNumber == 4: - queues.workerQueue.put(( + shared.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)) - )) + shared.UISignalQueue.put(( + '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, potentialPubSigningKey = \ - highlevelcrypto.deterministic_keys( - deterministicPassphrase, - encodeVarint(signingKeyNonce)) - potentialPrivEncryptionKey, potentialPubEncryptionKey = \ - highlevelcrypto.deterministic_keys( - deterministicPassphrase, - encodeVarint(encryptionKeyNonce)) - + potentialPrivSigningKey = hashlib.sha512( + deterministicPassphrase + encodeVarint(signingKeyNonce)).digest()[:32] + potentialPrivEncryptionKey = hashlib.sha512( + deterministicPassphrase + encodeVarint(encryptionKeyNonce)).digest()[:32] + potentialPubSigningKey = highlevelcrypto.pointMult( + potentialPrivSigningKey) + potentialPubEncryptionKey = highlevelcrypto.pointMult( + potentialPrivEncryptionKey) signingKeyNonce += 2 encryptionKeyNonce += 2 - ripe = highlevelcrypto.to_ripe( - potentialPubSigningKey, potentialPubEncryptionKey) - if ( - ripe[:numberOfNullBytesDemandedOnFrontOfRipeHash] - == b'\x00' * numberOfNullBytesDemandedOnFrontOfRipeHash - ): + ripe = hashlib.new('ripemd160') + sha = hashlib.new('sha512') + sha.update( + potentialPubSigningKey + potentialPubEncryptionKey) + 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') + shared.apiAddressGeneratorReturnQueue.put('chan name does not match address') saveAddressToDisk = False if command == 'getDeterministicAddress': saveAddressToDisk = False - if saveAddressToDisk and live: - privSigningKeyWIF = \ - highlevelcrypto.encodeWalletImportFormat( - potentialPrivSigningKey) - privEncryptionKeyWIF = \ - highlevelcrypto.encodeWalletImportFormat( - potentialPrivEncryptionKey) + if saveAddressToDisk: + # 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 = '\x80' + potentialPrivSigningKey + checksum = hashlib.sha256(hashlib.sha256( + privSigningKey).digest()).digest()[0:4] + privSigningKeyWIF = arithmetic.changebase( + privSigningKey + checksum, 256, 58) + 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) + shared.config.add_section(address) addressAlreadyExists = False - except configparser.DuplicateSectionError: + except: addressAlreadyExists = True - + if addressAlreadyExists: - self.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) - )) + logger.info('%s already exists. Not adding it again.' % address) + shared.UISignalQueue.put(( + '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) + shared.config.set(address, 'label', label) + shared.config.set(address, 'enabled', 'true') + shared.config.set(address, 'decoy', 'false') + if command == 'joinChan' or command == 'createChan': + shared.config.set(address, 'chan', 'true') + shared.config.set(address, 'noncetrialsperbyte', str( + nonceTrialsPerByte)) + shared.config.set(address, 'payloadlengthextrabytes', str( + payloadLengthExtraBytes)) + shared.config.set( + address, 'privSigningKey', privSigningKeyWIF) + shared.config.set( + address, 'privEncryptionKey', privEncryptionKeyWIF) + shared.writeKeysFile() - queues.UISignalQueue.put(( - 'writeNewAddressToTable', - (label, address, str(streamNumber)) - )) + shared.UISignalQueue.put(('writeNewAddressToTable', ( + label, address, str(streamNumber)))) listOfNewAddressesToSendOutThroughTheAPI.append( address) - shared.myECCryptorObjects[ripe] = \ - highlevelcrypto.makeCryptor( - hexlify(potentialPrivEncryptionKey)) - shared.myAddressesByHash[ripe] = address - tag = highlevelcrypto.double_sha512( - encodeVarint(addressVersionNumber) - + encodeVarint(streamNumber) + ripe - )[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)) + shared.workerQueue.put(( + '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(( + shared.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) + shared.UISignalQueue.put(( + 'updateStatusBar', tr._translate("MainWindow", "Done generating address"))) + # Done generating addresses. - if command in ( - 'createDeterministicAddresses', 'createChan', 'joinChan' - ): - queues.apiAddressGeneratorReturnQueue.put( + if command == 'createDeterministicAddresses' or command == 'joinChan' or command == 'createChan': + shared.apiAddressGeneratorReturnQueue.put( listOfNewAddressesToSendOutThroughTheAPI) elif command == 'getDeterministicAddress': - queues.apiAddressGeneratorReturnQueue.put(address) + shared.apiAddressGeneratorReturnQueue.put(address) else: - raise AddressGeneratorException( - "Error in the addressGenerator thread. Thread was" - + " given a command it could not understand: " + command) - queues.addressGeneratorQueue.task_done() + raise Exception( + "Error in the addressGenerator thread. Thread was given a command it could not understand: " + command) + shared.addressGeneratorQueue.task_done() diff --git a/src/class_objectHashHolder.py b/src/class_objectHashHolder.py new file mode 100644 index 00000000..e4e2c6d8 --- /dev/null +++ b/src/class_objectHashHolder.py @@ -0,0 +1,55 @@ +# 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) + 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(self.size): + self.collectionOfHashLists[i] = [] + self.collectionOfPeerLists[i] = [] + + 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 %= self.size + time.sleep(1) + + def holdHash(self,hash): + self.collectionOfHashLists[random.randrange(0, self.size)].append(hash) + + def hasHash(self, hash): + if hash in (hashlist for hashlist in self.collectionOfHashLists): + logger.debug("Hash in hashHolder") + return True + return False + + def holdPeer(self,peerDetails): + self.collectionOfPeerLists[random.randrange(0, self.size)].append(peerDetails) + + def hashCount(self): + return sum([len(x) for x in self.collectionOfHashLists]) + + def close(self): + self.shutdown = True diff --git a/src/class_objectProcessor.py b/src/class_objectProcessor.py index 974631cb..d5fe3a31 100644 --- a/src/class_objectProcessor.py +++ b/src/class_objectProcessor.py @@ -1,40 +1,27 @@ -""" -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 * +import helper_generic +from helper_generic import addDataPadding import helper_bitcoin import helper_inbox -import helper_msgcoding import helper_sent -import highlevelcrypto +from helper_sql import * +import tr +from debug import logger import l10n -import protocol -import queues -import shared -import state -from addresses import ( - decodeAddress, decodeVarint, - encodeAddress, encodeVarint, varintDecodeError -) -from bmconfigparser import config -from helper_sql import ( - sql_ready, sql_timeout, SqlBulkExecute, sqlExecute, sqlQuery) -from network import knownnodes, invQueue -from network.node import Peer -from tr import _translate - -logger = logging.getLogger('default') class objectProcessor(threading.Thread): @@ -44,146 +31,58 @@ 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 + shared.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) + objectType, data = shared.objectProcessorQueue.get() 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) - else: - logger.info( - 'Don\'t know how to handle object type %s', - objectType) - except helper_msgcoding.DecompressionSizeException as e: - logger.error( - 'The object is too big after decompression (stopped' - ' decompressing at %ib, your configured limit %ib).' - ' Ignoring', - e.size, config.safeGetInt('zlib', 'maxsize')) + logger.critical('Error! Bug! The class_objectProcessor was passed an object type it doesn\'t recognize: %s' % str(objectType)) 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) + if shared.shutdown: + 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) + while shared.objectProcessorQueue.curSize > 0: + objectType, data = shared.objectProcessorQueue.get() + sql.execute('''INSERT INTO objectprocessorqueue VALUES (?,?)''', + objectType,data) numberOfObjectsThatWereInTheObjectProcessorQueue += 1 - logger.debug( - 'Saved %s objects from the objectProcessorQueue to' - ' disk. objectProcessorThread exiting.', - numberOfObjectsThatWereInTheObjectProcessorQueue) - state.shutdown = 2 + logger.debug('Saved %s objects from the objectProcessorQueue to disk. objectProcessorThread exiting.' % str(numberOfObjectsThatWereInTheObjectProcessorQueue)) + shared.shutdown = 2 break - - @staticmethod - def checkackdata(data): - """Checking Acknowledgement of message received or not?""" - # Let's check whether this is a message acknowledgement bound for us. - if len(data) < 32: - return - - # bypass nonce and time, retain object type/version/stream + body - readPosition = 16 - - if data[readPosition:] in state.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())) - )) - 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""" - if len(data) > 200: - return logger.info( - 'getpubkey is abnormally long. Sanity check failed.' - ' Ignoring object.') + + def processgetpubkey(self, data): readPosition = 20 # bypass the nonce, time, and object type requestedAddressVersionNumber, addressVersionLength = decodeVarint( data[readPosition:readPosition + 10]) @@ -193,40 +92,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] @@ -235,48 +124,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 shared.safeConfigGetBoolean(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(shared.config.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)) + shared.workerQueue.put(( + 'doPOWForMyV2Pubkey', requestedHash)) elif requestedAddressVersionNumber == 3: - queues.workerQueue.put(('sendOutOrStoreMyV3Pubkey', requestedHash)) + shared.workerQueue.put(( + 'sendOutOrStoreMyV3Pubkey', requestedHash)) elif requestedAddressVersionNumber == 4: - queues.workerQueue.put(('sendOutOrStoreMyV4Pubkey', myAddress)) + shared.workerQueue.put(( + 'sendOutOrStoreMyV4Pubkey', myAddress)) def processpubkey(self, data): - """Process a pubkey object""" pubkeyProcessingStartTime = time.time() - state.numberOfPubkeysProcessed += 1 - queues.UISignalQueue.put(( + shared.numberOfPubkeysProcessed += 1 + shared.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]) @@ -285,258 +165,238 @@ 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 - pubSigningKey = '\x04' + data[readPosition:readPosition + 64] + publicSigningKey = data[readPosition:readPosition + 64] # Is it possible for a public key to be invalid such that trying to # encrypt or sign with it will cause an error? If it is, it would # be easiest to test them here. readPosition += 64 - pubEncryptionKey = '\x04' + data[readPosition:readPosition + 64] - if len(pubEncryptionKey) < 65: - return logger.debug( - 'publicEncryptionKey length less than 64. Sanity check' - ' failed.') + publicEncryptionKey = data[readPosition:readPosition + 64] + if len(publicEncryptionKey) < 64: + 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] - ripe = highlevelcrypto.to_ripe(pubSigningKey, pubEncryptionKey) + dataToStore = data[20:readPosition] # The data we'll store in the pubkeys table. + sha = hashlib.new('sha512') + sha.update( + '\x04' + publicSigningKey + '\x04' + publicEncryptionKey) + 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(pubSigningKey), hexlify(pubEncryptionKey) - ) + 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 - pubSigningKey = '\x04' + data[readPosition:readPosition + 64] + publicSigningKey = '\x04' + data[readPosition:readPosition + 64] readPosition += 64 - pubEncryptionKey = '\x04' + data[readPosition: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(pubSigningKey)): + if highlevelcrypto.verify(data[8:endOfSignedDataPosition], signature, hexlify(publicSigningKey)): logger.debug('ECDSA verify passed (within processpubkey)') else: logger.warning('ECDSA verify failed (within processpubkey)') return - ripe = highlevelcrypto.to_ripe(pubSigningKey, pubEncryptionKey) + sha = hashlib.new('sha512') + sha.update(publicSigningKey + publicEncryptionKey) + 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(pubSigningKey), hexlify(pubEncryptionKey) - ) + 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.') - + if tag not in shared.neededPubkeys: + 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 = shared.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 - queues.UISignalQueue.put(( + shared.numberOfMessagesProcessed += 1 + shared.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 = highlevelcrypto.calculateInventoryHash(data) + inventoryHash = calculateInventoryHash(data) initialDecryptionSuccessful = False + # Let's check whether this is a message acknowledgement bound for us. + if data[-32:] in shared.ackdataForWhichImWatching: + logger.info('This msg IS an acknowledgement bound for me.') + del shared.ackdataForWhichImWatching[data[-32:]] + sqlExecute('UPDATE sent SET status=?, lastactiontime=? WHERE ackdata=?', + 'ackreceived', + int(time.time()), + data[-32:]) + shared.UISignalQueue.put(('updateSentItemStatusByAckdata', (data[-32:], tr._translate("MainWindow",'Acknowledgement of the message received %1').arg(l10n.formatTimestamp())))) + return + else: + logger.info('This was NOT an acknowledgement bound for me.') + # This is not an acknowledgement bound for me. See if it is a message # bound for me by trying to decrypt it with my private keys. - - for key, cryptorObject in sorted( - shared.myECCryptorObjects.items(), - key=lambda x: random.random()): # nosec B311 + + for key, cryptorObject in shared.myECCryptorObjects.items(): 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]) @@ -545,47 +405,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 = highlevelcrypto.double_sha512(signature)[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. - ripe = highlevelcrypto.to_ripe(pubSigningKey, pubEncryptionKey) + sha = hashlib.new('sha512') + sha.update(pubSigningKey + pubEncryptionKey) + 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( @@ -595,42 +446,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 shared.safeConfigGetBoolean(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 = shared.config.getint( toAddress, 'noncetrialsperbyte') - requiredPayloadLengthExtraBytes = config.getint( + requiredPayloadLengthExtraBytes = shared.config.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 shared.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 shared.config.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.') @@ -638,190 +477,176 @@ 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 = shared.config.get(toAddress, 'label') + if toLabel == '': + toLabel = toAddress - # toLabel = config.safeGet(toAddress, 'label', toAddress) - try: - decodedMessage = helper_msgcoding.MsgDecode( - messageEncodingType, message) - except helper_msgcoding.MsgDecodeException: - return - subject = decodedMessage.subject - body = decodedMessage.body - + if messageEncodingType == 2: + subject, body = self.decodeType2Message(message) + logger.info('Message subject (first 100 characters): %s' % repr(subject)[:100]) + elif messageEncodingType == 1: + body = message + subject = '' + elif messageEncodingType == 0: + logger.info('messageEncodingType == 0. Doing nothing with the message. They probably just sent it so that we would store their public key or send their ack data for them.') + subject = '' + body = '' + else: + body = 'Unknown encoding type.\n\n' + repr(message) + subject = '' # Let us make sure that we haven't already received this message if helper_inbox.isMessageAlreadyInInbox(sigHash): logger.info('This msg is already in our inbox. Ignoring it.') 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', ( + shared.UISignalQueue.put(('displayNewInboxMessage', ( inventoryHash, toAddress, fromAddress, subject, body))) # 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 shared.safeConfigGetBoolean('bitmessagesettings', 'apienabled'): + try: + apiNotifyPath = shared.config.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 shared.safeConfigGetBoolean(toAddress, 'mailinglist') and messageEncodingType != 0: + try: + mailingListName = shared.config.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. + ackdataForBroadcast = OpenSSL.rand( + 32) # We don't actually need the ackdataForBroadcast for acknowledgement since this is a broadcast message but we can use it to update the user interface when the POW is done generating. 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, + ackdataForBroadcast, + int(time.time()), # sentTime (this doesn't change) + int(time.time()), # lastActionTime + 0, + 'broadcastqueued', + 0, + 'sent', + 2, + TTL) + helper_sent.insert(t) - queues.UISignalQueue.put(( - 'displayNewSentMessage', ( - toAddress, '[Broadcast subscribers]', fromAddress, - subject, message, ackdata) - )) - queues.workerQueue.put(('sendbroadcast', '')) + shared.UISignalQueue.put(('displayNewSentMessage', ( + toAddress, '[Broadcast subscribers]', fromAddress, subject, message, ackdataForBroadcast))) + shared.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 = highlevelcrypto.calculateInventoryHash(ackPayload) - state.Inventory[inventoryHash] = ( - objectType, toStreamNumber, ackPayload, expiresTime, b'') - 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 shared.safeConfigGetBoolean(toAddress, 'dontsendack') and \ + not shared.safeConfigGetBoolean(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 - queues.UISignalQueue.put(( + shared.numberOfBroadcastsProcessed += 1 + shared.UISignalQueue.put(( 'updateNumberOfBroadcastsProcessed', 'no data')) - inventoryHash = highlevelcrypto.calculateInventoryHash(data) + inventoryHash = calculateInventoryHash(data) readPosition = 20 # bypass the nonce, time, and object type broadcastVersion, broadcastVersionLength = decodeVarint( 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 shared.MyECSubscriptionCryptorObjects.items(): 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 @@ -829,30 +654,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] @@ -861,41 +676,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 - calculatedRipe = highlevelcrypto.to_ripe( - sendersPubSigningKey, sendersPubEncryptionKey) + sha = hashlib.new('sha512') + sha.update(sendersPubSigningKey + sendersPubEncryptionKey) + 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 = highlevelcrypto.double_sha512( - encodeVarint(sendersAddressVersion) - + encodeVarint(sendersStream) + calculatedRipe - )[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: @@ -913,17 +719,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 = highlevelcrypto.double_sha512(signature)[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 (?,?,?,?,?)''', @@ -938,13 +742,22 @@ class objectProcessor(threading.Thread): # and send it. self.possibleNewPubkey(fromAddress) - try: - decodedMessage = helper_msgcoding.MsgDecode( - messageEncodingType, message) - except helper_msgcoding.MsgDecodeException: + fromAddress = encodeAddress( + sendersAddressVersion, sendersStream, calculatedRipe) + logger.debug('fromAddress: ' + fromAddress) + + if messageEncodingType == 2: + subject, body = self.decodeType2Message(message) + logger.info('Broadcast subject (first 100 characters): %s' % repr(subject)[:100]) + elif messageEncodingType == 1: + body = message + subject = '' + elif messageEncodingType == 0: + logger.info('messageEncodingType == 0. Doing nothing with the message.') return - subject = decodedMessage.subject - body = decodedMessage.body + else: + body = 'Unknown encoding type.\n\n' + repr(message) + subject = '' toAddress = '[Broadcast subscribers]' if helper_inbox.isMessageAlreadyInInbox(sigHash): @@ -954,97 +767,85 @@ class objectProcessor(threading.Thread): time.time()), body, 'inbox', messageEncodingType, 0, sigHash) helper_inbox.insert(t) - queues.UISignalQueue.put(('displayNewInboxMessage', ( + shared.UISignalQueue.put(('displayNewInboxMessage', ( inventoryHash, toAddress, fromAddress, subject, body))) # 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 shared.safeConfigGetBoolean('bitmessagesettings', 'apienabled'): + try: + apiNotifyPath = shared.config.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: - if address in state.neededPubkeys: - del state.neededPubkeys[address] + + # 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 shared.neededPubkeys: + del shared.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 = highlevelcrypto.double_sha512( - encodeVarint(addressVersion) + encodeVarint(streamNumber) - + ripe - )[32:] - if tag in state.neededPubkeys: - del state.neededPubkeys[tag] + tag = hashlib.sha512(hashlib.sha512(encodeVarint( + addressVersion) + encodeVarint(streamNumber) + ripe).digest()).digest()[32:] + if tag in shared.neededPubkeys: + del shared.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) - queues.workerQueue.put(('sendmessage', '')) + '''UPDATE sent SET status='doingmsgpow', retrynumber=0 WHERE toaddress=? AND (status='awaitingpubkey' or status='doingpubkeypow') AND folder='sent' ''', + address) + shared.workerQueue.put(('sendmessage', '')) - @staticmethod - def ackDataHasAValidHeader(ackData): - """Checking ackData with valid Header, not sending ackData when false""" - if len(ackData) < protocol.Header.size: - logger.info( - 'The length of ackData is unreasonably short. Not sending' - ' ackData.') + def ackDataHasAValidHeader(self, ackData): + if len(ackData) < shared.Header.size: + 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 = shared.Header.unpack(ackData[:shared.Header.size]) + if magic != 0xE9BEB4D9: logger.info('Ackdata magic bytes were wrong. Not sending ackData.') return False - payload = ackData[protocol.Header.size:] + payload = ackData[shared.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') @@ -1052,12 +853,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..9bf3f82a --- /dev/null +++ b/src/class_objectProcessorQueue.py @@ -0,0 +1,29 @@ +import shared + +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..ca5ed056 --- /dev/null +++ b/src/class_outgoingSynSender.py @@ -0,0 +1,262 @@ +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 helper_threading import * + +# 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() + + 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 shared.trustedPeer: + shared.knownNodesLock.acquire() + peer = shared.trustedPeer + shared.knownNodes[self.streamNumber][peer] = time.time() + shared.knownNodesLock.release() + else: + while not shared.shutdown: + shared.knownNodesLock.acquire() + peer, = random.sample(shared.knownNodes[self.streamNumber], 1) + priority = (183600 - (time.time() - shared.knownNodes[self.streamNumber][peer])) / 183600 # 2 days and 3 hours + shared.knownNodesLock.release() + if shared.config.get('bitmessagesettings', 'socksproxytype') != 'none': + if peer.host.find(".onion") == -1: + priority /= 10 # hidden services have 10x priority over plain net + 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 + time.sleep(0.01) # prevent CPU hogging if something is broken + try: + return peer + except NameError: + return shared.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 shared.safeConfigGetBoolean('bitmessagesettings', 'dontconnect') and not self._stopped: + self.stop.wait(2) + while shared.safeConfigGetBoolean('bitmessagesettings', 'sendoutgoingconnections') and not self._stopped: + self.name = "outgoingSynSender" + maximumConnections = 1 if shared.trustedPeer else 8 # maximum number of outgoing connections = 8 + while len(self.selfInitiatedConnections[self.streamNumber]) >= maximumConnections: + self.stop.wait(10) + if shared.shutdown: + break + random.seed() + peer = self._getPeer() + shared.alreadyAttemptedConnectionsListLock.acquire() + while peer in shared.alreadyAttemptedConnectionsList or peer.host in shared.connectedHostsList: + shared.alreadyAttemptedConnectionsListLock.release() + # print 'choosing new sample' + random.seed() + peer = self._getPeer() + self.stop.wait(1) + if shared.shutdown: + 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. + if (time.time() - shared.alreadyAttemptedConnectionsListResetTime) > 1800: + shared.alreadyAttemptedConnectionsList.clear() + shared.alreadyAttemptedConnectionsListResetTime = int( + time.time()) + shared.alreadyAttemptedConnectionsListLock.acquire() + shared.alreadyAttemptedConnectionsList[peer] = 0 + try: + shared.alreadyAttemptedConnectionsListLock.release() + except threading.ThreadError as e: + pass + if shared.shutdown: + break + self.name = "outgoingSynSender-" + peer.host.replace(":", ".") # log parser field separator + if peer.host.find(':') == -1: + address_family = socket.AF_INET + else: + 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. + """ + shared.knownNodesLock.acquire() + try: + del shared.knownNodes[self.streamNumber][peer] + except: + pass + shared.knownNodesLock.release() + logger.debug('deleting ' + str(peer) + ' from shared.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 shared.config.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 shared.config.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 = shared.config.get( + 'bitmessagesettings', 'sockshostname') + socksport = shared.config.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 shared.config.getboolean('bitmessagesettings', 'socksauthentication'): + socksusername = shared.config.get( + 'bitmessagesettings', 'socksusername') + sockspassword = shared.config.get( + 'bitmessagesettings', 'sockspassword') + self.sock.setproxy( + proxytype, sockshostname, socksport, rdns, socksusername, sockspassword) + else: + self.sock.setproxy( + proxytype, sockshostname, socksport, rdns) + elif shared.config.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 = shared.config.get( + 'bitmessagesettings', 'sockshostname') + socksport = shared.config.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 shared.config.getboolean('bitmessagesettings', 'socksauthentication'): + socksusername = shared.config.get( + 'bitmessagesettings', 'socksusername') + sockspassword = shared.config.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)) + someObjectsOfWhichThisRemoteNodeIsAlreadyAware = {} # This is not necessairly a complete list; we clear it from time to time to save memory. + sendDataThreadQueue = Queue.Queue() # Used to submit information to the send data thread for this connection. + + sd = sendDataThread(sendDataThreadQueue) + sd.setup(self.sock, peer.host, peer.port, self.streamNumber, + someObjectsOfWhichThisRemoteNodeIsAlreadyAware) + sd.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, + someObjectsOfWhichThisRemoteNodeIsAlreadyAware, + 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 shared.verbose >= 2: + logger.debug('Could NOT connect to ' + str(peer) + ' during outgoing attempt. ' + str(err)) + + deletedPeer = None + with shared.knownNodesLock: + """ + It is remotely possible that peer is no longer in shared.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 shared.knownNodes. This is unlikely because of the + alreadyAttemptedConnectionsList but because we clear that list once + every half hour, it can happen. + """ + if peer in shared.knownNodes[self.streamNumber]: + timeLastSeen = shared.knownNodes[self.streamNumber][peer] + if (int(time.time()) - timeLastSeen) > 172800 and len(shared.knownNodes[self.streamNumber]) > 1000: # for nodes older than 48 hours old if we have more than 1000 hosts in our list, delete from the shared.knownNodes data-structure. + del shared.knownNodes[self.streamNumber][peer] + deletedPeer = peer + if deletedPeer: + str ('deleting ' + str(peer) + ' from shared.knownNodes because it is more than 48 hours old and we could not connect to it.') + + except socks.Socks5AuthError as err: + shared.UISignalQueue.put(( + 'updateStatusBar', tr._translate( + "MainWindow", "SOCKS5 Authentication problem: %1").arg(str(err)))) + except socks.Socks5Error as err: + if err[0] in [3, 4, 5, 6]: + # this is a more bening "error": host unreachable, network unreachable, connection refused, TTL expired + logger.debug('SOCKS5 error. ' + str(err)) + else: + logger.error('SOCKS5 error. ' + str(err)) + except socks.Socks4Error as err: + logger.error('Socks4Error: ' + str(err)) + except socket.error as err: + if shared.config.get('bitmessagesettings', 'socksproxytype')[0:5] == 'SOCKS': + logger.error('Bitmessage MIGHT be having trouble connecting to the SOCKS server. ' + str(err)) + else: + if shared.verbose >= 1: + logger.debug('Could NOT connect to ' + str(peer) + 'during outgoing attempt. ' + str(err)) + + deletedPeer = None + with shared.knownNodesLock: + """ + It is remotely possible that peer is no longer in shared.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 shared.knownNodes. This is unlikely because of the + alreadyAttemptedConnectionsList but because we clear that list once + every half hour, it can happen. + """ + if peer in shared.knownNodes[self.streamNumber]: + timeLastSeen = shared.knownNodes[self.streamNumber][peer] + if (int(time.time()) - timeLastSeen) > 172800 and len(shared.knownNodes[self.streamNumber]) > 1000: # for nodes older than 48 hours old if we have more than 1000 hosts in our list, delete from the shared.knownNodes data-structure. + del shared.knownNodes[self.streamNumber][peer] + deletedPeer = peer + if deletedPeer: + logger.debug('deleting ' + str(peer) + ' from shared.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..9ee1d176 --- /dev/null +++ b/src/class_receiveDataThread.py @@ -0,0 +1,791 @@ +doTimingAttackMitigation = False + +import base64 +import errno +import math +import time +import threading +import shared +import hashlib +import os +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 class_objectHashHolder import objectHashHolder +from helper_generic import addDataPadding, isHostInPrivateIPRange +from helper_sql import sqlQuery +from debug import logger + +# 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, + someObjectsOfWhichThisRemoteNodeIsAlreadyAware, + selfInitiatedConnections, + sendDataThreadQueue, + objectHashHolderInstance): + + self.sock = sock + self.peer = shared.Peer(HOST, port) + self.name = "receiveData-" + self.peer.host.replace(":", ".") # ":" log parser field separator + self.streamNumber = streamNumber + self.objectsThatWeHaveYetToGetFromThisPeer = {} + self.selfInitiatedConnections = selfInitiatedConnections + self.sendDataThreadQueue = sendDataThreadQueue # used to send commands and data to the sendDataThread + shared.connectedHostsList[ + self.peer.host] = 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 self.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 + self.selfInitiatedConnections[streamNumber][self] = 0 + self.someObjectsOfWhichThisRemoteNodeIsAlreadyAware = someObjectsOfWhichThisRemoteNodeIsAlreadyAware + self.objectHashHolderInstance = objectHashHolderInstance + 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 True: + if shared.config.getint('bitmessagesettings', 'maxdownloadrate') == 0: + downloadRateLimitBytes = float("inf") + else: + downloadRateLimitBytes = shared.config.getint('bitmessagesettings', 'maxdownloadrate') * 1000 + with shared.receiveDataLock: + while shared.numberOfBytesReceivedLastSecond >= downloadRateLimitBytes: + if int(time.time()) == shared.lastTimeWeResetBytesReceived: + # If it's still the same second that it was last time then sleep. + time.sleep(0.3) + else: + # It's a new second. Let us clear the shared.numberOfBytesReceivedLastSecond. + shared.lastTimeWeResetBytesReceived = int(time.time()) + shared.numberOfBytesReceivedLastSecond = 0 + dataLen = len(self.data) + try: + if ((self.services & shared.NODE_SSL == shared.NODE_SSL) and + self.connectionIsOrWasFullyEstablished and + shared.haveSSL(not self.initiatedConnection)): + dataRecv = self.sslSock.recv(1024) + else: + dataRecv = self.sock.recv(1024) + self.data += dataRecv + shared.numberOfBytesReceived += len(dataRecv) # for the 'network status' UI tab. The UI clears this value whenever it updates. + shared.numberOfBytesReceivedLastSecond += len(dataRecv) # for the download rate limit + except socket.timeout: + logger.error ('Timeout occurred waiting for data from ' + str(self.peer) + '. Closing receiveData thread. (ID: ' + str(id(self)) + ')') + break + except Exception as err: + if (sys.platform == 'win32' and err.errno in ([2, 10035])) or (sys.platform != 'win32' and err.errno == errno.EWOULDBLOCK): + select.select([self.sslSock], [], []) + continue + logger.error('sock.recv error. Closing receiveData thread (' + str(self.peer) + ', Thread ID: ' + str(id(self)) + ').' + str(err.errno) + "/" + str(err)) + break + # print 'Received', repr(self.data) + if len(self.data) == dataLen: # If self.sock.recv returned no data: + logger.debug('Connection to ' + str(self.peer) + ' closed. Closing receiveData thread. (ID: ' + str(id(self)) + ')') + break + else: + self.processData() + + try: + del self.selfInitiatedConnections[self.streamNumber][self] + 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.peer.host] + except Exception as err: + logger.error('Could not delete ' + str(self.peer.host) + ' from shared.connectedHostsList.' + str(err)) + + try: + del shared.numberOfObjectsThatWeHaveYetToGetPerPeer[ + self.peer] + except: + pass + shared.UISignalQueue.put(('updateNetworkStatusTab', 'no data')) + 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(len(shared.knownNodes[self.streamNumber]) + 2, 20)) * (0.2 + objectHashHolder.size/2) + # +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 processData(self): + if len(self.data) < shared.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 = shared.Header.unpack(self.data[:shared.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 + shared.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 + shared.Header.size: # check if the whole message has arrived yet. + return + payload = self.data[shared.Header.size:payloadLength + shared.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 + shared.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 shared.knownNodesLock: + shared.knownNodes[self.streamNumber][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 + 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 + shared.Header.size:] # take this message out and then process the next message + + if self.data == '': # if there are no more messages + while len(self.objectsThatWeHaveYetToGetFromThisPeer) > 0: + shared.numberOfInventoryLookupsPerformed += 1 + objectHash, = random.sample( + self.objectsThatWeHaveYetToGetFromThisPeer, 1) + if objectHash in shared.inventory: + logger.debug('Inventory already has object listed in inv message.') + del self.objectsThatWeHaveYetToGetFromThisPeer[objectHash] + else: + # We don't have the object in our inventory. Let's request it. + self.sendgetdata(objectHash) + del self.objectsThatWeHaveYetToGetFromThisPeer[ + objectHash] # It is possible that the remote node might not respond with the object. In that case, we'll very likely get it from someone else anyway. + if len(self.objectsThatWeHaveYetToGetFromThisPeer) == 0: + logger.debug('(concerning' + str(self.peer) + ') number of objectsThatWeHaveYetToGetFromThisPeer is now 0') + try: + del shared.numberOfObjectsThatWeHaveYetToGetPerPeer[ + self.peer] # this data structure is maintained so that we can keep track of how many total objects, across all connections, are currently outstanding. If it goes too high it can indicate that we are under attack by multiple nodes working together. + except: + pass + break + if len(self.objectsThatWeHaveYetToGetFromThisPeer) == 0: + # We had objectsThatWeHaveYetToGetFromThisPeer but the loop ran, they were all in our inventory, and now we don't have any to get anymore. + logger.debug('(concerning' + str(self.peer) + ') number of objectsThatWeHaveYetToGetFromThisPeer is now 0') + try: + del shared.numberOfObjectsThatWeHaveYetToGetPerPeer[ + self.peer] # this data structure is maintained so that we can keep track of how many total objects, across all connections, are currently outstanding. If it goes too high it can indicate that we are under attack by multiple nodes working together. + except: + pass + if len(self.objectsThatWeHaveYetToGetFromThisPeer) > 0: + logger.debug('(concerning' + str(self.peer) + ') number of objectsThatWeHaveYetToGetFromThisPeer is now ' + str(len(self.objectsThatWeHaveYetToGetFromThisPeer))) + + shared.numberOfObjectsThatWeHaveYetToGetPerPeer[self.peer] = len( + self.objectsThatWeHaveYetToGetFromThisPeer) # this data structure is maintained so that we can keep track of how many total objects, across all connections, are currently outstanding. If it goes too high it can indicate that we are under attack by multiple nodes working together. + self.processData() + + + def sendpong(self): + logger.debug('Sending pong') + self.sendDataThreadQueue.put((0, 'sendRawData', shared.CreatePacket('pong'))) + + + 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 connectionFullyEstablished(self): + if self.connectionIsOrWasFullyEstablished: + # there is no reason to run this function a second time + return + self.connectionIsOrWasFullyEstablished = True + + self.sslSock = self.sock + if ((self.services & shared.NODE_SSL == shared.NODE_SSL) and + shared.haveSSL(not self.initiatedConnection)): + logger.debug("Initialising TLS") + self.sslSock = ssl.wrap_socket(self.sock, keyfile = os.path.join(shared.codePath(), 'sslkeys', 'key.pem'), certfile = os.path.join(shared.codePath(), 'sslkeys', 'cert.pem'), server_side = not self.initiatedConnection, ssl_version=ssl.PROTOCOL_TLSv1, do_handshake_on_connect=False, ciphers='AECDH-AES256-SHA') + if hasattr(self.sslSock, "context"): + self.sslSock.context.set_ecdh_curve("secp256k1") + while True: + try: + self.sslSock.do_handshake() + break + except ssl.SSLError as e: + if e.errno == 2: + select.select([self.sslSock], [self.sslSock], []) + else: + break + except: + break + # 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 + shared.UISignalQueue.put(('setStatusIcon', 'green')) + self.sock.settimeout( + 600) # We'll send out a pong every 5 minutes to make sure the connection stays alive if there has been no other traffic to send lately. + shared.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(shared.sendDataQueues)) + "\n" + \ + 'broadcasting addr from within connectionFullyEstablished function.') + + # Let all of our peers know about this new node. + dataToSend = (int(time.time()), self.streamNumber, 1, self.peer.host, self.remoteNodeIncomingPort) + shared.broadcastToSendDataQueues(( + self.streamNumber, 'advertisepeer', dataToSend)) + + self.sendaddr() # This is one large addr message to this one peer. + if not self.initiatedConnection and len(shared.connectedHostsList) > 200: + logger.info ('We are connected to too many people. Closing connection.') + + self.sendDataThreadQueue.put((0, 'shutdown','no data')) + return + self.sendBigInv() + + def sendBigInv(self): + # Select all hashes for objects in this stream. + bigInvList = {} + for hash in shared.inventory.unexpired_hashes_by_stream(self.streamNumber): + if hash not in self.someObjectsOfWhichThisRemoteNodeIsAlreadyAware and 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', shared.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 shared.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) + + """ + 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): + totalNumberOfobjectsThatWeHaveYetToGetFromAllPeers = 0 # this counts duplicates separately because they take up memory + if len(shared.numberOfObjectsThatWeHaveYetToGetPerPeer) > 0: + for key, value in shared.numberOfObjectsThatWeHaveYetToGetPerPeer.items(): + totalNumberOfobjectsThatWeHaveYetToGetFromAllPeers += value + logger.debug('number of keys(hosts) in shared.numberOfObjectsThatWeHaveYetToGetPerPeer: ' + str(len(shared.numberOfObjectsThatWeHaveYetToGetPerPeer)) + "\n" + \ + 'totalNumberOfobjectsThatWeHaveYetToGetFromAllPeers = ' + str(totalNumberOfobjectsThatWeHaveYetToGetFromAllPeers)) + + 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 + if numberOfItemsInInv == 1: # we'll just request this data from the person who advertised the object. + if totalNumberOfobjectsThatWeHaveYetToGetFromAllPeers > 200000 and len(self.objectsThatWeHaveYetToGetFromThisPeer) > 1000 and shared.trustedPeer == None: # inv flooding attack mitigation + logger.debug('We already have ' + str(totalNumberOfobjectsThatWeHaveYetToGetFromAllPeers) + ' items yet to retrieve from peers and over 1000 from this node in particular. Ignoring this inv message.') + return + self.someObjectsOfWhichThisRemoteNodeIsAlreadyAware[ + data[lengthOfVarint:32 + lengthOfVarint]] = 0 + shared.numberOfInventoryLookupsPerformed += 1 + if data[lengthOfVarint:32 + lengthOfVarint] in shared.inventory: + logger.debug('Inventory has inventory item already.') + else: + self.sendgetdata(data[lengthOfVarint:32 + lengthOfVarint]) + else: + # There are many items listed in this inv message. Let us create a + # 'set' of objects we are aware of and a set of objects in this inv + # message so that we can diff one from the other cheaply. + startTime = time.time() + advertisedSet = set() + for i in range(numberOfItemsInInv): + advertisedSet.add(data[lengthOfVarint + (32 * i):32 + lengthOfVarint + (32 * i)]) + objectsNewToMe = advertisedSet - shared.inventory.hashes_by_stream(self.streamNumber) + logger.info('inv message lists %s objects. Of those %s are new to me. It took %s seconds to figure that out.', numberOfItemsInInv, len(objectsNewToMe), time.time()-startTime) + for item in objectsNewToMe: + if totalNumberOfobjectsThatWeHaveYetToGetFromAllPeers > 200000 and len(self.objectsThatWeHaveYetToGetFromThisPeer) > 1000 and shared.trustedPeer == None: # inv flooding attack mitigation + logger.debug('We already have ' + str(totalNumberOfobjectsThatWeHaveYetToGetFromAllPeers) + ' items yet to retrieve from peers and over ' + str(len(self.objectsThatWeHaveYetToGetFromThisPeer)), ' from this node in particular. Ignoring the rest of this inv message.') + break + self.someObjectsOfWhichThisRemoteNodeIsAlreadyAware[item] = 0 # helps us keep from sending inv messages to peers that already know about the objects listed therein + self.objectsThatWeHaveYetToGetFromThisPeer[item] = 0 # upon finishing dealing with an incoming message, the receiveDataThread will request a random object of from peer out of this data structure. This way if we get multiple inv messages from multiple peers which list mostly the same objects, we will make getdata requests for different random objects from the various peers. + if len(self.objectsThatWeHaveYetToGetFromThisPeer) > 0: + shared.numberOfObjectsThatWeHaveYetToGetPerPeer[ + self.peer] = len(self.objectsThatWeHaveYetToGetFromThisPeer) + + # Send a getdata message to our peer to request the object with the given + # hash + def sendgetdata(self, hash): + logger.debug('sending getdata to retrieve object with hash: ' + hexlify(hash)) + payload = '\x01' + hash + self.sendDataThreadQueue.put((0, 'sendRawData', shared.CreatePacket('getdata', payload))) + + + # 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)) + + shared.numberOfInventoryLookupsPerformed += 1 + shared.inventoryLock.acquire() + if self.objectHashHolderInstance.hasHash(hash): + shared.inventoryLock.release() + self.antiIntersectionDelay() + else: + shared.inventoryLock.release() + if hash in shared.inventory: + self.sendObject(shared.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, payload): + logger.debug('sending an object.') + self.sendDataThreadQueue.put((0, 'sendRawData', shared.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 != self.streamNumber and recaddrStream != (self.streamNumber * 2) and recaddrStream != ((self.streamNumber * 2) + 1): # if the embedded stream number is not in my stream or either of my child 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 shared.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 shared.knownNodesLock: + shared.knownNodes[recaddrStream] = {} + peerFromAddrMessage = shared.Peer(hostStandardFormat, recaddrPort) + if peerFromAddrMessage not in shared.knownNodes[recaddrStream]: + if len(shared.knownNodes[recaddrStream]) < 20000 and timeSomeoneElseReceivedMessageFromThisNode > (int(time.time()) - 10800) and timeSomeoneElseReceivedMessageFromThisNode < (int(time.time()) + 10800): # If we have more than 20000 nodes in our list already then just forget about adding more. Also, make sure that the time that someone else received a message from this node is within three hours from now. + with shared.knownNodesLock: + shared.knownNodes[recaddrStream][peerFromAddrMessage] = timeSomeoneElseReceivedMessageFromThisNode + logger.debug('added new node ' + str(peerFromAddrMessage) + ' to knownNodes in stream ' + str(recaddrStream)) + + shared.needToWriteKnownNodesToDisk = True + hostDetails = ( + timeSomeoneElseReceivedMessageFromThisNode, + recaddrStream, recaddrServices, hostStandardFormat, recaddrPort) + shared.broadcastToSendDataQueues(( + self.streamNumber, 'advertisepeer', hostDetails)) + else: + timeLastReceivedMessageFromThisNode = shared.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 shared.knownNodesLock: + shared.knownNodes[recaddrStream][peerFromAddrMessage] = timeSomeoneElseReceivedMessageFromThisNode + + logger.debug('knownNodes currently has ' + str(len(shared.knownNodes[self.streamNumber])) + ' nodes for this 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): + addrsInMyStream = {} + addrsInChildStreamLeft = {} + addrsInChildStreamRight = {} + # print 'knownNodes', shared.knownNodes + + # We are going to share a maximum number of 1000 addrs with our peer. + # 500 from this stream, 250 from the left child stream, and 250 from + # the right child stream. + with shared.knownNodesLock: + if len(shared.knownNodes[self.streamNumber]) > 0: + ownPosition = random.randint(0, 499) + sentOwn = False + for i in range(500): + # if current connection is over a proxy, sent our own onion address at a random position + if ownPosition == i and ".onion" in shared.config.get("bitmessagesettings", "onionhostname") and \ + hasattr(self.sock, "getproxytype") and self.sock.getproxytype() != "none" and not sentOwn: + peer = shared.Peer(shared.config.get("bitmessagesettings", "onionhostname"), shared.config.getint("bitmessagesettings", "onionport")) + else: + # still may contain own onion address, but we don't change it + peer, = random.sample(shared.knownNodes[self.streamNumber], 1) + if isHostInPrivateIPRange(peer.host): + continue + if peer.host == shared.config.get("bitmessagesettings", "onionhostname") and peer.port == shared.config.getint("bitmessagesettings", "onionport") : + sentOwn = True + addrsInMyStream[peer] = shared.knownNodes[ + self.streamNumber][peer] + if len(shared.knownNodes[self.streamNumber * 2]) > 0: + for i in range(250): + peer, = random.sample(shared.knownNodes[ + self.streamNumber * 2], 1) + if isHostInPrivateIPRange(peer.host): + continue + addrsInChildStreamLeft[peer] = shared.knownNodes[ + self.streamNumber * 2][peer] + if len(shared.knownNodes[(self.streamNumber * 2) + 1]) > 0: + for i in range(250): + peer, = random.sample(shared.knownNodes[ + (self.streamNumber * 2) + 1], 1) + if isHostInPrivateIPRange(peer.host): + continue + addrsInChildStreamRight[peer] = shared.knownNodes[ + (self.streamNumber * 2) + 1][peer] + numberOfAddressesInAddrMessage = 0 + payload = '' + # print 'addrsInMyStream.items()', addrsInMyStream.items() + for (HOST, PORT), value in addrsInMyStream.items(): + timeLastReceivedMessageFromThisNode = value + if timeLastReceivedMessageFromThisNode > (int(time.time()) - shared.maximumAgeOfNodesThatIAdvertiseToOthers): # If it is younger than 3 hours old.. + numberOfAddressesInAddrMessage += 1 + payload += pack( + '>Q', timeLastReceivedMessageFromThisNode) # 64-bit time + payload += pack('>I', self.streamNumber) + payload += pack( + '>q', 1) # service bit flags offered by this node + payload += shared.encodeHost(HOST) + payload += pack('>H', PORT) # remote port + for (HOST, PORT), value in addrsInChildStreamLeft.items(): + timeLastReceivedMessageFromThisNode = value + if timeLastReceivedMessageFromThisNode > (int(time.time()) - shared.maximumAgeOfNodesThatIAdvertiseToOthers): # If it is younger than 3 hours old.. + numberOfAddressesInAddrMessage += 1 + payload += pack( + '>Q', timeLastReceivedMessageFromThisNode) # 64-bit time + payload += pack('>I', self.streamNumber * 2) + payload += pack( + '>q', 1) # service bit flags offered by this node + payload += shared.encodeHost(HOST) + payload += pack('>H', PORT) # remote port + for (HOST, PORT), value in addrsInChildStreamRight.items(): + timeLastReceivedMessageFromThisNode = value + if timeLastReceivedMessageFromThisNode > (int(time.time()) - shared.maximumAgeOfNodesThatIAdvertiseToOthers): # If it is younger than 3 hours old.. + numberOfAddressesInAddrMessage += 1 + payload += pack( + '>Q', timeLastReceivedMessageFromThisNode) # 64-bit time + payload += pack('>I', (self.streamNumber * 2) + 1) + payload += pack( + '>q', 1) # service bit flags offered by this node + payload += shared.encodeHost(HOST) + payload += pack('>H', PORT) # remote port + + payload = encodeVarint(numberOfAddressesInAddrMessage) + payload + self.sendDataThreadQueue.put((0, 'sendRawData', shared.CreatePacket('addr', payload))) + + + # 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]) + if self.remoteProtocolVersion < 3: + self.sendDataThreadQueue.put((0, 'shutdown','no data')) + logger.debug ('Closing connection to old protocol version ' + str(self.remoteProtocolVersion) + ' node: ' + str(self.peer)) + return + timestamp, = unpack('>Q', data[12:20]) + timeOffset = timestamp - int(time.time()) + if timeOffset > 3600: + self.sendDataThreadQueue.put((0, 'sendRawData', shared.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, timeOffset)) + time.sleep(2) + self.sendDataThreadQueue.put((0, 'shutdown','no data')) + return + if timeOffset < -3600: + self.sendDataThreadQueue.put((0, 'sendRawData', shared.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, timeOffset)) + time.sleep(2) + self.sendDataThreadQueue.put((0, 'shutdown','no data')) + return + 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 + useragent = data[readPosition:readPosition + useragentLength] + + # version check + try: + userAgentName, userAgentVersion = useragent[1:-1].split(":", 2) + except: + userAgentName = useragent + userAgentVersion = "0.0.0" + if userAgentName == "PyBitmessage": + myVersion = [int(n) for n in shared.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): + shared.UISignalQueue.put(('newVersionAvailable', remoteVersion)) + except: + pass + + readPosition += useragentLength + numberOfStreamsInVersionMessage, lengthOfNumberOfStreamsInVersionMessage = decodeVarint( + data[readPosition:]) + readPosition += lengthOfNumberOfStreamsInVersionMessage + self.streamNumber, lengthOfRemoteStreamNumber = decodeVarint( + data[readPosition:]) + logger.debug('Remote node useragent: ' + useragent + ' stream number:' + str(self.streamNumber) + ' time offset: ' + str(timeOffset) + ' seconds.') + + if self.streamNumber != 1: + self.sendDataThreadQueue.put((0, 'shutdown','no data')) + logger.debug ('Closed connection to ' + str(self.peer) + ' because they are interested in stream ' + str(self.streamNumber) + '.') + return + shared.connectedHostsList[ + self.peer.host] = 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. + # If this was an incoming connection, then the sendDataThread + # doesn't know the stream. We have to set it. + if not self.initiatedConnection: + self.sendDataThreadQueue.put((0, 'setStreamNumber', self.streamNumber)) + if data[72:80] == shared.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 shared.knownNodesLock: + shared.knownNodes[self.streamNumber][shared.Peer(self.peer.host, self.remoteNodeIncomingPort)] = int(time.time()) + if not self.initiatedConnection: + shared.knownNodes[self.streamNumber][shared.Peer(self.peer.host, self.remoteNodeIncomingPort)] -= 162000 # penalise inbound, 2 days minus 3 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', shared.assembleVersionMessage( + self.peer.host, self.peer.port, self.streamNumber, not self.initiatedConnection))) + + + # Sends a verack message + def sendverack(self): + logger.debug('Sending verack') + self.sendDataThreadQueue.put((0, 'sendRawData', shared.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..9c048d32 --- /dev/null +++ b/src/class_sendDataThread.py @@ -0,0 +1,193 @@ +import time +import threading +import shared +import Queue +from struct import unpack, pack +import hashlib +import random +import sys +import socket + +from helper_generic import addDataPadding +from class_objectHashHolder import * +from addresses import * +from debug import logger + +# 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 + shared.sendDataQueues.append(self.sendDataThreadQueue) + self.data = '' + self.objectHashHolderInstance = objectHashHolder(self.sendDataThreadQueue) + self.objectHashHolderInstance.start() + self.connectionIsOrWasFullyEstablished = False + + + def setup( + self, + sock, + HOST, + PORT, + streamNumber, + someObjectsOfWhichThisRemoteNodeIsAlreadyAware): + self.sock = sock + self.peer = shared.Peer(HOST, PORT) + self.name = "sendData-" + self.peer.host.replace(":", ".") # log parser field separator + self.streamNumber = streamNumber + self.services = 0 + 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. + self.someObjectsOfWhichThisRemoteNodeIsAlreadyAware = someObjectsOfWhichThisRemoteNodeIsAlreadyAware + if self.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 = shared.assembleVersionMessage( + self.peer.host, self.peer.port, self.streamNumber, 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): + if shared.config.getint('bitmessagesettings', 'maxuploadrate') == 0: + uploadRateLimitBytes = 999999999 # float("inf") doesn't work + else: + uploadRateLimitBytes = shared.config.getint('bitmessagesettings', 'maxuploadrate') * 1000 + with shared.sendDataLock: + while data: + while shared.numberOfBytesSentLastSecond >= uploadRateLimitBytes: + if int(time.time()) == shared.lastTimeWeResetBytesSent: + time.sleep(0.3) + else: + # It's a new second. Let us clear the shared.numberOfBytesSentLastSecond + shared.lastTimeWeResetBytesSent = int(time.time()) + shared.numberOfBytesSentLastSecond = 0 + # If the user raises or lowers the uploadRateLimit then we should make use of + # the new setting. If we are hitting the limit then we'll check here about + # once per second. + if shared.config.getint('bitmessagesettings', 'maxuploadrate') == 0: + uploadRateLimitBytes = 999999999 # float("inf") doesn't work + else: + uploadRateLimitBytes = shared.config.getint('bitmessagesettings', 'maxuploadrate') * 1000 + if ((self.services & shared.NODE_SSL == shared.NODE_SSL) and + self.connectionIsOrWasFullyEstablished and + shared.haveSSL(not self.initiatedConnection)): + amountSent = self.sslSock.send(data[:1000]) + else: + amountSent = self.sock.send(data[:1000]) + shared.numberOfBytesSent += amountSent # used for the 'network status' tab in the UI + shared.numberOfBytesSentLastSecond += amountSent + self.lastTimeISentData = int(time.time()) + data = data[amountSent:] + + + def run(self): + logger.debug('sendDataThread starting. ID: ' + str(id(self)) + '. Number of queues in sendDataQueues: ' + str(len(shared.sendDataQueues))) + while True: + deststream, command, data = self.sendDataThreadQueue.get() + + if deststream == self.streamNumber or deststream == 0: + 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 in the sendData thread (ID: ' + str(id(self)) + ') to ' + str(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 += shared.encodeHost(host) + payload += pack('>H', port) + + payload = encodeVarint(numberOfAddressesInAddrMessage) + payload + packet = shared.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: + if hash not in self.someObjectsOfWhichThisRemoteNodeIsAlreadyAware: + payload += hash + if payload != '': + payload = encodeVarint(len(payload)/32) + payload + packet = shared.CreatePacket('inv', payload) + try: + self.sendBytes(packet) + except: + logger.error('sendinv: self.sock.sendall failed') + break + elif command == 'pong': + self.someObjectsOfWhichThisRemoteNodeIsAlreadyAware.clear() # To save memory, let us clear this data structure from time to time. As its function is to help us keep from sending inv messages to peers which sent us the same inv message mere seconds earlier, it will be fine to clear this data structure from time to time. + if self.lastTimeISentData < (int(time.time()) - 298): + # Send out a pong message to keep the connection alive. + logger.debug('Sending pong to ' + str(self.peer) + ' to keep connection alive.') + packet = shared.CreatePacket('pong') + try: + self.sendBytes(packet) + except: + logger.error('send pong failed') + break + elif command == 'sendRawData': + try: + self.sendBytes(data) + except: + logger.error('Sending of data to ' + str(self.peer) + ' failed. sendDataThread thread ' + str(self) + ' ending now.') + break + elif command == 'connectionIsOrWasFullyEstablished': + self.connectionIsOrWasFullyEstablished = True + self.services, self.sslSock = data + else: + logger.error('sendDataThread ID: ' + str(id(self)) + ' ignoring command ' + command + ' because the thread is not in stream' + str(deststream)) + + try: + self.sock.shutdown(socket.SHUT_RDWR) + self.sock.close() + except: + pass + shared.sendDataQueues.remove(self.sendDataThreadQueue) + logger.info('sendDataThread ending. ID: ' + str(id(self)) + '. Number of queues in sendDataQueues: ' + str(len(shared.sendDataQueues))) + self.objectHashHolderInstance.close() diff --git a/src/class_singleCleaner.py b/src/class_singleCleaner.py index 06153dcf..b9e8b1a1 100644 --- a/src/class_singleCleaner.py +++ b/src/class_singleCleaner.py @@ -1,187 +1,135 @@ -""" -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. +import threading +import shared +import time +import sys +import os +import pickle +import tr#anslate +from helper_sql import * +from helper_threading import * +from debug import logger + +""" +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) +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) +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...) +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 time -import queues -import state -from bmconfigparser import config -from helper_sql import sqlExecute, sqlQuery -from network import connectionpool, knownnodes, StoppableThread -from tr import _translate +class singleCleaner(threading.Thread, StoppableThread): + def __init__(self): + threading.Thread.__init__(self, name="singleCleaner") + self.initStop() -#: 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" - cycleLength = 300 - expireDiscoveredPeers = 300 - - def run(self): # pylint: disable=too-many-branches - gc.disable() + def run(self): 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') - - while state.shutdown == 0: - self.stop.wait(self.cycleLength) - queues.UISignalQueue.put(( - 'updateStatusBar', - 'Doing housekeeping (Flushing inventory in memory to disk...)' - )) - state.Inventory.flush() - queues.UISignalQueue.put(('updateStatusBar', '')) + shared.maximumLengthOfTimeToBotherResendingMessages = (float(shared.config.get('bitmessagesettings', 'stopresendingafterxdays')) * 24 * 60 * 60) + (float(shared.config.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') + while shared.shutdown == 0: + shared.UISignalQueue.put(( + 'updateStatusBar', 'Doing housekeeping (Flushing inventory in memory to disk...)')) + shared.inventory.flush() + shared.UISignalQueue.put(('updateStatusBar', '')) + + shared.broadcastToSendDataQueues(( + 0, 'pong', 'no data')) # commands the sendData threads to send out a pong message if they haven't sent anything else in the last five minutes. The socket timeout-time is 10 minutes. # If we are running as a daemon then we are going to fill up the UI # queue which will never be handled by a UI. We should clear it to # save memory. - # FIXME redundant? - if state.thisapp.daemon or not state.enableGUI: - queues.UISignalQueue.queue.clear() - - tick = int(time.time()) - if timeWeLastClearedInventoryAndPubkeysTables < tick - 7380: - timeWeLastClearedInventoryAndPubkeysTables = tick - state.Inventory.clean() - queues.workerQueue.put(('sendOnionPeerObj', '')) + if shared.safeConfigGetBoolean('bitmessagesettings', 'daemon'): + shared.UISignalQueue.queue.clear() + if timeWeLastClearedInventoryAndPubkeysTables < int(time.time()) - 7380: + timeWeLastClearedInventoryAndPubkeysTables = int(time.time()) + shared.inventory.clean() # 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(connectionpool.pool) - 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()) + toDelete = [] + shared.knownNodesLock.acquire() + for stream in shared.knownNodes: + for node in shared.knownNodes[stream].keys(): + if now - shared.knownNodes[stream][node] > 259200: # 3 days + shared.needToWriteKownNodesToDisk = True + del shared.knownNodes[stream][node] + shared.knownNodesLock.release() - # inv/object tracking - for connection in connectionpool.pool.connections(): - connection.clean() - - # discovery tracking - exp = time.time() - singleCleaner.expireDiscoveredPeers - reaper = (k for k, v in state.discoveredPeers.items() if v < exp) - for k in reaper: + # Let us write out the knowNodes to disk if there is anything new to write out. + if shared.needToWriteKnownNodesToDisk: + shared.knownNodesLock.acquire() + output = open(shared.appdata + 'knownnodes.dat', 'wb') try: - del state.discoveredPeers[k] - except KeyError: - pass - # ..todo:: cleanup pending upload / download + pickle.dump(shared.knownNodes, output) + output.close() + except Exception as err: + if "Errno 28" in str(err): + logger.fatal('(while receiveDataThread shared.needToWriteKnownNodesToDisk) Alert: Your disk or data storage volume is full. ') + shared.UISignalQueue.put(('alert', (tr._translate("MainWindow", "Disk full"), tr._translate("MainWindow", 'Alert: Your disk or data storage volume is full. Bitmessage will now exit.'), True))) + if shared.daemon: + os._exit(0) + shared.knownNodesLock.release() + shared.needToWriteKnownNodesToDisk = False + self.stop.wait(300) - 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) +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 shared.neededPubkeys[ + address] # We need to take this entry out of the shared.neededPubkeys structure because the shared.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 = ? AND folder = 'sent'", address) - queues.workerQueue.put(('sendmessage', '')) + shared.UISignalQueue.put(( + 'updateStatusBar', 'Doing work necessary to again attempt to request a public key...')) + sqlExecute( + '''UPDATE sent SET status='msgqueued' WHERE toaddress=?''', + address) + shared.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 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) + shared.workerQueue.put(('sendmessage', '')) + shared.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..0a093d0e --- /dev/null +++ b/src/class_singleListener.py @@ -0,0 +1,148 @@ +import threading +import shared +import socket +from class_sendDataThread import * +from class_receiveDataThread import * +import helper_bootstrap +from helper_threading import * +import errno +import re + +# 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 shared.safeConfigGetBoolean('bitmessagesettings', 'sockslisten') and ".onion" in shared.config.get('bitmessagesettings', 'onionhostname'): + HOST = shared.config.get('bitmessagesettings', 'onionbindip') + PORT = shared.config.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', shared.config.get('bitmessagesettings', 'onionbindip')): + try: + s.connect((ip, shared.config.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 shared.trustedPeer: + return + + while shared.safeConfigGetBoolean('bitmessagesettings', 'dontconnect') and shared.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 shared.config.get('bitmessagesettings', 'socksproxytype')[0:5] == 'SOCKS' and \ + (not shared.config.getboolean('bitmessagesettings', 'sockslisten') and \ + ".onion" not in shared.config.get('bitmessagesettings', 'onionhostname')) and \ + shared.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, e: + if (isinstance(e.args, tuple) and + e.args[0] in (errno.EAFNOSUPPORT, + errno.EPFNOSUPPORT, + errno.EADDRNOTAVAIL, + errno.ENOPROTOOPT)): + 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 shared.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 shared.config.get('bitmessagesettings', 'socksproxytype')[0:5] == 'SOCKS' and not shared.config.getboolean('bitmessagesettings', 'sockslisten') and ".onion" not in shared.config.get('bitmessagesettings', 'onionhostname') and shared.shutdown == 0: + self.stop.wait(10) + while len(shared.connectedHostsList) > 220 and shared.shutdown == 0: + logger.info('We are connected to too many people. Not accepting further incoming connections for ten seconds.') + + self.stop.wait(10) + + while shared.shutdown == 0: + socketObject, sockaddr = sock.accept() + (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 shared.config.get('bitmessagesettings', 'onionhostname') or not shared.checkSocksIP(HOST)): + socketObject.close() + logger.info('We are already connected to ' + str(HOST) + '. Ignoring connection.') + else: + break + + someObjectsOfWhichThisRemoteNodeIsAlreadyAware = {} # This is not necessairly a complete list; we clear it from time to time to save memory. + sendDataThreadQueue = Queue.Queue() # Used to submit information to the send data thread for this connection. + socketObject.settimeout(20) + + sd = sendDataThread(sendDataThreadQueue) + sd.setup( + socketObject, HOST, PORT, -1, someObjectsOfWhichThisRemoteNodeIsAlreadyAware) + sd.start() + + rd = receiveDataThread() + rd.daemon = True # close the main program even if there are threads left + rd.setup( + socketObject, HOST, PORT, -1, someObjectsOfWhichThisRemoteNodeIsAlreadyAware, self.selfInitiatedConnections, sendDataThreadQueue, sd.objectHashHolderInstance) + rd.start() + + logger.info('connected to ' + HOST + ' during INCOMING request.') + diff --git a/src/class_singleWorker.py b/src/class_singleWorker.py index f79d9240..f5ac16aa 100644 --- a/src/class_singleWorker.py +++ b/src/class_singleWorker.py @@ -1,617 +1,425 @@ -""" -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 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 -import highlevelcrypto -import l10n -import proofofwork -import protocol -import queues +import threading import shared -import state +import time +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 proofofwork +import sys import tr -from addresses import decodeAddress, decodeVarint, encodeVarint -from bmconfigparser import config -from helper_sql import sqlExecute, sqlQuery -from network import knownnodes, StoppableThread, invQueue -from six.moves import configparser, queue +from debug import logger +from helper_sql import * +import helper_inbox +from helper_generic import addDataPadding +from helper_threading import * +import l10n +from protocol import * +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') - proofofwork.init() + # QThread.__init__(self, parent) + threading.Thread.__init__(self, name="singleWorker") + self.initStop() 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') + shared.workerQueue.put(("stopThread", "data")) + 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) - if state.shutdown > 0: - return - + # Initialize the neededPubkeys dictionary. queryreturn = sqlQuery( - '''SELECT DISTINCT toaddress FROM sent''' - ''' WHERE (status='awaitingpubkey' AND folder='sent')''') - for toAddress, in queryreturn: - toAddressVersionNumber, toStreamNumber, toRipe = \ - decodeAddress(toAddress)[1:] - if toAddressVersionNumber <= 3: - state.neededPubkeys[toAddress] = 0 + '''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 : + shared.neededPubkeys[toAddress] = 0 elif toAddressVersionNumber >= 4: - doubleHashOfAddressData = highlevelcrypto.double_sha512( - encodeVarint(toAddressVersionNumber) - + encodeVarint(toStreamNumber) + toRipe - ) - # 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)) - ) + shared.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' OR status='doingmsgpow')''') 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: - # 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] + self.stop.wait( + 10) # give some time for the GUI to start before we start on existing POW tasks. - # For the case if user deleted knownnodes - # but is still having onionpeer objects in inventory - if not knownnodes.knownNodesActual: - for item in state.Inventory.by_type_and_tag(protocol.OBJECT_ONIONPEER): - queues.objectProcessorQueue.put(( - protocol.OBJECT_ONIONPEER, item.payload - )) - # FIXME: should also delete from inventory + if shared.shutdown == 0: + queryreturn = sqlQuery( + '''SELECT DISTINCT toaddress FROM sent WHERE (status='doingpubkeypow' AND folder='sent')''') + for row in queryreturn: + toaddress, = row + logger.debug("c: %s", shared.shutdown) + self.requestPubKey(toaddress) + # just in case there are any pending tasks for msg + # messages that have yet to be sent. + self.sendMsg() + # just in case there are any tasks for Broadcasts + # that have yet to be sent. + self.sendBroadcast() - # 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', '')) - - while state.shutdown == 0: + while shared.shutdown == 0: self.busy = 0 - command, data = queues.workerQueue.get() + command, data = shared.workerQueue.get() self.busy = 1 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") - 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...") + shared.workerQueue.task_done() - def _getKeysForAddress(self, address): - try: - privSigningKeyBase58 = config.get(address, 'privsigningkey') - privEncryptionKeyBase58 = config.get(address, 'privencryptionkey') - except (configparser.NoSectionError, configparser.NoOptionError): - self.logger.error( - 'Could not read or decode privkey for address %s', address) - raise ValueError - - privSigningKeyHex = hexlify(highlevelcrypto.decodeWalletImportFormat( - privSigningKeyBase58.encode())) - privEncryptionKeyHex = hexlify( - highlevelcrypto.decodeWalletImportFormat( - privEncryptionKeyBase58.encode())) - - # 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:] - pubEncryptionKey = unhexlify(highlevelcrypto.privToPub( - privEncryptionKeyHex))[1:] - - 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""" + 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 - myAddress = shared.myAddressesByHash[adressHash] - addressVersionNumber, streamNumber = decodeAddress(myAddress)[1:3] - - # 28 days from now plus or minus five minutes - TTL = int(28 * 24 * 60 * 60 + helper_random.randomrandrange(-300, 300)) + """configSections = shared.config.sections() + 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 += '\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 += getBitfield(myAddress) # bitfield of features supported by me (see the wiki). try: - pubSigningKey, pubEncryptionKey = self._getKeysForAddress( - myAddress)[2:] - except ValueError: - return - except Exception: # pylint:disable=broad-exception-caught - self.logger.error( - 'Error within doPOWForMyV2Pubkey. Could not read' - ' the keys from the keys.dat file for a requested' - ' address. %s\n', exc_info=True) + privSigningKeyBase58 = shared.config.get( + myAddress, 'privsigningkey') + privEncryptionKeyBase58 = shared.config.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 - 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:] + payload += pubEncryptionKey[1:] # Do the POW for this pubkey message - payload = self._doPOWDefaults( - payload, TTL, log_prefix='(For pubkey message)') + target = 2 ** 64 / (shared.networkDefaultProofOfWorkNonceTrialsPerByte*(len(payload) + 8 + shared.networkDefaultPayloadLengthExtraBytes + ((TTL*(len(payload)+8+shared.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 = highlevelcrypto.calculateInventoryHash(payload) + inventoryHash = calculateInventoryHash(payload) objectType = 1 - state.Inventory[inventoryHash] = ( - objectType, streamNumber, payload, embeddedTime, '') + shared.inventory[inventoryHash] = ( + objectType, streamNumber, payload, embeddedTime,'') - self.logger.info( - 'broadcasting inv with hash: %s', hexlify(inventoryHash)) + logger.info('broadcasting inv with hash: ' + hexlify(inventoryHash)) - invQueue.put((streamNumber, inventoryHash)) - queues.UISignalQueue.put(('updateStatusBar', '')) + shared.broadcastToSendDataQueues(( + streamNumber, 'advertiseobject', inventoryHash)) + shared.UISignalQueue.put(('updateStatusBar', '')) try: - config.set( + shared.config.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. + shared.writeKeysFile() + 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: - self.logger.warning( # The address has been deleted. - "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 shared.safeConfigGetBoolean(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 += getBitfield(myAddress) # bitfield of features supported by me (see the wiki). try: - # , privEncryptionKeyHex - privSigningKeyHex, _, pubSigningKey, pubEncryptionKey = \ - self._getKeysForAddress(myAddress) - except ValueError: - return - except Exception: # pylint:disable=broad-exception-caught - self.logger.error( - 'Error within sendOutOrStoreMyV3Pubkey. Could not read' - ' the keys from the keys.dat file for a requested' - ' address. %s\n', exc_info=True) + privSigningKeyBase58 = shared.config.get( + myAddress, 'privsigningkey') + privEncryptionKeyBase58 = shared.config.get( + myAddress, 'privencryptionkey') + except Exception as 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(shared.config.getint( myAddress, 'noncetrialsperbyte')) - payload += encodeVarint(config.getint( + payload += encodeVarint(shared.config.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 / (shared.networkDefaultProofOfWorkNonceTrialsPerByte*(len(payload) + 8 + shared.networkDefaultPayloadLengthExtraBytes + ((TTL*(len(payload)+8+shared.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)) - inventoryHash = highlevelcrypto.calculateInventoryHash(payload) + payload = pack('>Q', nonce) + payload + inventoryHash = calculateInventoryHash(payload) objectType = 1 - state.Inventory[inventoryHash] = ( - objectType, streamNumber, payload, embeddedTime, '') + shared.inventory[inventoryHash] = ( + objectType, streamNumber, payload, embeddedTime,'') - self.logger.info( - 'broadcasting inv with hash: %s', hexlify(inventoryHash)) + logger.info('broadcasting inv with hash: ' + hexlify(inventoryHash)) - invQueue.put((streamNumber, inventoryHash)) - queues.UISignalQueue.put(('updateStatusBar', '')) + shared.broadcastToSendDataQueues(( + streamNumber, 'advertiseobject', inventoryHash)) + shared.UISignalQueue.put(('updateStatusBar', '')) try: - config.set( + shared.config.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. + shared.writeKeysFile() + 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 shared.config.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.safeConfigGetBoolean(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) + dataToEncrypt = getBitfield(myAddress) try: - # , privEncryptionKeyHex - privSigningKeyHex, _, pubSigningKey, pubEncryptionKey = \ - self._getKeysForAddress(myAddress) - except ValueError: - return - except Exception: # pylint:disable=broad-exception-caught - self.logger.error( - 'Error within sendOutOrStoreMyV4Pubkey. Could not read' - ' the keys from the keys.dat file for a requested' - ' address. %s\n', exc_info=True) + privSigningKeyBase58 = shared.config.get( + myAddress, 'privsigningkey') + privEncryptionKeyBase58 = shared.config.get( + myAddress, 'privencryptionkey') + except Exception as 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(shared.config.getint( myAddress, 'noncetrialsperbyte')) - dataToEncrypt += encodeVarint(config.getint( + dataToEncrypt += encodeVarint(shared.config.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 = highlevelcrypto.double_sha512( - encodeVarint(addressVersionNumber) - + encodeVarint(streamNumber) + addressHash - ) - 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 / (shared.networkDefaultProofOfWorkNonceTrialsPerByte*(len(payload) + 8 + shared.networkDefaultPayloadLengthExtraBytes + ((TTL*(len(payload)+8+shared.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)) - inventoryHash = highlevelcrypto.calculateInventoryHash(payload) + payload = pack('>Q', nonce) + payload + inventoryHash = calculateInventoryHash(payload) objectType = 1 - state.Inventory[inventoryHash] = ( - objectType, streamNumber, payload, embeddedTime, - doubleHashOfAddressData[32:] - ) + shared.inventory[inventoryHash] = ( + objectType, streamNumber, payload, embeddedTime, doubleHashOfAddressData[32:]) - self.logger.info( - 'broadcasting inv with hash: %s', hexlify(inventoryHash)) + logger.info('broadcasting inv with hash: ' + hexlify(inventoryHash)) - invQueue.put((streamNumber, inventoryHash)) - queues.UISignalQueue.put(('updateStatusBar', '')) + shared.broadcastToSendDataQueues(( + streamNumber, 'advertiseobject', inventoryHash)) + shared.UISignalQueue.put(('updateStatusBar', '')) try: - config.set( + shared.config.set( myAddress, 'lastpubkeysendtime', str(int(time.time()))) - config.save() + shared.writeKeysFile() 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 = highlevelcrypto.calculateInventoryHash(objectPayload) - - if state.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 = highlevelcrypto.calculateInventoryHash(payload) - state.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)) - 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' ''') queryreturn = sqlQuery( - '''SELECT fromaddress, subject, message, ''' - ''' ackdata, ttl, encodingtype FROM sent ''' - ''' WHERE status=? and folder='sent' ''', 'broadcastqueued') - + '''SELECT fromaddress, subject, message, ackdata, ttl 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) + fromaddress, subject, body, ackdata, TTL = row + 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 ValueError: - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "Error! Could not find sender address" - " (your address) in the keys.dat file.")) - )) - continue - 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 = shared.config.get( + fromaddress, 'privsigningkey') + privEncryptionKeyBase58 = shared.config.get( + fromaddress, 'privencryptionkey') + except: + shared.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 + privSigningKeyHex = hexlify(shared.decodeWalletImportFormat( + privSigningKeyBase58)) + privEncryptionKeyHex = hexlify(shared.decodeWalletImportFormat( + privEncryptionKeyBase58)) - # 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') + 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 = highlevelcrypto.double_sha512( - encodeVarint(addressVersionNumber) - + encodeVarint(streamNumber) + ripe - ) + doubleHashOfAddressData = hashlib.sha512(hashlib.sha512(encodeVarint( + addressVersionNumber) + encodeVarint(streamNumber) + ripe).digest()).digest() tag = doubleHashOfAddressData[32:] payload += tag else: @@ -619,39 +427,30 @@ class singleWorker(StoppableThread): dataToEncrypt = encodeVarint(addressVersionNumber) dataToEncrypt += encodeVarint(streamNumber) - # behavior bitfield - dataToEncrypt += protocol.getBitfield(fromaddress) - dataToEncrypt += pubSigningKey + pubEncryptionKey + dataToEncrypt += 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(encodedMessage.length) - dataToEncrypt += encodedMessage.data + dataToEncrypt += encodeVarint(shared.config.getint(fromaddress,'noncetrialsperbyte')) + dataToEncrypt += encodeVarint(shared.config.getint(fromaddress,'payloadlengthextrabytes')) + dataToEncrypt += '\x02' # message encoding type + dataToEncrypt += encodeVarint(len('Subject:' + subject + '\n' + 'Body:' + body)) #Type 2 is simple UTF-8 message encoding per the documentation on the wiki. + dataToEncrypt += 'Subject:' + subject + '\n' + 'Body:' + body 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] @@ -659,327 +458,195 @@ 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 / (shared.networkDefaultProofOfWorkNonceTrialsPerByte*(len(payload) + 8 + shared.networkDefaultPayloadLengthExtraBytes + ((TTL*(len(payload)+8+shared.networkDefaultPayloadLengthExtraBytes))/(2 ** 16)))) + logger.info('(For broadcast message) Doing proof of work...') + shared.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 = highlevelcrypto.calculateInventoryHash(payload) + inventoryHash = calculateInventoryHash(payload) objectType = 3 - state.Inventory[inventoryHash] = ( + shared.inventory[inventoryHash] = ( objectType, streamNumber, payload, embeddedTime, tag) - self.logger.info( - 'sending inv (within sendBroadcast function)' - ' for object: %s', - hexlify(inventoryHash) - ) - invQueue.put((streamNumber, inventoryHash)) + logger.info('sending inv (within sendBroadcast function) for object: ' + hexlify(inventoryHash)) + shared.broadcastToSendDataQueues(( + streamNumber, 'advertiseobject', inventoryHash)) - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "Broadcast sent on %1" - ).arg(l10n.formatTimestamp())) - )) + shared.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' ''') - 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. + while True: # while we have a msg that needs some work + + # Select just one msg that needs work. + queryreturn = sqlQuery( + '''SELECT toaddress, fromaddress, subject, message, ackdata, status, ttl, retrynumber FROM sent WHERE (status='msgqueued' or status='doingmsgpow' or status='forcepow') and folder='sent' LIMIT 1''') + if len(queryreturn) == 0: # if there is no work to do then + break # break out of this sendMsg loop and + # wait for something to get put in the shared.workerQueue. + row = queryreturn[0] + toaddress, fromaddress, subject, message, ackdata, status, TTL, retryNumber = 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 shared.config.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 = highlevelcrypto.double_sha512( - encodeVarint(toAddressVersionNumber) - + encodeVarint(toStreamNumber) + toRipe - )[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 shared.neededPubkeys or toTag in shared.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) + shared.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. + 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. + shared.neededPubkeys[tag] = (toaddress, highlevelcrypto.makeCryptor(hexlify(privEncryptionKey))) - # 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 = \ - highlevelcrypto.double_sha512( - encodeVarint(toAddressVersionNumber) - + encodeVarint(toStreamNumber) + toRipe - ) - # 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)) - ) - - for value in state.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': + for value in shared.inventory.by_type_and_tag(1, toTag): + 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] + del shared.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) + shared.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. - - 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)) + 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. + + if retryNumber == 0: + if TTL > 28 * 24 * 60 * 60: + TTL = 28 * 24 * 60 * 60 + else: + TTL = 28 * 24 * 60 * 60 + TTL = int(TTL + random.randrange(-300, 300))# add some randomness to the TTL embeddedTime = int(time.time() + TTL) + + if not shared.config.has_section(toaddress): # if we aren't sending this to ourselves or a chan + shared.ackdataForWhichImWatching[ackdata] = 0 + shared.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.safeConfigGetBoolean('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.') + shared.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] @@ -987,528 +654,324 @@ 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 = shared.networkDefaultProofOfWorkNonceTrialsPerByte + requiredPayloadLengthExtraBytes = shared.networkDefaultPayloadLengthExtraBytes + shared.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 < shared.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 = shared.networkDefaultProofOfWorkNonceTrialsPerByte + if requiredPayloadLengthExtraBytes < shared.networkDefaultPayloadLengthExtraBytes: + requiredPayloadLengthExtraBytes = shared.networkDefaultPayloadLengthExtraBytes + logger.debug('Using averageProofOfWorkNonceTrialsPerByte: %s and payloadLengthExtraBytes: %s.' % (requiredAverageProofOfWorkNonceTrialsPerByte, requiredPayloadLengthExtraBytes)) + shared.UISignalQueue.put(('updateSentItemStatusByAckdata', (ackdata, tr._translate("MainWindow", "Doing work necessary to send message.\nReceiver\'s required difficulty: %1 and %2").arg(str(float( + requiredAverageProofOfWorkNonceTrialsPerByte) / shared.networkDefaultProofOfWorkNonceTrialsPerByte)).arg(str(float(requiredPayloadLengthExtraBytes) / shared.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 > shared.config.getint('bitmessagesettings', 'maxacceptablenoncetrialsperbyte') and shared.config.getint('bitmessagesettings', 'maxacceptablenoncetrialsperbyte') != 0) or (requiredPayloadLengthExtraBytes > shared.config.getint('bitmessagesettings', 'maxacceptablepayloadlengthextrabytes') and shared.config.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())))) + shared.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) / shared.networkDefaultProofOfWorkNonceTrialsPerByte)).arg(str(float( + requiredPayloadLengthExtraBytes) / shared.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]) - behaviorBitfield = protocol.getBitfield(fromaddress) + 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 = getBitfield(fromaddress) try: - privEncryptionKeyBase58 = config.get( + privEncryptionKeyBase58 = shared.config.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: + shared.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( - highlevelcrypto.decodeWalletImportFormat( - privEncryptionKeyBase58.encode())) + 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 = shared.networkDefaultProofOfWorkNonceTrialsPerByte + requiredPayloadLengthExtraBytes = shared.networkDefaultPayloadLengthExtraBytes + shared.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 += 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 ValueError: - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "Error! Could not find sender address" - " (your address) in the keys.dat file.")) - )) - continue - 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 = shared.config.get( + fromaddress, 'privsigningkey') + privEncryptionKeyBase58 = shared.config.get( + fromaddress, 'privencryptionkey') + except: + shared.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) + shared.networkDefaultProofOfWorkNonceTrialsPerByte) payload += encodeVarint( - defaults.networkDefaultPayloadLengthExtraBytes) + shared.networkDefaultPayloadLengthExtraBytes) else: - payload += encodeVarint(config.getint( + payload += encodeVarint(shared.config.getint( fromaddress, 'noncetrialsperbyte')) - payload += encodeVarint(config.getint( + payload += encodeVarint(shared.config.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 += 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.' - ) + 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 += '\x02' # Type 2 is simple UTF-8 message encoding as specified on the Protocol Specification on the Bitmessage Wiki. + messageToTransmit = 'Subject:' + \ + subject + '\n' + 'Body:' + message + payload += encodeVarint(len(messageToTransmit)) + payload += messageToTransmit + if shared.config.has_section(toaddress): + logger.info('Not bothering to include ackdata because we are sending to ourselves or a chan.') fullAckPayload = '' + elif not checkBitfield(behaviorBitfield, shared.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) + shared.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) / shared.networkDefaultProofOfWorkNonceTrialsPerByte, float(requiredPayloadLengthExtraBytes) / shared.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 = highlevelcrypto.calculateInventoryHash(encryptedPayload) + inventoryHash = calculateInventoryHash(encryptedPayload) objectType = 2 - state.Inventory[inventoryHash] = ( + shared.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())))) + if shared.config.has_section(toaddress) or not checkBitfield(behaviorBitfield, shared.BITFIELD_DOESACK): + shared.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) - ) - invQueue.put((toStreamNumber, inventoryHash)) + shared.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)) + shared.broadcastToSendDataQueues(( + toStreamNumber, 'advertiseobject', 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 shared.config.has_section(toaddress) or not checkBitfield(behaviorBitfield, shared.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 - ) + if retryNumber == 0: + sleepTill = int(time.time()) + TTL + else: + sleepTill = int(time.time()) + 28*24*60*60 * 2**retryNumber + 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 = highlevelcrypto.double_sha512(signature)[32:] + # If we are sending to ourselves or a chan, let's put the message in + # our own inbox. + if shared.config.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) + time.time()), message, 'inbox', 2, 0, sigHash) helper_inbox.insert(t) - queues.UISignalQueue.put(('displayNewInboxMessage', ( + shared.UISignalQueue.put(('displayNewInboxMessage', ( inventoryHash, toaddress, fromaddress, subject, message))) # 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 shared.safeConfigGetBoolean('bitmessagesettings', 'apienabled'): + try: + apiNotifyPath = shared.config.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 + shared.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. - - doubleHashOfAddressData = highlevelcrypto.double_sha512( - encodeVarint(addressVersionNumber) - + encodeVarint(streamNumber) + ripe - ) - privEncryptionKey = doubleHashOfAddressData[:32] - # Note that this is the second half of the sha512 hash. - tag = doubleHashOfAddressData[32:] - 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) + # 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 shared.neededPubkeys: + shared.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. + + if retryNumber == 0: + TTL = 2.5*24*60*60 # 2.5 days. This was chosen fairly arbitrarily. + else: + 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.' - queues.UISignalQueue.put(('updateStatusBar', statusbar)) - queues.UISignalQueue.put(( - 'updateSentItemStatusByToAddress', ( - toAddress, - tr._translate( - "MainWindow", - "Doing work necessary to request encryption key.")) - )) + # print 'trial value', trialValue + statusbar = 'Doing the computations necessary to request the recipient\'s public key.' + shared.UISignalQueue.put(('updateStatusBar', statusbar)) + shared.UISignalQueue.put(('updateSentItemStatusByToAddress', ( + toAddress, tr._translate("MainWindow",'Doing work necessary to request encryption key.')))) + + target = 2 ** 64 / (shared.networkDefaultProofOfWorkNonceTrialsPerByte*(len(payload) + 8 + shared.networkDefaultPayloadLengthExtraBytes + ((TTL*(len(payload)+8+shared.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 = self._doPOWDefaults(payload, TTL) - - inventoryHash = highlevelcrypto.calculateInventoryHash(payload) + payload = pack('>Q', nonce) + payload + inventoryHash = calculateInventoryHash(payload) objectType = 1 - state.Inventory[inventoryHash] = ( + shared.inventory[inventoryHash] = ( objectType, streamNumber, payload, embeddedTime, '') - self.logger.info('sending inv (for the getpubkey message)') - 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) - - 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())) - )) - - 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 + logger.info('sending inv (for the getpubkey message)') + shared.broadcastToSendDataQueues(( + streamNumber, 'advertiseobject', inventoryHash)) + + if retryNumber == 0: + sleeptill = int(time.time()) + TTL else: - TTL = 28 * 24 * 60 * 60 # 4 weeks - # Add some randomness to the TTL - TTL = int(TTL + helper_random.randomrandrange(-300, 300)) + sleeptill = int(time.time()) + 28*24*60*60 * 2**retryNumber + sqlExecute( + '''UPDATE sent SET lastactiontime=?, status='awaitingpubkey', retrynumber=?, sleeptill=? WHERE toaddress=? AND (status='doingpubkeypow' OR status='awaitingpubkey') ''', + int(time.time()), + retryNumber+1, + sleeptill, + toAddress) + + shared.UISignalQueue.put(( + 'updateStatusBar', tr._translate("MainWindow",'Broadcasting the public key request. This program will auto-retry if they are offline.'))) + shared.UISignalQueue.put(('updateSentItemStatusByToAddress', (toAddress, tr._translate("MainWindow",'Sending public key request. Waiting for reply. Requested at %1').arg(l10n.formatTimestamp())))) + + 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 + TTL = int(TTL + random.randrange(-300, 300)) # Add some randomness to the TTL embeddedTime = int(time.time() + TTL) + payload = pack('>Q', (embeddedTime)) + payload += '\x00\x00\x00\x02' # object type: msg + payload += encodeVarint(1) # msg version + payload += encodeVarint(toStreamNumber) + ackdata + + target = 2 ** 64 / (shared.networkDefaultProofOfWorkNonceTrialsPerByte*(len(payload) + 8 + shared.networkDefaultPayloadLengthExtraBytes + ((TTL*(len(payload)+8+shared.networkDefaultPayloadLengthExtraBytes))/(2 ** 16)))) + logger.info('(For ack message) Doing proof of work. TTL set to ' + str(TTL)) - # type/version/stream already included - payload = pack('>Q', (embeddedTime)) + ackdata + 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 = self._doPOWDefaults( - payload, TTL, log_prefix='(For ack message)', log_time=True) - - return protocol.CreatePacket('object', payload) + payload = pack('>Q', nonce) + payload + return shared.CreatePacket('object', payload) diff --git a/src/class_smtpDeliver.py b/src/class_smtpDeliver.py index 9e3b8ab3..42baf989 100644 --- a/src/class_smtpDeliver.py +++ b/src/class_smtpDeliver.py @@ -1,54 +1,53 @@ -""" -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 -import queues -import state -from bmconfigparser import config -from network.threads import StoppableThread +from debug import logger +from helper_threading import * +import shared 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: + shared.UISignallerQueue.put(("stopThread", "data")) + except: + pass super(smtpDeliver, self).stopThread() @classmethod def get(cls): - """(probably) Singleton functionality""" if not cls._instance: - cls._instance = smtpDeliver() + cls._instance = UISignaler() 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() + while shared.shutdown == 0: + command, data = shared.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 = shared.safeConfigGet("bitmessagesettings", "smtpdeliver", '') if dest == '': continue try: @@ -58,12 +57,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: shared.safeConfigGet(y, "label"), filter(lambda x: x == toAddress, shared.config.sections())) + if len(toLabel) > 0: msg['To'] = "\"%s\" <%s>" % (Header(toLabel[0], 'utf-8'), toAddress + '@' + SMTPDOMAIN) else: msg['To'] = toAddress + '@' + SMTPDOMAIN @@ -71,14 +66,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 +101,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 deleted file mode 100644 index 44ea7c9c..00000000 --- a/src/class_smtpServer.py +++ /dev/null @@ -1,217 +0,0 @@ -""" -SMTP server thread -""" -import asyncore -import base64 -import email -import logging -import re -import signal -import smtpd -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 helper_sql import sqlExecute -from network.threads import StoppableThread -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 - self.push('250-PyBitmessage %s' % softwareVersion) - 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) - if correctauth == decoded: - self.auth = True - self.push('235 2.7.0 Authentication successful') - else: - raise SmtpServerChannelException("Auth fail") - except: # noqa:E722 - 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') - 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 - 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') - ackdata = genAckPayload(streamNumber, stealthLevel) - 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 - 2, # encodingtype - # not necessary to have a TTL higher than 2 days - min(config.getint('bitmessagesettings', 'ttl'), 86400 * 2) - ) - - 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])) - 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 - p = re.compile(".*<([^>]+)>") - if not hasattr(self.channel, "auth") or not self.channel.auth: - logger.error('Missing or invalid auth') - return - try: - self.msg_headers = Parser().parsestr(data) - except: # noqa:E722 - 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) - except Exception as err: - logger.debug('Bad envelope from %s: %r', mailfrom, 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) - except Exception as err: - logger.error('Bad headers from %s: %r', msg_from, err) - return - - try: - msg_subject = self.decode_header('subject')[0] - except: # noqa:E722 - msg_subject = "Subject missing..." - - msg_tmp = email.message_from_string(data) - body = u'' - for part in msg_tmp.walk(): - if part and part.get_content_type() == "text/plain": - body += part.get_payload(decode=1).decode(part.get_content_charset('utf-8'), errors='replace') - - for to in rcpttos: - 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) - self.send(sender, rcpt, msg_subject, body) - logger.info('Relayed %s to %s', sender, rcpt) - except Exception as err: - logger.error('Bad to %s: %r', to, err) - continue - return - - -class smtpServer(StoppableThread): - """SMTP server thread""" - def __init__(self, _=None): - super(smtpServer, self).__init__(name="smtpServerThread") - self.server = smtpServerPyBitmessage(('127.0.0.1', LISTENPORT), None) - - def stopThread(self): - super(smtpServer, self).stopThread() - self.server.close() - return - - def run(self): - asyncore.loop(1) - - -def signals(_, __): - """Signal handler""" - logger.warning('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') - smtpThread = smtpServer() - smtpThread.start() - signal.signal(signal.SIGINT, signals) - signal.signal(signal.SIGTERM, signals) - logger.warning('Processing') - smtpThread.join() - logger.warning('The end') - - -if __name__ == "__main__": - runServer() diff --git a/src/class_sqlThread.py b/src/class_sqlThread.py index 7df9e253..016386ab 100644 --- a/src/class_sqlThread.py +++ b/src/class_sqlThread.py @@ -1,85 +1,59 @@ -""" -sqlThread is defined here -""" - -import os -import shutil # used for moving the messages.dat file -import sqlite3 -import sys import threading +import shared +import sqlite3 import time +import shutil # used for moving the messages.dat file +import sys +import os +from debug import logger +from namecoin import ensureNamecoinOptions +import random +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() - self.conn = sqlite3.connect(state.appdata + 'messages.dat') + def run(self): + self.conn = sqlite3.connect(shared.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 +65,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 shared.config.getint('bitmessagesettings', 'settingsversion') == 1: + shared.config.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. + shared.config.set('bitmessagesettings', 'socksproxytype', 'none') + shared.config.set('bitmessagesettings', 'sockshostname', 'localhost') + shared.config.set('bitmessagesettings', 'socksport', '9050') + shared.config.set('bitmessagesettings', 'socksauthentication', 'false') + shared.config.set('bitmessagesettings', 'socksusername', '') + shared.config.set('bitmessagesettings', 'sockspassword', '') + shared.config.set('bitmessagesettings', 'sockslisten', 'false') + shared.config.set('bitmessagesettings', 'keysencrypted', 'false') + shared.config.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 shared.config.getint('bitmessagesettings', 'settingsversion') == 2: item = '''ALTER TABLE pubkeys ADD usedpersonally text DEFAULT 'no' ''' parameters = '' self.cur.execute(item, parameters) self.conn.commit() - settingsversion = 3 + shared.config.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 shared.config.getint('bitmessagesettings', 'settingsversion') == 3: item = '''ALTER TABLE inbox ADD encodingtype int DEFAULT '2' ''' parameters = '' self.cur.execute(item, parameters) @@ -124,13 +107,21 @@ class sqlThread(threading.Thread): self.cur.execute(item, parameters) self.conn.commit() - settingsversion = 4 + shared.config.set('bitmessagesettings', 'settingsversion', '4') - config.set( - 'bitmessagesettings', 'settingsversion', str(settingsversion)) - config.save() + if shared.config.getint('bitmessagesettings', 'settingsversion') == 4: + shared.config.set('bitmessagesettings', 'defaultnoncetrialsperbyte', str( + shared.networkDefaultProofOfWorkNonceTrialsPerByte)) + shared.config.set('bitmessagesettings', 'defaultpayloadlengthextrabytes', str( + shared.networkDefaultPayloadLengthExtraBytes)) + shared.config.set('bitmessagesettings', 'settingsversion', '5') - helper_startup.updateConfig() + if shared.config.getint('bitmessagesettings', 'settingsversion') == 5: + shared.config.set( + 'bitmessagesettings', 'maxacceptablenoncetrialsperbyte', '0') + shared.config.set( + 'bitmessagesettings', 'maxacceptablepayloadlengthextrabytes', '0') + shared.config.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 +132,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 +173,41 @@ class sqlThread(threading.Thread): self.cur.execute( '''update sent set status='broadcastqueued' where status='broadcastpending' ''') self.conn.commit() + + if not shared.config.has_option('bitmessagesettings', 'sockslisten'): + shared.config.set('bitmessagesettings', 'sockslisten', 'false') + + ensureNamecoinOptions() + + """# Add a new column to the inventory table to store the first 20 bytes of encrypted messages to support Android app + item = '''SELECT value FROM settings WHERE key='version';''' + parameters = '' + 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 +218,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 +226,19 @@ class sqlThread(threading.Thread): parameters = (4,) self.cur.execute(item, parameters) + if not shared.config.has_option('bitmessagesettings', 'userlocale'): + shared.config.set('bitmessagesettings', 'userlocale', 'system') + if not shared.config.has_option('bitmessagesettings', 'sendoutgoingconnections'): + shared.config.set('bitmessagesettings', 'sendoutgoingconnections', 'True') + + # Raise the default required difficulty from 1 to 2 + # With the change to protocol v3, this is obsolete. + if shared.config.getint('bitmessagesettings', 'settingsversion') == 6: + """if int(shared.config.get('bitmessagesettings','defaultnoncetrialsperbyte')) == shared.networkDefaultProofOfWorkNonceTrialsPerByte: + shared.config.set('bitmessagesettings','defaultnoncetrialsperbyte', str(shared.networkDefaultProofOfWorkNonceTrialsPerByte * 2)) + """ + shared.config.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 +246,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 shared.config.has_option('bitmessagesettings', 'useidenticons'): + shared.config.set('bitmessagesettings', 'useidenticons', 'True') + if not shared.config.has_option('bitmessagesettings', 'identiconsuffix'): # acts as a salt + shared.config.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 shared.config.getint('bitmessagesettings', 'settingsversion') == 7: + shared.config.set( + 'bitmessagesettings', 'stopresendingafterxdays', '') + shared.config.set( + 'bitmessagesettings', 'stopresendingafterxmonths', '') + shared.config.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 +275,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 shared.config.getint('bitmessagesettings', 'settingsversion') == 8: + shared.config.set('bitmessagesettings','defaultnoncetrialsperbyte', str(shared.networkDefaultProofOfWorkNonceTrialsPerByte)) + shared.config.set('bitmessagesettings','defaultpayloadlengthextrabytes', str(shared.networkDefaultPayloadLengthExtraBytes)) + previousTotalDifficulty = int(shared.config.getint('bitmessagesettings', 'maxacceptablenoncetrialsperbyte')) / 320 + previousSmallMessageDifficulty = int(shared.config.getint('bitmessagesettings', 'maxacceptablepayloadlengthextrabytes')) / 14000 + shared.config.set('bitmessagesettings','maxacceptablenoncetrialsperbyte', str(previousTotalDifficulty * 1000)) + shared.config.set('bitmessagesettings','maxacceptablepayloadlengthextrabytes', str(previousSmallMessageDifficulty * 1000)) + shared.config.set('bitmessagesettings', 'settingsversion', '9') + + # Adjust the required POW values for each of this user's addresses to conform to protocol v3 norms. + if shared.config.getint('bitmessagesettings', 'settingsversion') == 9: + for addressInKeysFile in shared.config.sections(): + try: + previousTotalDifficulty = float(shared.config.getint(addressInKeysFile, 'noncetrialsperbyte')) / 320 + previousSmallMessageDifficulty = float(shared.config.getint(addressInKeysFile, 'payloadlengthextrabytes')) / 14000 + if previousTotalDifficulty <= 2: + previousTotalDifficulty = 1 + if previousSmallMessageDifficulty < 1: + previousSmallMessageDifficulty = 1 + shared.config.set(addressInKeysFile,'noncetrialsperbyte', str(int(previousTotalDifficulty * 1000))) + shared.config.set(addressInKeysFile,'payloadlengthextrabytes', str(int(previousSmallMessageDifficulty * 1000))) + except: + continue + shared.config.set('bitmessagesettings', 'maxdownloadrate', '0') + shared.config.set('bitmessagesettings', 'maxuploadrate', '0') + shared.config.set('bitmessagesettings', 'settingsversion', '10') + shared.writeKeysFile() + + # sanity check + if shared.config.getint('bitmessagesettings', 'maxacceptablenoncetrialsperbyte') == 0: + shared.config.set('bitmessagesettings','maxacceptablenoncetrialsperbyte', str(shared.ridiculousDifficulty * shared.networkDefaultProofOfWorkNonceTrialsPerByte)) + if shared.config.getint('bitmessagesettings', 'maxacceptablepayloadlengthextrabytes') == 0: + shared.config.set('bitmessagesettings','maxacceptablepayloadlengthextrabytes', str(shared.ridiculousDifficulty * shared.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 +355,87 @@ 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 shared.config.has_option('bitmessagesettings', 'ttl'): + shared.config.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';''') - - # 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';''') - + + if not shared.config.has_option('bitmessagesettings', 'onionhostname'): + shared.config.set('bitmessagesettings', 'onionhostname', '') + if not shared.config.has_option('bitmessagesettings', 'onionport'): + shared.config.set('bitmessagesettings', 'onionport', '8444') + if not shared.config.has_option('bitmessagesettings', 'onionbindip'): + shared.config.set('bitmessagesettings', 'onionbindip', '127.0.0.1') + if not shared.config.has_option('bitmessagesettings', 'smtpdeliver'): + shared.config.set('bitmessagesettings', 'smtpdeliver', '') + shared.writeKeysFile() + # 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 +445,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.') + shared.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,47 +467,25 @@ 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.') + shared.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() - while True: - item = helper_sql.sqlSubmitQueue.get() + item = shared.sqlSubmitQueue.get() if item == 'commit': try: 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.') + shared.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,23 +499,13 @@ 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.') + shared.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( - paths.lookupAppdataFolder() + 'messages.dat', paths.lookupExeFolder() + 'messages.dat') - self.conn = sqlite3.connect(paths.lookupExeFolder() + 'messages.dat') + shared.lookupAppdataFolder() + 'messages.dat', shared.lookupExeFolder() + 'messages.dat') + self.conn = sqlite3.connect(shared.lookupExeFolder() + 'messages.dat') self.conn.text_factory = str self.cur = self.conn.cursor() elif item == 'movemessagstoappdata': @@ -551,23 +515,13 @@ 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.') + shared.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( - paths.lookupExeFolder() + 'messages.dat', paths.lookupAppdataFolder() + 'messages.dat') - self.conn = sqlite3.connect(paths.lookupAppdataFolder() + 'messages.dat') + shared.lookupExeFolder() + 'messages.dat', shared.lookupAppdataFolder() + 'messages.dat') + self.conn = sqlite3.connect(shared.lookupAppdataFolder() + 'messages.dat') self.conn.text_factory = str self.cur = self.conn.cursor() elif item == 'deleteandvacuume': @@ -575,66 +529,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.') + shared.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() + parameters = shared.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.') + shared.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) + shared.sqlReturnQueue.put((self.cur.fetchall(), rowcount)) + # shared.sqlSubmitQueue.task_done() diff --git a/src/debug.py b/src/debug.py index 639be123..707d7062 100644 --- a/src/debug.py +++ b/src/debug.py @@ -1,96 +1,58 @@ -""" +# -*- 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 shared import sys - -from six.moves import configparser - +import traceback 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)) - + logging.critical(''.join(traceback.format_tb(tb))) + logging.critical('{0}: {1}'.format(ex_cls, ex)) 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()) - else: - # no need to confuse the user if the logger config - # is missing entirely - fail_msg = 'Using default logger configuration' + logging.config.fileConfig(os.path.join (shared.appdata, 'logging.dat')) + have_logging = True + print "Loaded debug config from %s" % (os.path.join(shared.appdata, 'logging.dat')) + except: + print "Failed to load debug config from %s, using default logging config" % (os.path.join(shared.appdata, 'logging.dat')) + print sys.exc_info() + + sys.excepthook = log_uncaught_exceptions - logging_config = { + if have_logging: + return False + + logging.config.dictConfig({ 'version': 1, 'formatters': { 'default': { - 'format': u'%(asctime)s - %(levelname)s - %(message)s', + 'format': '%(asctime)s - %(levelname)s - %(message)s', }, }, 'handlers': { @@ -98,60 +60,59 @@ def configureLogging(): 'class': 'logging.StreamHandler', 'formatter': 'default', 'level': log_level, - 'stream': 'ext://sys.stderr' + 'stream': 'ext://sys.stdout' }, 'file': { 'class': 'logging.handlers.RotatingFileHandler', 'formatter': 'default', 'level': log_level, - 'filename': os.path.join(state.appdata, 'debug.log'), - 'maxBytes': 2097152, # 2 MiB + 'filename': shared.appdata + 'debug.log', + 'maxBytes': 2097152, # 2 MiB 'backupCount': 1, - 'encoding': 'UTF-8', } }, '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..f91e6fe6 --- /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 shared + +def createDefaultKnownNodes(appdata): + ############## Stream 1 ################ + stream1 = {} + + #stream1[shared.Peer('2604:2000:1380:9f:82e:148b:2746:d0c7', 8080)] = int(time.time()) + stream1[shared.Peer('5.45.99.75', 8444)] = int(time.time()) + stream1[shared.Peer('75.167.159.54', 8444)] = int(time.time()) + stream1[shared.Peer('95.165.168.168', 8444)] = int(time.time()) + stream1[shared.Peer('85.180.139.241', 8444)] = int(time.time()) + stream1[shared.Peer('158.222.211.81', 8080)] = int(time.time()) + stream1[shared.Peer('178.62.12.187', 8448)] = int(time.time()) + stream1[shared.Peer('24.188.198.204', 8111)] = int(time.time()) + stream1[shared.Peer('109.147.204.113', 1195)] = int(time.time()) + stream1[shared.Peer('178.11.46.221', 8444)] = int(time.time()) + + ############# 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 deleted file mode 100644 index 32162b56..00000000 --- a/src/defaults.py +++ /dev/null @@ -1,24 +0,0 @@ -""" -Common default values -""" - -#: 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". -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 diff --git a/src/depends.py b/src/depends.py index d966d5fe..294c6a22 100755 --- a/src/depends.py +++ b/src/depends.py @@ -1,249 +1,71 @@ -""" -Utility functions to check the availability of dependencies -and suggest how it may be installed -""" +#! python -import os -import re import sys -# 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.') - if sys.platform.startswith('freebsd'): - logger.error( - 'On FreeBSD, try running "pkg install py27-sqlite3" as root.') + logger.error('The sqlite3 module is not included in this version of Python.') + 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,218 +75,145 @@ 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 - openssl_hexversion = None - openssl_cflags = None + SSLEAY_VERSION = 0 + SSLEAY_CFLAGS = 2 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) + logger.info('OpenSSL Name: ' + library._name) try: - openssl_version, openssl_hexversion, openssl_cflags = \ - pyelliptic.openssl.get_version(library) - except AttributeError: # sphinx chokes - return True - if not openssl_version: + library.SSLeay.restype = ctypes.c_long + library.SSLeay_version.restype = ctypes.c_char_p + library.SSLeay_version.argtypes = [ctypes.c_int] + except AttributeError: 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: ' + library.SSLeay_version(SSLEAY_VERSION)) + compile_options = library.SSLeay_version(SSLEAY_CFLAGS) + logger.info('OpenSSL Compile Options: ' + compile_options) + openssl_hexversion = library.SSLeay() + #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(compile_options) + 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.') 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. - - 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 - - -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] 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 deleted file mode 100644 index f65999a1..00000000 --- a/src/fallback/__init__.py +++ /dev/null @@ -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/__init__.py b/src/fallback/umsgpack/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/fallback/umsgpack/umsgpack.py b/src/fallback/umsgpack/umsgpack.py deleted file mode 100644 index 34938614..00000000 --- a/src/fallback/umsgpack/umsgpack.py +++ /dev/null @@ -1,1067 +0,0 @@ -# u-msgpack-python v2.4.1 - v at sergeev.io -# https://github.com/vsergeev/u-msgpack-python -# -# u-msgpack-python is a lightweight MessagePack serializer and deserializer -# module, compatible with both Python 2 and 3, as well CPython and PyPy -# implementations of Python. u-msgpack-python is fully compliant with the -# latest MessagePack specification.com/msgpack/msgpack/blob/master/spec.md). In -# particular, it supports the new binary, UTF-8 string, and application ext -# types. -# -# MIT License -# -# Copyright (c) 2013-2016 vsergeev / Ivan (Vanya) A. Sergeev -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -""" -src/fallback/umsgpack/umsgpack.py -================================= - -u-msgpack-python v2.4.1 - v at sergeev.io -https://github.com/vsergeev/u-msgpack-python - -u-msgpack-python is a lightweight MessagePack serializer and deserializer -module, compatible with both Python 2 and 3, as well CPython and PyPy -implementations of Python. u-msgpack-python is fully compliant with the -latest MessagePack specification.com/msgpack/msgpack/blob/master/spec.md). In -particular, it supports the new binary, UTF-8 string, and application ext -types. - -License: MIT -""" -# 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 sys - -__version__ = "2.4.1" -"Module version string" - -version = (2, 4, 1) -"Module version tuple" - - -############################################################################## -# Ext Class -############################################################################## - -# Extension type for application-defined types and data -class Ext: # pylint: disable=old-style-class - """ - The Ext class facilitates creating a serializable extension object to store - an application-defined type and data byte array. - """ - - def __init__(self, type, data): - """ - Construct a new Ext object. - - Args: - type: application-defined type integer from 0 to 127 - data: application-defined data byte array - - Raises: - TypeError: - Specified ext type is outside of 0 to 127 range. - - Example: - >>> foo = umsgpack.Ext(0x05, b"\x01\x02\x03") - >>> umsgpack.packb({u"special stuff": foo, u"awesome": True}) - '\x82\xa7awesome\xc3\xadspecial stuff\xc7\x03\x05\x01\x02\x03' - >>> bar = umsgpack.unpackb(_) - >>> print(bar["special stuff"]) - Ext Object (Type: 0x05, Data: 01 02 03) - >>> - """ - # 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") - # Check data is type bytes - elif sys.version_info[0] == 3 and not isinstance(data, bytes): - raise TypeError("ext data is not type \'bytes\'") - elif sys.version_info[0] == 2 and not isinstance(data, str): - raise TypeError("ext data is not type \'str\'") - self.type = type - self.data = data - - def __eq__(self, other): - """ - Compare this Ext object with another for equality. - """ - return (isinstance(other, self.__class__) and - self.type == other.type and - self.data == other.data) - - def __ne__(self, other): - """ - Compare this Ext object with another for inequality. - """ - return not self.__eq__(other) - - def __str__(self): - """ - String representation of this Ext object. - """ - s = "Ext Object (Type: 0x%02x, Data: " % self.type - s += " ".join(["0x%02x" % ord(self.data[i:i + 1]) - for i in xrange(min(len(self.data), 8))]) - if len(self.data) > 8: - s += " ..." - s += ")" - return s - - def __hash__(self): - """ - Provide a hash of this Ext object. - """ - return hash((self.type, self.data)) - - -class InvalidString(bytes): - """Subclass of bytes to hold invalid UTF-8 strings.""" - pass - -############################################################################## -# Exceptions -############################################################################## - - -# Base Exception classes -class PackException(Exception): - "Base class for exceptions encountered during packing." - pass - - -class UnpackException(Exception): - "Base class for exceptions encountered during unpacking." - pass - - -# Packing error -class UnsupportedTypeException(PackException): - "Object type not supported for packing." - pass - - -# Unpacking error -class InsufficientDataException(UnpackException): - "Insufficient data to unpack the serialized object." - pass - - -class InvalidStringException(UnpackException): - "Invalid UTF-8 string encountered during unpacking." - pass - - -class ReservedCodeException(UnpackException): - "Reserved code encountered during unpacking." - pass - - -class UnhashableKeyException(UnpackException): - """ - Unhashable key encountered during map unpacking. - The serialized map cannot be deserialized into a Python dictionary. - """ - pass - - -class DuplicateKeyException(UnpackException): - "Duplicate key encountered during map unpacking." - pass - - -# Backwards compatibility -KeyNotPrimitiveException = UnhashableKeyException -KeyDuplicateException = DuplicateKeyException - -############################################################################# -# Exported Functions and Glob -############################################################################# - -# Exported functions and variables, set up in __init() -pack = None -packb = None -unpack = None -unpackb = None -dump = None -dumps = None -load = None -loads = None - -compatibility = False -u""" -Compatibility mode boolean. - -When compatibility mode is enabled, u-msgpack-python will serialize both -unicode strings and bytes into the old "raw" msgpack type, and deserialize the -"raw" msgpack type into bytes. This provides backwards compatibility with the -old MessagePack specification. - -Example: ->>> umsgpack.compatibility = True ->>> ->>> umsgpack.packb([u"some string", b"some bytes"]) -b'\x92\xabsome string\xaasome bytes' ->>> umsgpack.unpackb(_) -[b'some string', b'some bytes'] ->>> -""" - -############################################################################## -# Packing -############################################################################## - -# You may notice struct.pack("B", obj) instead of the simpler chr(obj) in the -# code below. This is to allow for seamless Python 2 and 3 compatibility, as -# chr(obj) has a str return type instead of bytes in Python 3, and -# struct.pack(...) has the right return type in both versions. - - -def _pack_integer(obj, fp, options): - if obj < 0: - if obj >= -32: - fp.write(struct.pack("b", obj)) - elif obj >= -2**(8 - 1): - fp.write(b"\xd0" + struct.pack("b", obj)) - elif obj >= -2**(16 - 1): - fp.write(b"\xd1" + struct.pack(">h", obj)) - elif obj >= -2**(32 - 1): - fp.write(b"\xd2" + struct.pack(">i", obj)) - elif obj >= -2**(64 - 1): - fp.write(b"\xd3" + struct.pack(">q", obj)) - else: - raise UnsupportedTypeException("huge signed int") - else: - if obj <= 127: - fp.write(struct.pack("B", obj)) - elif obj <= 2**8 - 1: - fp.write(b"\xcc" + struct.pack("B", obj)) - elif obj <= 2**16 - 1: - fp.write(b"\xcd" + struct.pack(">H", obj)) - elif obj <= 2**32 - 1: - fp.write(b"\xce" + struct.pack(">I", obj)) - elif obj <= 2**64 - 1: - fp.write(b"\xcf" + struct.pack(">Q", obj)) - else: - raise UnsupportedTypeException("huge unsigned int") - - -def _pack_nil(obj, fp, options): - fp.write(b"\xc0") - - -def _pack_boolean(obj, fp, options): - fp.write(b"\xc3" if obj else b"\xc2") - - -def _pack_float(obj, fp, options): - float_precision = options.get('force_float_precision', _float_precision) - - if float_precision == "double": - fp.write(b"\xcb" + struct.pack(">d", obj)) - elif float_precision == "single": - fp.write(b"\xca" + struct.pack(">f", obj)) - else: - raise ValueError("invalid float precision") - - -def _pack_string(obj, fp, options): - obj = obj.encode('utf-8') - if len(obj) <= 31: - fp.write(struct.pack("B", 0xa0 | len(obj)) + obj) - elif len(obj) <= 2**8 - 1: - fp.write(b"\xd9" + struct.pack("B", len(obj)) + obj) - elif len(obj) <= 2**16 - 1: - fp.write(b"\xda" + struct.pack(">H", len(obj)) + obj) - elif len(obj) <= 2**32 - 1: - fp.write(b"\xdb" + struct.pack(">I", len(obj)) + obj) - else: - raise UnsupportedTypeException("huge string") - - -def _pack_binary(obj, fp, options): - if len(obj) <= 2**8 - 1: - fp.write(b"\xc4" + struct.pack("B", len(obj)) + obj) - elif len(obj) <= 2**16 - 1: - fp.write(b"\xc5" + struct.pack(">H", len(obj)) + obj) - elif len(obj) <= 2**32 - 1: - fp.write(b"\xc6" + struct.pack(">I", len(obj)) + obj) - else: - raise UnsupportedTypeException("huge binary string") - - -def _pack_oldspec_raw(obj, fp, options): - if len(obj) <= 31: - fp.write(struct.pack("B", 0xa0 | len(obj)) + obj) - elif len(obj) <= 2**16 - 1: - fp.write(b"\xda" + struct.pack(">H", len(obj)) + obj) - elif len(obj) <= 2**32 - 1: - fp.write(b"\xdb" + struct.pack(">I", len(obj)) + obj) - else: - raise UnsupportedTypeException("huge raw string") - - -def _pack_ext(obj, fp, options): - if len(obj.data) == 1: - fp.write(b"\xd4" + struct.pack("B", obj.type & 0xff) + obj.data) - elif len(obj.data) == 2: - fp.write(b"\xd5" + struct.pack("B", obj.type & 0xff) + obj.data) - elif len(obj.data) == 4: - fp.write(b"\xd6" + struct.pack("B", obj.type & 0xff) + obj.data) - elif len(obj.data) == 8: - fp.write(b"\xd7" + struct.pack("B", obj.type & 0xff) + obj.data) - elif len(obj.data) == 16: - fp.write(b"\xd8" + struct.pack("B", obj.type & 0xff) + obj.data) - elif len(obj.data) <= 2**8 - 1: - fp.write(b"\xc7" + - struct.pack("BB", len(obj.data), obj.type & 0xff) + obj.data) - elif len(obj.data) <= 2**16 - 1: - fp.write(b"\xc8" + - struct.pack(">HB", len(obj.data), obj.type & 0xff) + obj.data) - elif len(obj.data) <= 2**32 - 1: - fp.write(b"\xc9" + - struct.pack(">IB", len(obj.data), obj.type & 0xff) + obj.data) - else: - raise UnsupportedTypeException("huge ext data") - - -def _pack_array(obj, fp, options): - if len(obj) <= 15: - fp.write(struct.pack("B", 0x90 | len(obj))) - elif len(obj) <= 2**16 - 1: - fp.write(b"\xdc" + struct.pack(">H", len(obj))) - elif len(obj) <= 2**32 - 1: - fp.write(b"\xdd" + struct.pack(">I", len(obj))) - else: - raise UnsupportedTypeException("huge array") - - for e in obj: - pack(e, fp, **options) - - -def _pack_map(obj, fp, options): - if len(obj) <= 15: - fp.write(struct.pack("B", 0x80 | len(obj))) - elif len(obj) <= 2**16 - 1: - fp.write(b"\xde" + struct.pack(">H", len(obj))) - elif len(obj) <= 2**32 - 1: - fp.write(b"\xdf" + struct.pack(">I", len(obj))) - else: - raise UnsupportedTypeException("huge array") - - for k, v in obj.items(): - pack(k, fp, **options) - pack(v, fp, **options) - -######################################## - - -# Pack for Python 2, with 'unicode' type, 'str' type, and 'long' type -def _pack2(obj, fp, **options): - """ - Serialize a Python object into MessagePack bytes. - - Args: - obj: a Python object - fp: a .write()-supporting file-like object - - Kwargs: - ext_handlers (dict): dictionary of Ext handlers, mapping a custom type - to a callable that packs an instance of the type - into an Ext object - force_float_precision (str): "single" to force packing floats as - IEEE-754 single-precision floats, - "double" to force packing floats as - IEEE-754 double-precision floats. - - Returns: - None. - - Raises: - UnsupportedType(PackException): - Object type not supported for packing. - - Example: - >>> f = open('test.bin', 'wb') - >>> umsgpack.pack({u"compact": True, u"schema": 0}, f) - >>> - """ - global compatibility - - ext_handlers = options.get("ext_handlers") - - if obj is None: - _pack_nil(obj, fp, options) - elif ext_handlers and obj.__class__ in ext_handlers: - _pack_ext(ext_handlers[obj.__class__](obj), fp, options) - elif isinstance(obj, bool): - _pack_boolean(obj, fp, options) - elif isinstance(obj, (int, long)): - _pack_integer(obj, fp, options) - elif isinstance(obj, float): - _pack_float(obj, fp, options) - elif compatibility and isinstance(obj, unicode): - _pack_oldspec_raw(bytes(obj), fp, options) - elif compatibility and isinstance(obj, bytes): - _pack_oldspec_raw(obj, fp, options) - elif isinstance(obj, unicode): - _pack_string(obj, fp, options) - elif isinstance(obj, str): - _pack_binary(obj, fp, options) - elif isinstance(obj, (list, tuple)): - _pack_array(obj, fp, options) - elif isinstance(obj, dict): - _pack_map(obj, fp, options) - elif isinstance(obj, Ext): - _pack_ext(obj, fp, options) - elif ext_handlers: - # Linear search for superclass - t = next((t for t in ext_handlers.keys() if isinstance(obj, t)), None) - if t: - _pack_ext(ext_handlers[t](obj), fp, options) - else: - raise UnsupportedTypeException( - "unsupported type: %s" % str(type(obj))) - else: - raise UnsupportedTypeException("unsupported type: %s" % str(type(obj))) - - -# Pack for Python 3, with unicode 'str' type, 'bytes' type, and no 'long' type -def _pack3(obj, fp, **options): - """ - Serialize a Python object into MessagePack bytes. - - Args: - obj: a Python object - fp: a .write()-supporting file-like object - - Kwargs: - ext_handlers (dict): dictionary of Ext handlers, mapping a custom type - to a callable that packs an instance of the type - into an Ext object - force_float_precision (str): "single" to force packing floats as - IEEE-754 single-precision floats, - "double" to force packing floats as - IEEE-754 double-precision floats. - - Returns: - None. - - Raises: - UnsupportedType(PackException): - Object type not supported for packing. - - Example: - >>> f = open('test.bin', 'wb') - >>> umsgpack.pack({u"compact": True, u"schema": 0}, f) - >>> - """ - global compatibility - - ext_handlers = options.get("ext_handlers") - - if obj is None: - _pack_nil(obj, fp, options) - elif ext_handlers and obj.__class__ in ext_handlers: - _pack_ext(ext_handlers[obj.__class__](obj), fp, options) - elif isinstance(obj, bool): - _pack_boolean(obj, fp, options) - elif isinstance(obj, int): - _pack_integer(obj, fp, options) - elif isinstance(obj, float): - _pack_float(obj, fp, options) - elif compatibility and isinstance(obj, str): - _pack_oldspec_raw(obj.encode('utf-8'), fp, options) - elif compatibility and isinstance(obj, bytes): - _pack_oldspec_raw(obj, fp, options) - elif isinstance(obj, str): - _pack_string(obj, fp, options) - elif isinstance(obj, bytes): - _pack_binary(obj, fp, options) - elif isinstance(obj, (list, tuple)): - _pack_array(obj, fp, options) - elif isinstance(obj, dict): - _pack_map(obj, fp, options) - elif isinstance(obj, Ext): - _pack_ext(obj, fp, options) - elif ext_handlers: - # Linear search for superclass - t = next((t for t in ext_handlers.keys() if isinstance(obj, t)), None) - if t: - _pack_ext(ext_handlers[t](obj), fp, options) - else: - raise UnsupportedTypeException( - "unsupported type: %s" % str(type(obj))) - else: - raise UnsupportedTypeException( - "unsupported type: %s" % str(type(obj))) - - -def _packb2(obj, **options): - """ - Serialize a Python object into MessagePack bytes. - - Args: - obj: a Python object - - Kwargs: - ext_handlers (dict): dictionary of Ext handlers, mapping a custom type - to a callable that packs an instance of the type - into an Ext object - force_float_precision (str): "single" to force packing floats as - IEEE-754 single-precision floats, - "double" to force packing floats as - IEEE-754 double-precision floats. - - Returns: - A 'str' containing serialized MessagePack bytes. - - Raises: - UnsupportedType(PackException): - Object type not supported for packing. - - Example: - >>> umsgpack.packb({u"compact": True, u"schema": 0}) - '\x82\xa7compact\xc3\xa6schema\x00' - >>> - """ - fp = io.BytesIO() - _pack2(obj, fp, **options) - return fp.getvalue() - - -def _packb3(obj, **options): - """ - Serialize a Python object into MessagePack bytes. - - Args: - obj: a Python object - - Kwargs: - ext_handlers (dict): dictionary of Ext handlers, mapping a custom type - to a callable that packs an instance of the type - into an Ext object - force_float_precision (str): "single" to force packing floats as - IEEE-754 single-precision floats, - "double" to force packing floats as - IEEE-754 double-precision floats. - - Returns: - A 'bytes' containing serialized MessagePack bytes. - - Raises: - UnsupportedType(PackException): - Object type not supported for packing. - - Example: - >>> umsgpack.packb({u"compact": True, u"schema": 0}) - b'\x82\xa7compact\xc3\xa6schema\x00' - >>> - """ - fp = io.BytesIO() - _pack3(obj, fp, **options) - return fp.getvalue() - -############################################################################# -# Unpacking -############################################################################# - - -def _read_except(fp, n): - data = fp.read(n) - if len(data) < n: - raise InsufficientDataException() - return data - - -def _unpack_integer(code, fp, options): - if (ord(code) & 0xe0) == 0xe0: - return struct.unpack("b", code)[0] - elif code == b'\xd0': - return struct.unpack("b", _read_except(fp, 1))[0] - elif code == b'\xd1': - return struct.unpack(">h", _read_except(fp, 2))[0] - elif code == b'\xd2': - return struct.unpack(">i", _read_except(fp, 4))[0] - elif code == b'\xd3': - return struct.unpack(">q", _read_except(fp, 8))[0] - elif (ord(code) & 0x80) == 0x00: - return struct.unpack("B", code)[0] - elif code == b'\xcc': - return struct.unpack("B", _read_except(fp, 1))[0] - elif code == b'\xcd': - return struct.unpack(">H", _read_except(fp, 2))[0] - elif code == b'\xce': - return struct.unpack(">I", _read_except(fp, 4))[0] - elif code == b'\xcf': - return struct.unpack(">Q", _read_except(fp, 8))[0] - raise Exception("logic error, not int: 0x%02x" % ord(code)) - - -def _unpack_reserved(code, fp, options): - if code == b'\xc1': - raise ReservedCodeException( - "encountered reserved code: 0x%02x" % ord(code)) - raise Exception( - "logic error, not reserved code: 0x%02x" % ord(code)) - - -def _unpack_nil(code, fp, options): - if code == b'\xc0': - return None - raise Exception("logic error, not nil: 0x%02x" % ord(code)) - - -def _unpack_boolean(code, fp, options): - if code == b'\xc2': - return False - elif code == b'\xc3': - return True - raise Exception("logic error, not boolean: 0x%02x" % ord(code)) - - -def _unpack_float(code, fp, options): - if code == b'\xca': - return struct.unpack(">f", _read_except(fp, 4))[0] - elif code == b'\xcb': - return struct.unpack(">d", _read_except(fp, 8))[0] - raise Exception("logic error, not float: 0x%02x" % ord(code)) - - -def _unpack_string(code, fp, options): - if (ord(code) & 0xe0) == 0xa0: - length = ord(code) & ~0xe0 - elif code == b'\xd9': - length = struct.unpack("B", _read_except(fp, 1))[0] - elif code == b'\xda': - length = struct.unpack(">H", _read_except(fp, 2))[0] - elif code == b'\xdb': - length = struct.unpack(">I", _read_except(fp, 4))[0] - else: - raise Exception("logic error, not string: 0x%02x" % ord(code)) - - # Always return raw bytes in compatibility mode - global compatibility - if compatibility: - return _read_except(fp, length) - - data = _read_except(fp, length) - try: - return bytes.decode(data, 'utf-8') - except UnicodeDecodeError: - if options.get("allow_invalid_utf8"): - return InvalidString(data) - raise InvalidStringException("unpacked string is invalid utf-8") - - -def _unpack_binary(code, fp, options): - if code == b'\xc4': - length = struct.unpack("B", _read_except(fp, 1))[0] - elif code == b'\xc5': - length = struct.unpack(">H", _read_except(fp, 2))[0] - elif code == b'\xc6': - length = struct.unpack(">I", _read_except(fp, 4))[0] - else: - raise Exception("logic error, not binary: 0x%02x" % ord(code)) - - return _read_except(fp, length) - - -def _unpack_ext(code, fp, options): - if code == b'\xd4': - length = 1 - elif code == b'\xd5': - length = 2 - elif code == b'\xd6': - length = 4 - elif code == b'\xd7': - length = 8 - elif code == b'\xd8': - length = 16 - elif code == b'\xc7': - length = struct.unpack("B", _read_except(fp, 1))[0] - elif code == b'\xc8': - length = struct.unpack(">H", _read_except(fp, 2))[0] - elif code == b'\xc9': - length = struct.unpack(">I", _read_except(fp, 4))[0] - else: - raise Exception("logic error, not ext: 0x%02x" % ord(code)) - - ext = Ext(ord(_read_except(fp, 1)), _read_except(fp, length)) - - # Unpack with ext handler, if we have one - ext_handlers = options.get("ext_handlers") - if ext_handlers and ext.type in ext_handlers: - ext = ext_handlers[ext.type](ext) - - return ext - - -def _unpack_array(code, fp, options): - if (ord(code) & 0xf0) == 0x90: - length = (ord(code) & ~0xf0) - elif code == b'\xdc': - length = struct.unpack(">H", _read_except(fp, 2))[0] - elif code == b'\xdd': - length = struct.unpack(">I", _read_except(fp, 4))[0] - else: - raise Exception("logic error, not array: 0x%02x" % ord(code)) - - return [_unpack(fp, options) for _ in xrange(length)] - - -def _deep_list_to_tuple(obj): - if isinstance(obj, list): - return tuple([_deep_list_to_tuple(e) for e in obj]) - return obj - - -def _unpack_map(code, fp, options): - if (ord(code) & 0xf0) == 0x80: - length = (ord(code) & ~0xf0) - elif code == b'\xde': - length = struct.unpack(">H", _read_except(fp, 2))[0] - elif code == b'\xdf': - length = struct.unpack(">I", _read_except(fp, 4))[0] - else: - raise Exception("logic error, not map: 0x%02x" % ord(code)) - - d = {} if not options.get('use_ordered_dict') \ - else collections.OrderedDict() - for _ in xrange(length): - # Unpack key - k = _unpack(fp, options) - - if isinstance(k, list): - # Attempt to convert list into a hashable tuple - k = _deep_list_to_tuple(k) - elif not isinstance(k, collections.Hashable): - raise UnhashableKeyException( - "encountered unhashable key: %s, %s" % (str(k), str(type(k)))) - elif k in d: - raise DuplicateKeyException( - "encountered duplicate key: %s, %s" % (str(k), str(type(k)))) - - # Unpack value - v = _unpack(fp, options) - - try: - d[k] = v - except TypeError: - raise UnhashableKeyException( - "encountered unhashable key: %s" % str(k)) - return d - - -def _unpack(fp, options): - code = _read_except(fp, 1) - return _unpack_dispatch_table[code](code, fp, options) - -######################################## - - -def _unpack2(fp, **options): - """ - Deserialize MessagePack bytes into a Python object. - - Args: - fp: a .read()-supporting file-like object - - Kwargs: - ext_handlers (dict): dictionary of Ext handlers, mapping integer Ext - type to a callable that unpacks an instance of - Ext into an object - use_ordered_dict (bool): unpack maps into OrderedDict, instead of - unordered dict (default False) - allow_invalid_utf8 (bool): unpack invalid strings into instances of - InvalidString, for access to the bytes - (default False) - - Returns: - A Python object. - - Raises: - InsufficientDataException(UnpackException): - Insufficient data to unpack the serialized object. - InvalidStringException(UnpackException): - Invalid UTF-8 string encountered during unpacking. - ReservedCodeException(UnpackException): - Reserved code encountered during unpacking. - UnhashableKeyException(UnpackException): - Unhashable key encountered during map unpacking. - The serialized map cannot be deserialized into a Python dictionary. - DuplicateKeyException(UnpackException): - Duplicate key encountered during map unpacking. - - Example: - >>> f = open('test.bin', 'rb') - >>> umsgpack.unpackb(f) - {u'compact': True, u'schema': 0} - >>> - """ - return _unpack(fp, options) - - -def _unpack3(fp, **options): - """ - Deserialize MessagePack bytes into a Python object. - - Args: - fp: a .read()-supporting file-like object - - Kwargs: - ext_handlers (dict): dictionary of Ext handlers, mapping integer Ext - type to a callable that unpacks an instance of - Ext into an object - use_ordered_dict (bool): unpack maps into OrderedDict, instead of - unordered dict (default False) - allow_invalid_utf8 (bool): unpack invalid strings into instances of - InvalidString, for access to the bytes - (default False) - - Returns: - A Python object. - - Raises: - InsufficientDataException(UnpackException): - Insufficient data to unpack the serialized object. - InvalidStringException(UnpackException): - Invalid UTF-8 string encountered during unpacking. - ReservedCodeException(UnpackException): - Reserved code encountered during unpacking. - UnhashableKeyException(UnpackException): - Unhashable key encountered during map unpacking. - The serialized map cannot be deserialized into a Python dictionary. - DuplicateKeyException(UnpackException): - Duplicate key encountered during map unpacking. - - Example: - >>> f = open('test.bin', 'rb') - >>> umsgpack.unpackb(f) - {'compact': True, 'schema': 0} - >>> - """ - return _unpack(fp, options) - - -# For Python 2, expects a str object -def _unpackb2(s, **options): - """ - Deserialize MessagePack bytes into a Python object. - - Args: - s: a 'str' or 'bytearray' containing serialized MessagePack bytes - - Kwargs: - ext_handlers (dict): dictionary of Ext handlers, mapping integer Ext - type to a callable that unpacks an instance of - Ext into an object - use_ordered_dict (bool): unpack maps into OrderedDict, instead of - unordered dict (default False) - allow_invalid_utf8 (bool): unpack invalid strings into instances of - InvalidString, for access to the bytes - (default False) - - Returns: - A Python object. - - Raises: - TypeError: - Packed data type is neither 'str' nor 'bytearray'. - InsufficientDataException(UnpackException): - Insufficient data to unpack the serialized object. - InvalidStringException(UnpackException): - Invalid UTF-8 string encountered during unpacking. - ReservedCodeException(UnpackException): - Reserved code encountered during unpacking. - UnhashableKeyException(UnpackException): - Unhashable key encountered during map unpacking. - The serialized map cannot be deserialized into a Python dictionary. - DuplicateKeyException(UnpackException): - Duplicate key encountered during map unpacking. - - Example: - >>> umsgpack.unpackb(b'\x82\xa7compact\xc3\xa6schema\x00') - {u'compact': True, u'schema': 0} - >>> - """ - if not isinstance(s, (str, bytearray)): - raise TypeError("packed data must be type 'str' or 'bytearray'") - return _unpack(io.BytesIO(s), options) - - -# For Python 3, expects a bytes object -def _unpackb3(s, **options): - """ - Deserialize MessagePack bytes into a Python object. - - Args: - s: a 'bytes' or 'bytearray' containing serialized MessagePack bytes - - Kwargs: - ext_handlers (dict): dictionary of Ext handlers, mapping integer Ext - type to a callable that unpacks an instance of - Ext into an object - use_ordered_dict (bool): unpack maps into OrderedDict, instead of - unordered dict (default False) - allow_invalid_utf8 (bool): unpack invalid strings into instances of - InvalidString, for access to the bytes - (default False) - - Returns: - A Python object. - - Raises: - TypeError: - Packed data type is neither 'bytes' nor 'bytearray'. - InsufficientDataException(UnpackException): - Insufficient data to unpack the serialized object. - InvalidStringException(UnpackException): - Invalid UTF-8 string encountered during unpacking. - ReservedCodeException(UnpackException): - Reserved code encountered during unpacking. - UnhashableKeyException(UnpackException): - Unhashable key encountered during map unpacking. - The serialized map cannot be deserialized into a Python dictionary. - DuplicateKeyException(UnpackException): - Duplicate key encountered during map unpacking. - - Example: - >>> umsgpack.unpackb(b'\x82\xa7compact\xc3\xa6schema\x00') - {'compact': True, 'schema': 0} - >>> - """ - if not isinstance(s, (bytes, bytearray)): - raise TypeError("packed data must be type 'bytes' or 'bytearray'") - return _unpack(io.BytesIO(s), options) - -############################################################################# -# Module Initialization -############################################################################# - - -def __init(): - # pylint: disable=global-variable-undefined - - global pack - global packb - global unpack - global unpackb - global dump - global dumps - global load - global loads - global compatibility - global _float_precision - global _unpack_dispatch_table - global xrange - - # Compatibility mode for handling strings/bytes with the old specification - compatibility = False - - # Auto-detect system float precision - if sys.float_info.mant_dig == 53: - _float_precision = "double" - else: - _float_precision = "single" - - # Map packb and unpackb to the appropriate version - if sys.version_info[0] == 3: - pack = _pack3 - packb = _packb3 - dump = _pack3 - dumps = _packb3 - unpack = _unpack3 - unpackb = _unpackb3 - load = _unpack3 - loads = _unpackb3 - xrange = range # pylint: disable=redefined-builtin - else: - pack = _pack2 - packb = _packb2 - dump = _pack2 - dumps = _packb2 - unpack = _unpack2 - unpackb = _unpackb2 - load = _unpack2 - loads = _unpackb2 - - # Build a dispatch table for fast lookup of unpacking function - - _unpack_dispatch_table = {} - # Fix uint - for code in range(0, 0x7f + 1): - _unpack_dispatch_table[struct.pack("B", code)] = _unpack_integer - # Fix map - for code in range(0x80, 0x8f + 1): - _unpack_dispatch_table[struct.pack("B", code)] = _unpack_map - # Fix array - for code in range(0x90, 0x9f + 1): - _unpack_dispatch_table[struct.pack("B", code)] = _unpack_array - # Fix str - for code in range(0xa0, 0xbf + 1): - _unpack_dispatch_table[struct.pack("B", code)] = _unpack_string - # Nil - _unpack_dispatch_table[b'\xc0'] = _unpack_nil - # Reserved - _unpack_dispatch_table[b'\xc1'] = _unpack_reserved - # Boolean - _unpack_dispatch_table[b'\xc2'] = _unpack_boolean - _unpack_dispatch_table[b'\xc3'] = _unpack_boolean - # Bin - for code in range(0xc4, 0xc6 + 1): - _unpack_dispatch_table[struct.pack("B", code)] = _unpack_binary - # Ext - for code in range(0xc7, 0xc9 + 1): - _unpack_dispatch_table[struct.pack("B", code)] = _unpack_ext - # Float - _unpack_dispatch_table[b'\xca'] = _unpack_float - _unpack_dispatch_table[b'\xcb'] = _unpack_float - # Uint - for code in range(0xcc, 0xcf + 1): - _unpack_dispatch_table[struct.pack("B", code)] = _unpack_integer - # Int - for code in range(0xd0, 0xd3 + 1): - _unpack_dispatch_table[struct.pack("B", code)] = _unpack_integer - # Fixext - for code in range(0xd4, 0xd8 + 1): - _unpack_dispatch_table[struct.pack("B", code)] = _unpack_ext - # String - for code in range(0xd9, 0xdb + 1): - _unpack_dispatch_table[struct.pack("B", code)] = _unpack_string - # Array - _unpack_dispatch_table[b'\xdc'] = _unpack_array - _unpack_dispatch_table[b'\xdd'] = _unpack_array - # Map - _unpack_dispatch_table[b'\xde'] = _unpack_map - _unpack_dispatch_table[b'\xdf'] = _unpack_map - # Negative fixint - for code in range(0xe0, 0xff + 1): - _unpack_dispatch_table[struct.pack("B", code)] = _unpack_integer - - -__init() diff --git a/src/helper_ackPayload.py b/src/helper_ackPayload.py deleted file mode 100644 index 1c5ddf98..00000000 --- a/src/helper_ackPayload.py +++ /dev/null @@ -1,51 +0,0 @@ -""" -This module is for generating ack payload -""" - -from binascii import hexlify -from struct import pack - -import helper_random -import highlevelcrypto -from addresses import encodeVarint - - -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 - # Generate a dummy privkey and derive the pubkey - dummyPubKeyHex = highlevelcrypto.privToPub( - hexlify(highlevelcrypto.randomBytes(32))) - # Generate a dummy message of random length - # (the smallest possible standard-formatted message is 234 bytes) - dummyMessage = highlevelcrypto.randomBytes( - helper_random.randomrandrange(234, 801)) - # Encrypt the message using standard BM encryption (ECIES) - ackdata = highlevelcrypto.encrypt(dummyMessage, dummyPubKeyHex) - acktype = 2 # message - version = 1 - - elif stealthLevel == 1: # Basic privacy payload (random getpubkey) - ackdata = highlevelcrypto.randomBytes(32) - acktype = 0 # getpubkey - version = 4 - - else: # Minimum viable payload (non stealth) - ackdata = highlevelcrypto.randomBytes(32) - acktype = 2 # message - version = 1 - - 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..e14e932c --- /dev/null +++ b/src/helper_bootstrap.py @@ -0,0 +1,93 @@ +import shared +import socket +import defaultKnownNodes +import pickle +import time + +from debug import logger +import socks + +def knownNodes(): + try: + # We shouldn't have to use the shared.knownNodesLock because this had + # better be the only thread accessing knownNodes right now. + pickleFile = open(shared.appdata + 'knownnodes.dat', 'rb') + loadedKnownNodes = pickle.load(pickleFile) + pickleFile.close() + # The old format of storing knownNodes was as a 'host: (port, time)' + # mapping. The new format is as 'Peer: time' pairs. If we loaded + # data in the old format, transform it to the new style. + for stream, nodes in loadedKnownNodes.items(): + shared.knownNodes[stream] = {} + for node_tuple in nodes.items(): + try: + host, (port, lastseen) = node_tuple + peer = shared.Peer(host, port) + except: + peer, lastseen = node_tuple + shared.knownNodes[stream][peer] = lastseen + except: + shared.knownNodes = defaultKnownNodes.createDefaultKnownNodes(shared.appdata) + # your own onion address, if setup + if shared.config.has_option('bitmessagesettings', 'onionhostname') and ".onion" in shared.config.get('bitmessagesettings', 'onionhostname'): + shared.knownNodes[1][shared.Peer(shared.config.get('bitmessagesettings', 'onionhostname'), shared.config.getint('bitmessagesettings', 'onionport'))] = int(time.time()) + if shared.config.getint('bitmessagesettings', 'settingsversion') > 10: + logger.error('Bitmessage cannot read future versions of the keys file (keys.dat). Run the newer version of Bitmessage.') + raise SystemExit + +def dns(): + # DNS bootstrap. This could be programmed to use the SOCKS proxy to do the + # DNS lookup some day but for now we will just rely on the entries in + # defaultKnownNodes.py. Hopefully either they are up to date or the user + # has run Bitmessage recently without SOCKS turned on and received good + # bootstrap nodes using that method. + if shared.config.get('bitmessagesettings', 'socksproxytype') == 'none': + try: + for item in socket.getaddrinfo('bootstrap8080.bitmessage.org', 80): + logger.info('Adding ' + item[4][0] + ' to knownNodes based on DNS bootstrap method') + shared.knownNodes[1][shared.Peer(item[4][0], 8080)] = int(time.time()) + except: + logger.error('bootstrap8080.bitmessage.org DNS bootstrapping failed.') + try: + for item in socket.getaddrinfo('bootstrap8444.bitmessage.org', 80): + logger.info ('Adding ' + item[4][0] + ' to knownNodes based on DNS bootstrap method') + shared.knownNodes[1][shared.Peer(item[4][0], 8444)] = int(time.time()) + except: + logger.error('bootstrap8444.bitmessage.org DNS bootstrapping failed.') + elif shared.config.get('bitmessagesettings', 'socksproxytype') == 'SOCKS5': + shared.knownNodes[1][shared.Peer('quzwelsuziwqgpt2.onion', 8444)] = int(time.time()) + 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 = shared.config.get( + 'bitmessagesettings', 'sockshostname') + socksport = shared.config.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 shared.config.getboolean('bitmessagesettings', 'socksauthentication'): + socksusername = shared.config.get( + 'bitmessagesettings', 'socksusername') + sockspassword = shared.config.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) + if ip is not None: + logger.info ('Adding ' + ip + ' to knownNodes based on SOCKS DNS bootstrap method') + shared.knownNodes[1][shared.Peer(ip, port)] = time.time() + 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..b6516c05 --- /dev/null +++ b/src/helper_generic.py @@ -0,0 +1,50 @@ +import socket +import sys +from binascii import hexlify, unhexlify + +from debug import logger +import shared + +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 signal_handler(signal, frame): + logger.error("Got signal %i", signal) + if shared.safeConfigGetBoolean('bitmessagesettings', 'daemon'): + shared.doCleanShutdown() + else: + 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 + else: + 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 + 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..a3ad9755 100644 --- a/src/helper_inbox.py +++ b/src/helper_inbox.py @@ -1,35 +1,16 @@ -"""Helper Inbox performs inbox messages related operations""" - -import queues -from helper_sql import sqlExecute, sqlQuery - +from helper_sql import * +import shared 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 + #shared.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) - - + shared.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 + return queryReturn[0][0] != 0 \ No newline at end of file diff --git a/src/helper_msgcoding.py b/src/helper_msgcoding.py index 05fa1c1b..0aa1cda9 100644 --- a/src/helper_msgcoding.py +++ b/src/helper_msgcoding.py @@ -1,49 +1,17 @@ -""" -Message encoding end decoding functions -""" +#!/usr/bin/python2.7 -import string +import msgpack import zlib -import messagetypes -from bmconfigparser import config from debug import logger -from tr import _translate - -try: - import msgpack -except ImportError: - try: - import umsgpack as msgpack - except ImportError: - import fallback.umsgpack.umsgpack as msgpack BITMESSAGE_ENCODING_IGNORE = 0 BITMESSAGE_ENCODING_TRIVIAL = 1 BITMESSAGE_ENCODING_SIMPLE = 2 -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 - +BITMESSAGE.ENCODING_EXTENDED = 3 class MsgEncode(object): - """Message encoder class""" - def __init__(self, message, encoding=BITMESSAGE_ENCODING_SIMPLE): + def __init__(self, message, encoding = BITMESSAGE_ENCODING_SIMPLE): self.data = None self.encoding = encoding self.length = 0 @@ -53,107 +21,66 @@ class MsgEncode(object): self.encodeSimple(message) elif self.encoding == BITMESSAGE_ENCODING_TRIVIAL: self.encodeTrivial(message) - else: - 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) + self.data = zlib.compress(msgpack.dumps({"": "message", "subject": message['subject'], "message": ['body']}), 9) except zlib.error: - logger.error("Error compressing message") - raise MsgEncodeException("Error compressing message") + logger.error ("Error compressing message") + raise except msgpack.exceptions.PackException: - logger.error("Error msgpacking message") - raise MsgEncodeException("Error msgpacking message") + logger.error ("Error msgpacking message") + raise 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.subject = _translate("MsgDecode", "Unknown encoding") + return def decodeExtended(self, data): - """Handle extended encoding""" - dc = zlib.decompressobj() - tmp = "" - while len(tmp) <= config.safeGetInt("zlib", "maxsize"): - try: - got = dc.decompress( - data, config.safeGetInt("zlib", "maxsize") - + 1 - len(tmp)) - # EOF - if got == "": - break - tmp += got - data = dc.unconsumed_tail - except zlib.error: - logger.error("Error decompressing message") - raise MsgDecodeException("Error decompressing message") - else: - raise DecompressionSizeException(len(tmp)) - try: - tmp = msgpack.loads(tmp) + tmp = msgpack.loads(zlib.decompress(data)) + except zlib.error: + logger.error ("Error decompressing message") + raise except (msgpack.exceptions.UnpackException, msgpack.exceptions.ExtraData): - logger.error("Error msgunpacking message") - raise MsgDecodeException("Error msgunpacking message") - + logger.error ("Error msgunpacking message") + raise try: - msgType = tmp[""] - except KeyError: - logger.error("Message type missing") - raise MsgDecodeException("Message type missing") - - msgObj = messagetypes.constructObject(tmp) - if msgObj is None: - raise MsgDecodeException("Malformed message") - try: - msgObj.process() - except: # noqa:E722 - raise MsgDecodeException("Malformed message") - if msgType == "message": - self.subject = msgObj.subject - self.body = msgObj.body + if tmp[""] == "message": + self.body = tmp["body"] + self.subject = tmp["subject"] + except: + logger.error ("Malformed message") + raise def decodeSimple(self, data): - """Handle simple encoding""" bodyPositionIndex = string.find(data, '\nBody:') if bodyPositionIndex > 1: - subject = data[8:bodyPositionIndex] + 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 = data[bodyPositionIndex + 6:] + body = message[bodyPositionIndex + 6:] else: subject = '' - body = data + body = message # Throw away any extra lines (headers) after the subject. if subject: subject = subject.splitlines()[0] self.subject = subject - self.body = body + self.message = body diff --git a/src/helper_random.py b/src/helper_random.py deleted file mode 100644 index e6da707e..00000000 --- a/src/helper_random.py +++ /dev/null @@ -1,61 +0,0 @@ -"""Convenience functions for random operations. Not suitable for security / cryptography operations.""" - -import random - - -NoneType = type(None) - - -def seed(): - """Initialize random number generator""" - random.seed() - - -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..d27401cf 100644 --- a/src/helper_sql.py +++ b/src/helper_sql.py @@ -1,153 +1,69 @@ -""" -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""" - # 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: - for i in range( - len(args) - idCount, len(args), - sqlExecuteChunked.chunkSize - (len(args) - idCount) - ): - chunk_slice = args[ - i:i + sqlExecuteChunked.chunkSize - (len(args) - idCount) - ] - sqlSubmitQueue.put( - sql_statement.format(','.join('?' * len(chunk_slice))) - ) - # first static args, and then iterative chunk - sqlSubmitQueue.put( - args[0:len(args) - idCount] + chunk_slice - ) - ret_val = sqlReturnQueue.get() - total_row_count += ret_val[1] - sqlSubmitQueue.put('commit') - return total_row_count - - -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..a194a574 100644 --- a/src/helper_startup.py +++ b/src/helper_startup.py @@ -1,373 +1,149 @@ -""" -Startup operations. -""" -# pylint: disable=too-many-branches,too-many-statements - -import ctypes -import logging -import os -import platform -import socket +import shared +import ConfigParser 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 -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 = shared.config.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(':') + shared.trustedPeer = shared.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') - - if config.safeGet('bitmessagesettings', 'settingsversion'): - logger.info('Loading config files from same directory as program.') + if shared.appdata: + shared.config.read(shared.appdata + 'keys.dat') + #shared.appdata must have been specified as a startup option. + try: + shared.config.get('bitmessagesettings', 'settingsversion') + print 'Loading config files from directory specified on startup: ' + shared.appdata 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. - 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) + except: + needToCreateKeysFile = True + + else: + shared.config.read(shared.lookupExeFolder() + 'keys.dat') + try: + shared.config.get('bitmessagesettings', 'settingsversion') + print 'Loading config files from same directory as program.' + needToCreateKeysFile = False + shared.appdata = shared.lookupExeFolder() + except: + # Could not load the keys.dat file in the program directory. Perhaps it + # is in the appdata directory. + shared.appdata = shared.lookupAppdataFolder() + shared.config.read(shared.appdata + 'keys.dat') + try: + shared.config.get('bitmessagesettings', 'settingsversion') + print 'Loading existing config files from', shared.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') + shared.config.add_section('bitmessagesettings') + shared.config.set('bitmessagesettings', 'settingsversion', '10') + shared.config.set('bitmessagesettings', 'port', '8444') + shared.config.set( + 'bitmessagesettings', 'timeformat', '%%a, %%d %%b %%Y %%I:%%M %%p') + shared.config.set('bitmessagesettings', 'blackwhitelist', 'black') + shared.config.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. + shared.config.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', '') + shared.config.set( + 'bitmessagesettings', 'minimizetotray', 'true') + shared.config.set( + 'bitmessagesettings', 'showtraynotifications', 'true') + shared.config.set('bitmessagesettings', 'startintray', 'false') + shared.config.set('bitmessagesettings', 'socksproxytype', 'none') + shared.config.set( + 'bitmessagesettings', 'sockshostname', 'localhost') + shared.config.set('bitmessagesettings', 'socksport', '9050') + shared.config.set( + 'bitmessagesettings', 'socksauthentication', 'false') + shared.config.set( + 'bitmessagesettings', 'sockslisten', 'false') + shared.config.set('bitmessagesettings', 'socksusername', '') + shared.config.set('bitmessagesettings', 'sockspassword', '') + shared.config.set('bitmessagesettings', 'keysencrypted', 'false') + shared.config.set( + 'bitmessagesettings', 'messagesencrypted', 'false') + shared.config.set('bitmessagesettings', 'defaultnoncetrialsperbyte', str( + shared.networkDefaultProofOfWorkNonceTrialsPerByte)) + shared.config.set('bitmessagesettings', 'defaultpayloadlengthextrabytes', str( + shared.networkDefaultPayloadLengthExtraBytes)) + shared.config.set('bitmessagesettings', 'minimizeonclose', 'false') + shared.config.set( + 'bitmessagesettings', 'maxacceptablenoncetrialsperbyte', '0') + shared.config.set( + 'bitmessagesettings', 'maxacceptablepayloadlengthextrabytes', '0') + shared.config.set('bitmessagesettings', 'dontconnect', 'true') + shared.config.set('bitmessagesettings', 'userlocale', 'system') + shared.config.set('bitmessagesettings', 'useidenticons', 'True') + shared.config.set('bitmessagesettings', 'identiconsuffix', ''.join(random.choice("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") for x in range(12))) # a twelve character pseudo-password to salt the identicons + shared.config.set('bitmessagesettings', 'replybelow', 'False') + shared.config.set('bitmessagesettings', 'maxdownloadrate', '0') + shared.config.set('bitmessagesettings', 'maxuploadrate', '0') + shared.config.set('bitmessagesettings', 'ttl', '367200') + + #start:UI setting to stop trying to send messages after X days/months + shared.config.set( + 'bitmessagesettings', 'stopresendingafterxdays', '') + shared.config.set( + 'bitmessagesettings', 'stopresendingafterxmonths', '') + #shared.config.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.') + shared.appdata = '' + print 'Creating new config files in same directory as program.' else: - logger.info('Creating new config files in %s', state.appdata) - if not os.path.exists(state.appdata): - os.makedirs(state.appdata) + print 'Creating new config files in', shared.appdata + if not os.path.exists(shared.appdata): + os.makedirs(shared.appdata) if not sys.platform.startswith('win'): os.umask(0o077) - config.save() - else: - updateConfig() - config_ready.set() + shared.writeKeysFile() + _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) + VER_THIS=StrictVersion(platform.version()) + if sys.platform[0:3]=="win": + return StrictVersion("5.1.2600")<=VER_THIS and StrictVersion("6.0.6000")>=VER_THIS + return False + except Exception as err: + print "Info: we could not tell whether your OS is limited to having very few half open connections because we couldn't interpret the platform version. Don't worry; we'll assume that it is not limited. This tends to occur on Raspberry Pis. :", err + return False diff --git a/src/helper_threading.py b/src/helper_threading.py new file mode 100644 index 00000000..599d297d --- /dev/null +++ b/src/helper_threading.py @@ -0,0 +1,10 @@ +import threading + +class StoppableThread(object): + def initStop(self): + self.stop = threading.Event() + self._stopped = False + + def stopThread(self): + self._stopped = True + self.stop.set() \ No newline at end of file diff --git a/src/highlevelcrypto.py b/src/highlevelcrypto.py index b83da2f3..c8b67e77 100644 --- a/src/highlevelcrypto.py +++ b/src/highlevelcrypto.py @@ -1,238 +1,93 @@ -""" -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. `_ -""" - -import hashlib -import os from binascii import hexlify - -try: - import pyelliptic - from fallback import RIPEMD160Hash - from pyelliptic import OpenSSL - from pyelliptic import arithmetic as a -except ImportError: - from pybitmessage import pyelliptic - from pybitmessage.fallback import RIPEMD160Hash - from pybitmessage.pyelliptic import OpenSSL - from pybitmessage.pyelliptic import arithmetic as a - - -__all__ = [ - 'decodeWalletImportFormat', 'deterministic_keys', - 'double_sha512', 'calculateInventoryHash', 'encodeWalletImportFormat', - 'encrypt', 'makeCryptor', 'pointMult', 'privToPub', 'randomBytes', - 'random_keys', 'sign', 'to_ripe', 'verify'] - - -# WIF (uses arithmetic ): -def decodeWalletImportFormat(WIFstring): - """ - Convert private key from base58 that's used in the config file to - 8-bit binary string. - """ - fullString = a.changebase(WIFstring, 58, 256) - privkey = fullString[:-4] - if fullString[-4:] != \ - hashlib.sha256(hashlib.sha256(privkey).digest()).digest()[:4]: - raise ValueError('Checksum failed') - elif privkey[0:1] == b'\x80': # checksum passed - return privkey[1:] - - raise ValueError('No hex 80 prefix') - - -# 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 -def encodeWalletImportFormat(privKey): - """ - Convert private key from binary 8-bit string into base58check WIF string. - """ - privKey = b'\x80' + privKey - checksum = hashlib.sha256(hashlib.sha256(privKey).digest()).digest()[0:4] - return a.changebase(privKey + checksum, 256, 58) - - -# Random - -def randomBytes(n): - """Get n random bytes""" - try: - return os.urandom(n) - except NotImplementedError: - return OpenSSL.rand(n) - - -# Hashes - -def _bm160(data): - """RIPEME160(SHA512(data)) -> bytes""" - return RIPEMD160Hash(hashlib.sha512(data).digest()).digest() - - -def to_ripe(signing_key, encryption_key): - """Convert two public keys to a ripe hash""" - return _bm160(signing_key + encryption_key) - - -def double_sha512(data): - """Binary double SHA512 digest""" - return hashlib.sha512(hashlib.sha512(data).digest()).digest() - - -def calculateInventoryHash(data): - """Calculate inventory hash from object data""" - return double_sha512(data)[:32] - - -# Keys - -def random_keys(): - """Return a pair of keys, private and public""" - priv = randomBytes(32) - pub = pointMult(priv) - return priv, pub - - -def deterministic_keys(passphrase, nonce): - """Generate keys from *passphrase* and *nonce* (encoded as varint)""" - priv = hashlib.sha512(passphrase + nonce).digest()[:32] - pub = pointMult(priv) - return priv, pub - - +import pyelliptic +from pyelliptic import arithmetic as a, OpenSSL +def makeCryptor(privkey): + private_key = a.changebase(privkey, 16, 256, minlen=32) + public_key = pointMult(private_key) + 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): + pubkey_bin = hexToPubkey(pubkey) + 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) +# 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) +# Decrypts message with an existing pyelliptic.ECC.ECC object +def decryptFast(msg,cryptor): + return cryptor.decrypt(msg) +# 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 + return makeCryptor(hexPrivkey).sign(msg, digest_alg=OpenSSL.EVP_ecdsa) # SHA1 + #return makeCryptor(hexPrivkey).sign(msg, digest_alg=OpenSSL.EVP_sha256) # SHA256. We should switch to this eventually. +# 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. + try: + sigVerifyPassed = makePubCryptor(hexPubkey).verify(sig,msg,digest_alg=OpenSSL.EVP_ecdsa) # 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) - - -# Encryption - -def makeCryptor(privkey, curve='secp256k1'): - """Return a private `.pyelliptic.ECC` instance""" - 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) - return cryptor - - -def makePubCryptor(pubkey): - """Return a public `.pyelliptic.ECC` instance""" - pubkey_bin = hexToPubkey(pubkey) - return pyelliptic.ECC(curve='secp256k1', pubkey=pubkey_bin) - - -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""" - return makeCryptor(hexPrivkey).decrypt(msg) - - -def decryptFast(msg, cryptor): - """Decrypts message with an existing `.pyelliptic.ECC` object""" - return cryptor.decrypt(msg) - - -# Signatures - -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 ( - # 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""" - # 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") - - try: - return makePubCryptor(hexPubkey).verify( - sig, msg, digest_alg=_choose_digest_alg(digestAlg)) - except: - return False + 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 deleted file mode 100644 index 5b739e84..00000000 --- a/src/inventory.py +++ /dev/null @@ -1,48 +0,0 @@ -"""The Inventory""" - -# TODO make this dynamic, and watch out for frozen, like with messagetypes -import storage.filesystem -import storage.sqlite -from bmconfigparser import config - - -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()))() - - -class Inventory: - """ - Inventory 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) - self.numberOfInventoryLookupsPerformed = 0 - - # cheap inheritance copied from asyncore - def __getattr__(self, attr): - if attr == "__contains__": - self.numberOfInventoryLookupsPerformed += 1 - try: - realRet = getattr(self._realInventory, attr) - except AttributeError: - 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 diff --git a/src/l10n.py b/src/l10n.py index fe02d3f4..a32abb84 100644 --- a/src/l10n.py +++ b/src/l10n.py @@ -1,33 +1,20 @@ -"""Localization helpers""" import logging -import os -import re -import sys import time -from six.moves import range +import shared -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 +39,82 @@ 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 shared.config.has_option('bitmessagesettings', 'timeformat'): + time_format = shared.config.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 - # 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 shared.config.has_option('bitmessagesettings', 'userlocale'): + userlocale = shared.config.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-android-live.py b/src/main-android-live.py deleted file mode 100644 index e1644436..00000000 --- a/src/main-android-live.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/main.py b/src/main.py deleted file mode 100644 index ce042b84..00000000 --- a/src/main.py +++ /dev/null @@ -1,31 +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 mockbm import multiqueue -import state - -from mockbm.class_addressGenerator import FakeAddressGenerator # noqa:E402 -from bitmessagekivy.mpybit import NavigateApp # noqa:E402 -from mockbm import network # noqa:E402 - -stats = network.stats -objectracker = network.objectracker - - -def main(): - """main method for starting threads""" - addressGeneratorThread = FakeAddressGenerator() - addressGeneratorThread.daemon = True - addressGeneratorThread.start() - state.kivyapp = NavigateApp() - state.kivyapp.run() - addressGeneratorThread.stopThread() - - -if __name__ == "__main__": - os.environ['INSTALL_TESTS'] = "True" - main() diff --git a/src/message_data_reader.py b/src/message_data_reader.py new file mode 100644 index 00000000..a99be336 --- /dev/null +++ b/src/message_data_reader.py @@ -0,0 +1,111 @@ +#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 shared +import string +from binascii import hexlify + +appdata = shared.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() + shared.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 deleted file mode 100644 index b9ddd1e9..00000000 --- a/src/messagetypes/__init__.py +++ /dev/null @@ -1,55 +0,0 @@ -import logging - -from importlib import import_module - -logger = logging.getLogger('default') - - -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): - 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 - 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 -else: - import os - for mod in os.listdir(os.path.dirname(__file__)): - if mod == "__init__.py": - continue - splitted = os.path.splitext(mod) - if splitted[1] != ".py": - continue - try: - import_module(".{}".format(splitted[0]), __name__) - except ImportError: - logger.error("Error importing %s", mod, exc_info=True) - else: - logger.debug("Imported message type module %s", mod) diff --git a/src/messagetypes/message.py b/src/messagetypes/message.py deleted file mode 100644 index 245c753f..00000000 --- a/src/messagetypes/message.py +++ /dev/null @@ -1,45 +0,0 @@ -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()} - - -class Message(MsgBase): - """Encapsulate a message""" - # pylint: disable=attribute-defined-outside-init - - 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"] - - def encode(self, data): - """Encode a message""" - super(Message, self).__init__() - self.data["subject"] = data.get("subject", "") - self.data["body"] = data.get("body", "") - - 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 deleted file mode 100644 index b3e96513..00000000 --- a/src/messagetypes/vote.py +++ /dev/null @@ -1,30 +0,0 @@ -import logging - -from .message import MsgBase - -logger = logging.getLogger('default') - - -class Vote(MsgBase): - """Module used to vote""" - - 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__() - try: - self.data["msgid"] = data["msgid"] - self.data["vote"] = data["vote"] - except KeyError as e: - logger.error("Missing key %s", e) - 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/namecoin.py b/src/namecoin.py index a16cb3d7..8e1a0b13 100644 --- a/src/namecoin.py +++ b/src/namecoin.py @@ -1,40 +1,44 @@ -""" -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 -import defaults -from addresses import decodeAddress -from bmconfigparser import config -from debug import logger -from tr import _translate # translate +import shared +import tr # translate 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.""" - +# This class handles the Namecoin identity integration. +class namecoinConnection (object): user = None password = None host = None @@ -42,270 +46,183 @@ class namecoinConnection(object): nmctype = None bufsize = 4096 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 = shared.config.get (configSection, "namecoinrpctype") + self.host = shared.config.get (configSection, "namecoinrpchost") + self.port = shared.config.get (configSection, "namecoinrpcport") + self.user = shared.config.get (configSection, "namecoinrpcuser") + self.password = shared.config.get (configSection, + "namecoinrpcpassword") else: - self.nmctype = options["type"] - self.host = options["host"] - self.port = int(options["port"]) - self.user = options["user"] - self.password = options["password"] + self.nmctype = options["type"] + self.host = options["host"] + self.port = options["port"] + self.user = options["user"] + self.password = options["password"] assert self.nmctype == "namecoind" or self.nmctype == "nmcontrol" - if self.nmctype == "namecoind": - 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: + raise RPCError ({"code": -4}) else: assert False except RPCError as exc: - logger.exception("Namecoin query RPC exception") - if isinstance(exc.error, dict): - errmsg = exc.error["message"] + if exc.error["code"] == -4: + return (tr._translate("MainWindow",'The name %1 was not found.').arg(unicode(string)), None) 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: - logger.exception("Namecoin query exception") - return (_translate( - "MainWindow", "The namecoin query failed."), None) + return (tr._translate("MainWindow",'The namecoin query failed (%1)').arg(unicode(exc.error["message"])), None) + except Exception as exc: + print "Namecoin query exception: %s" % str (exc) + return (tr._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: + 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: + return (None, val["bitmessage"]) + return (tr._translate("MainWindow",'The name %1 has no associated Bitmessage address.').arg(unicode(string)), None) - 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. - """ + # Test the connection settings. This routine tries to query a "getinfo" + # command, and builds either an error message or a success message with + # some info from it. + def test (self): try: if self.nmctype == "namecoind": - try: - vers = self.callRPC("getinfo", [])["version"] - except RPCError: - vers = self.callRPC("getnetworkinfo", [])["version"] - + res = self.callRPC ("getinfo", []) + vers = res["version"] + v3 = vers % 100 vers = vers / 100 v2 = vers % 100 vers = vers / 100 v1 = vers if v3 == 0: - versStr = "0.%d.%d" % (v1, v2) + 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." - ) - ) + print "Unexpected nmcontrol reply: %s" % res + return ('failed', tr._translate("MainWindow",'Couldn\'t understand NMControl.')) else: - sys.exit("Unsupported Namecoin type") + assert False - return message - - except Exception: - logger.info("Namecoin connection test failure") - return ( - "failed", - _translate( - "MainWindow", "The connection to namecoin failed.") - ) - - def callRPC(self, method, params): - """Helper routine that actually performs an JSON RPC call.""" + except Exception as exc: + print "Namecoin connection test: %s" % str (exc) + return ('failed', "The connection to namecoin failed.") + # Helper routine that actually performs an JSON RPC call. + def callRPC (self, method, params): data = {"method": method, "params": params, "id": self.queryid} if self.nmctype == "namecoind": - resp = self.queryHTTP(json.dumps(data)) + 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.") + self.queryid = self.queryid + 1 - if self.nmctype == "namecoind": - self.queryid = self.queryid + 1 + if val["error"] is not None: + raise RPCError (val["error"]) - error = val["error"] - if error is None: - return val["result"] + return val["result"] - if isinstance(error, bool): - raise RPCError(val["result"]) - raise RPCError(error) - - def queryHTTP(self, data): - """Query the server via HTTP.""" + # Query the server via HTTP. + def queryHTTP (self, data): + header = "POST / HTTP/1.1\n" + header += "User-Agent: bitmessage\n" + header += "Host: %s\n" % self.host + header += "Content-Type: application/json\n" + header += "Content-Length: %d\n" % len (data) + header += "Accept: application/json\n" + authstr = "%s:%s" % (self.user, self.password) + header += "Authorization: Basic %s\n" % base64.b64encode (authstr) + resp = self.queryServer ("%s\n%s" % (header, data)) + lines = resp.split ("\r\n") result = None - - try: - self.con.putrequest("POST", "/") - self.con.putheader("Connection", "Keep-Alive") - self.con.putheader("User-Agent", "bitmessage") - self.con.putheader("Host", self.host) - self.con.putheader("Content-Type", "application/json") - 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.endheaders() - self.con.send(data) - except: # noqa:E722 - 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 + body = False + for line in lines: + if line == "" and not body: + body = True + elif body: + if result is not None: + raise Exception ("Expected a single line in HTTP response.") + result = line return result - 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, int (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,60 +231,54 @@ def lookupNamecoinFolder(): return dataFolder +# Ensure all namecoin options are set, by setting those to default values +# that aren't there. +def ensureNamecoinOptions (): + if not shared.config.has_option (configSection, "namecoinrpctype"): + shared.config.set (configSection, "namecoinrpctype", "namecoind") + if not shared.config.has_option (configSection, "namecoinrpchost"): + shared.config.set (configSection, "namecoinrpchost", "localhost") -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 = shared.config.has_option (configSection, "namecoinrpcuser") + hasPass = shared.config.has_option (configSection, "namecoinrpcpassword") + hasPort = shared.config.has_option (configSection, "namecoinrpcport") # Try to read user/password from .namecoin configuration file. defaultUser = "" defaultPass = "" - nmcFolder = lookupNamecoinFolder() - nmcConfig = nmcFolder + "namecoin.conf" try: - nmc = open(nmcConfig, "r") + nmcFolder = lookupNamecoinFolder () + nmcConfig = nmcFolder + "namecoin.conf" + 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 if key == "rpcpassword" and not hasPass: defaultPass = val if key == "rpcport": - defaults.namecoinDefaultRpcPort = val + shared.namecoinDefaultRpcPort = val + + nmc.close () - nmc.close() - except IOError: - logger.warning( - "%s unreadable or missing, Namecoin support deactivated", - nmcConfig) - except Exception: - logger.warning("Error processing namecoin.conf", exc_info=True) + except Exception as exc: + print "Could not read the Namecoin config file probably because you don't have Namecoin installed. That's ok; we don't really need it. Detailed error message: %s" % str (exc) # 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): + shared.config.set (configSection, "namecoinrpcuser", defaultUser) + if (not hasPass): + shared.config.set (configSection, "namecoinrpcpassword", defaultPass) # Set default port now, possibly to found value. - if not hasPort: - config.set(configSection, "namecoinrpcport", defaults.namecoinDefaultRpcPort) + if (not hasPort): + shared.config.set (configSection, "namecoinrpcport", + shared.namecoinDefaultRpcPort) diff --git a/src/network/__init__.py b/src/network/__init__.py deleted file mode 100644 index c87ad64d..00000000 --- a/src/network/__init__.py +++ /dev/null @@ -1,54 +0,0 @@ -""" -Network subsystem package -""" -from six.moves import queue -from .dandelion import Dandelion -from .threads import StoppableThread -from .multiqueue import MultiQueue - -dandelion_ins = Dandelion() - -# network queues -invQueue = MultiQueue() -addrQueue = MultiQueue() -portCheckerQueue = queue.Queue() -receiveDataQueue = queue.Queue() - -__all__ = ["StoppableThread"] - - -def start(config, state): - """Start network threads""" - from .announcethread import AnnounceThread - import connectionpool # pylint: disable=relative-import - from .addrthread import AddrThread - from .downloadthread import DownloadThread - from .invthread import InvThread - from .networkthread import BMNetworkThread - from .knownnodes import readKnownNodes - from .receivequeuethread import ReceiveQueueThread - from .uploadthread import UploadThread - - # check and set dandelion enabled value at network startup - dandelion_ins.init_dandelion_enabled(config) - # pass pool instance into dandelion class instance - dandelion_ins.init_pool(connectionpool.pool) - - readKnownNodes() - connectionpool.pool.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 deleted file mode 100644 index 81e44506..00000000 --- a/src/network/addrthread.py +++ /dev/null @@ -1,49 +0,0 @@ -""" -Announce addresses as they are received from other hosts -""" -import random -from six.moves import queue - -# magic imports! -import connectionpool -from protocol import assembleAddrMessage -from network import addrQueue # FIXME: init with queue - -from threads import StoppableThread - - -class AddrThread(StoppableThread): - """(Node) address broadcasting thread""" - name = "AddrBroadcaster" - - def run(self): - while not self._stopped: - chunk = [] - while True: - try: - data = addrQueue.get(False) - chunk.append(data) - except queue.Empty: - break - - if chunk: - # Choose peers randomly - connections = connectionpool.pool.establishedConnections() - random.shuffle(connections) - for i in connections: - random.shuffle(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)) - - addrQueue.iterate() - for i in range(len(chunk)): - addrQueue.task_done() - self.stop.wait(1) diff --git a/src/network/advanceddispatcher.py b/src/network/advanceddispatcher.py deleted file mode 100644 index 49f0d19d..00000000 --- a/src/network/advanceddispatcher.py +++ /dev/null @@ -1,173 +0,0 @@ -""" -Improved version of asyncore dispatcher -""" -import socket -import threading -import time - -import network.asyncore_pollchoose as asyncore -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 - - 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" - self.lastTx = time.time() - self.sentBytes = 0 - self.receivedBytes = 0 - self.expectBytes = 0 - self.readLock = threading.RLock() - self.writeLock = threading.RLock() - self.processingLock = threading.RLock() - 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: - for chunk in data: - self.write_buf.extend(chunk) - else: - with self.writeLock: - self.write_buf.extend(data) - - def slice_write_buf(self, length=0): - """Cut the beginning of the stream write buffer.""" - if length > 0: - with self.writeLock: - if length >= len(self.write_buf): - del self.write_buf[:] - else: - 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): - del self.read_buf[:] - else: - 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): - if not self.connected or state.shutdown: - 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(): - break - except BusyError: - return False - return False - - def set_state(self, state_str, length=0, expectBytes=0): - """Set the next processing state.""" - self.expectBytes = expectBytes - self.slice_read_buf(length) - self.state = state_str - - 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)) - - 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)) - if self.downloadChunk < 0: - self.downloadChunk = 0 - except AttributeError: - pass - return asyncore.dispatcher.readable(self) and ( - self.connecting or self.accepting or ( - self.connected and self.downloadChunk > 0)) - - def handle_read(self): - """Append incoming data to the read buffer.""" - self.lastTx = time.time() - newData = self.recv(self.downloadChunk) - self.receivedBytes += len(newData) - asyncore.update_received(len(newData)) - with self.readLock: - self.read_buf.extend(newData) - - def handle_write(self): - """Send outgoing data from write buffer.""" - self.lastTx = time.time() - written = self.send(self.write_buf[0:self.uploadChunk]) - asyncore.update_sent(written) - self.sentBytes += written - self.slice_write_buf(written) - - 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.""" - 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: - self.write_buf = bytearray() - self.set_state("close") - self.close() diff --git a/src/network/announcethread.py b/src/network/announcethread.py deleted file mode 100644 index 7cb35e77..00000000 --- a/src/network/announcethread.py +++ /dev/null @@ -1,43 +0,0 @@ -""" -Announce myself (node address) -""" -import time - -# magic imports! -import connectionpool -from bmconfigparser import config -from protocol import assembleAddrMessage - -from node import Peer -from threads import StoppableThread - - -class AnnounceThread(StoppableThread): - """A thread to manage regular announcing of this node""" - name = "Announcer" - announceInterval = 60 - - def run(self): - lastSelfAnnounced = 0 - while not self._stopped: - processed = 0 - if lastSelfAnnounced < time.time() - self.announceInterval: - self.announceSelf() - lastSelfAnnounced = time.time() - if processed == 0: - self.stop.wait(10) - - @staticmethod - def announceSelf(): - """Announce our presence""" - for connection in connectionpool.pool.udpSockets.values(): - if not connection.announcing: - continue - for stream in connectionpool.pool.streams: - addr = ( - stream, - Peer( - '127.0.0.1', - config.safeGetInt('bitmessagesettings', 'port')), - int(time.time())) - connection.append_write_buf(assembleAddrMessage([addr])) diff --git a/src/network/asyncore_pollchoose.py b/src/network/asyncore_pollchoose.py deleted file mode 100644 index 9d0ffc1b..00000000 --- a/src/network/asyncore_pollchoose.py +++ /dev/null @@ -1,1012 +0,0 @@ -""" -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 -import select -import socket -import random -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 - - -try: - from errno import WSAEWOULDBLOCK -except (ImportError, AttributeError): - WSAEWOULDBLOCK = EWOULDBLOCK -try: - from errno import WSAENOTSOCK -except (ImportError, AttributeError): - WSAENOTSOCK = ENOTSOCK -try: - from errno import WSAECONNRESET -except (ImportError, AttributeError): - WSAECONNRESET = ECONNRESET -try: - # Desirable side-effects on Windows; imports winsock error numbers - from errno import WSAEADDRINUSE # pylint: disable=unused-import -except (ImportError, AttributeError): - WSAEADDRINUSE = EADDRINUSE - - -_DISCONNECTED = frozenset(( - ECONNRESET, ENOTCONN, ESHUTDOWN, ECONNABORTED, EPIPE, EBADF, ECONNREFUSED, - EHOSTUNREACH, ENETUNREACH, ETIMEDOUT, WSAECONNRESET)) - -OP_READ = 1 -OP_WRITE = 2 - -try: - socket_map -except NameError: - socket_map = {} - - -def _strerror(err): - try: - return os.strerror(err) - except (ValueError, OverflowError, NameError): - if err in errorcode: - return errorcode[err] - return "Unknown error %s" % err - - -class ExitNow(Exception): - """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 -downloadTimestamp = 0 -downloadBucket = 0 -receivedBytes = 0 -maxUploadRate = 0 -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: - 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: - obj.handle_error() - - -def set_rates(download, upload): - """Set throttling rates""" - - global maxDownloadRate, maxUploadRate, downloadBucket - global uploadBucket, downloadTimestamp, uploadTimestamp - - maxDownloadRate = float(download) * 1024 - maxUploadRate = float(upload) * 1024 - downloadBucket = maxDownloadRate - uploadBucket = maxUploadRate - 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) - 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: - bucketIncrease = maxUploadRate * (currentTimestamp - uploadTimestamp) - uploadBucket += bucketIncrease - if uploadBucket > maxUploadRate: - uploadBucket = int(maxUploadRate) - uploadBucket -= upload - uploadTimestamp = currentTimestamp - - -def _exception(obj): - """Handle exceptions as appropriate""" - - try: - obj.handle_expt_event() - except _reraised_exceptions: - raise - except BaseException: - 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() - if flags & select.POLLOUT and can_send(): - obj.handle_write_event() - if flags & select.POLLPRI: - obj.handle_expt_event() - if flags & (select.POLLHUP | select.POLLERR | select.POLLNVAL): - obj.handle_close() - except socket.error as e: - if e.args[0] not in _DISCONNECTED: - obj.handle_error() - else: - obj.handle_close() - except _reraised_exceptions: - raise - except BaseException: - obj.handle_error() - - -def select_poller(timeout=0.0, map=None): - """A poller which uses select(), available on most platforms.""" - - if map is None: - map = socket_map - if map: - r = [] - w = [] - e = [] - for fd, obj in list(map.items()): - is_r = obj.readable() - is_w = obj.writable() - if is_r: - r.append(fd) - # accepting sockets should not be writable - if is_w and not obj.accepting: - w.append(fd) - if is_r or is_w: - e.append(fd) - if [] == r == w == e: - time.sleep(timeout) - return - - try: - r, w, e = select.select(r, w, e, timeout) - except KeyboardInterrupt: - return - except socket.error as err: - if err.args[0] in (EBADF, EINTR): - return - except Exception as err: - if err.args[0] in (WSAENOTSOCK, ): - return - - for fd in random.sample(r, len(r)): - obj = map.get(fd) - if obj is None: - continue - read(obj) - - for fd in random.sample(w, len(w)): - obj = map.get(fd) - if obj is None: - continue - write(obj) - - for fd in e: - obj = map.get(fd) - if obj is None: - continue - _exception(obj) - else: - current_thread().stop.wait(timeout) - - -def poll_poller(timeout=0.0, map=None): - """A poller which uses poll(), available on most UNIXen.""" - - if map is None: - map = socket_map - if timeout is not None: - # timeout is in milliseconds - timeout = int(timeout * 1000) - try: - poll_poller.pollster - except AttributeError: - poll_poller.pollster = select.poll() - if map: - for fd, obj in list(map.items()): - flags = newflags = 0 - if obj.readable(): - flags |= select.POLLIN | select.POLLPRI - newflags |= OP_READ - else: - newflags &= ~ OP_READ - # accepting sockets should not be writable - if obj.writable() and not obj.accepting: - flags |= select.POLLOUT - newflags |= OP_WRITE - else: - newflags &= ~ OP_WRITE - if newflags != obj.poller_flags: - obj.poller_flags = newflags - try: - if obj.poller_registered: - poll_poller.pollster.modify(fd, flags) - else: - poll_poller.pollster.register(fd, flags) - obj.poller_registered = True - except IOError: - pass - try: - r = poll_poller.pollster.poll(timeout) - except KeyboardInterrupt: - r = [] - except socket.error as err: - if err.args[0] in (EBADF, WSAENOTSOCK, EINTR): - return - for fd, flags in random.sample(r, len(r)): - obj = map.get(fd) - if obj is None: - continue - readwrite(obj, flags) - else: - current_thread().stop.wait(timeout) - - -# Aliases for backward compatibility -poll = select_poller -poll2 = poll3 = poll_poller - - -def epoll_poller(timeout=0.0, map=None): - """A poller which uses epoll(), supported on Linux 2.5.44 and newer.""" - - if map is None: - map = socket_map - try: - epoll_poller.pollster - except AttributeError: - epoll_poller.pollster = select.epoll() - if map: - for fd, obj in map.items(): - flags = newflags = 0 - if obj.readable(): - flags |= select.POLLIN | select.POLLPRI - newflags |= OP_READ - else: - newflags &= ~ OP_READ - # accepting sockets should not be writable - if obj.writable() and not obj.accepting: - flags |= select.POLLOUT - newflags |= OP_WRITE - else: - newflags &= ~ OP_WRITE - if newflags != obj.poller_flags: - obj.poller_flags = newflags - # Only check for exceptions if object was either readable - # or writable. - flags |= select.POLLERR | select.POLLHUP | select.POLLNVAL - try: - if obj.poller_registered: - epoll_poller.pollster.modify(fd, flags) - else: - epoll_poller.pollster.register(fd, flags) - obj.poller_registered = True - except IOError: - pass - try: - r = epoll_poller.pollster.poll(timeout) - except IOError as e: - if e.errno != EINTR: - raise - r = [] - except select.error as err: - if err.args[0] != EINTR: - raise - r = [] - for fd, flags in random.sample(r, len(r)): - obj = map.get(fd) - if obj is None: - continue - readwrite(obj, flags) - else: - current_thread().stop.wait(timeout) - - -def kqueue_poller(timeout=0.0, map=None): - """A poller which uses kqueue(), BSD specific.""" - # pylint: disable=no-member,too-many-statements - - if map is None: - map = socket_map - try: - kqueue_poller.pollster - except AttributeError: - kqueue_poller.pollster = select.kqueue() - if map: - updates = [] - selectables = 0 - for fd, obj in map.items(): - kq_filter = 0 - if obj.readable(): - kq_filter |= 1 - selectables += 1 - if obj.writable() and not obj.accepting: - kq_filter |= 2 - selectables += 1 - if kq_filter != obj.poller_filter: - # unlike other pollers, READ and WRITE aren't OR able but have - # to be set and checked separately - if kq_filter & 1 != obj.poller_filter & 1: - poller_flags = select.KQ_EV_ADD - if kq_filter & 1: - poller_flags |= select.KQ_EV_ENABLE - else: - poller_flags |= select.KQ_EV_DISABLE - updates.append( - select.kevent( - fd, filter=select.KQ_FILTER_READ, - flags=poller_flags)) - if kq_filter & 2 != obj.poller_filter & 2: - poller_flags = select.KQ_EV_ADD - if kq_filter & 2: - poller_flags |= select.KQ_EV_ENABLE - else: - poller_flags |= select.KQ_EV_DISABLE - updates.append( - select.kevent( - fd, filter=select.KQ_FILTER_WRITE, - flags=poller_flags)) - obj.poller_filter = kq_filter - - if not selectables: - # unlike other pollers, kqueue poll does not wait if there are no - # filters setup - current_thread().stop.wait(timeout) - return - - events = kqueue_poller.pollster.control(updates, selectables, timeout) - if len(events) > 1: - events = random.sample(events, len(events)) - - for event in events: - fd = event.ident - obj = map.get(fd) - if obj is None: - continue - if event.flags & select.KQ_EV_ERROR: - _exception(obj) - continue - if event.flags & select.KQ_EV_EOF and event.data and event.fflags: - obj.handle_close() - continue - if event.filter == select.KQ_FILTER_READ: - read(obj) - if event.filter == select.KQ_FILTER_WRITE: - write(obj) - else: - current_thread().stop.wait(timeout) - - -def loop(timeout=30.0, use_poll=False, map=None, count=None, poller=None): - """Poll in a loop, until count or timeout is reached""" - - if map is None: - map = socket_map - if count is None: - count = True - # code which grants backward compatibility with "use_poll" - # argument which should no longer be used in favor of - # "poller" - - if poller is None: - if use_poll: - poller = poll_poller - elif hasattr(select, 'epoll'): - poller = epoll_poller - elif hasattr(select, 'kqueue'): - poller = kqueue_poller - elif hasattr(select, 'poll'): - poller = poll_poller - elif hasattr(select, 'select'): - poller = select_poller - - if timeout == 0: - deadline = 0 - else: - deadline = time.time() + timeout - while count: - # fill buckets first - update_sent() - update_received() - subtimeout = deadline - time.time() - if subtimeout <= 0: - break - # then poll - poller(subtimeout, map) - if isinstance(count, int): - count = count - 1 - - -class dispatcher(object): - """Dispatcher for socket objects""" - # pylint: disable=too-many-public-methods,too-many-instance-attributes - - debug = False - connected = False - accepting = False - connecting = False - closing = False - addr = None - ignore_log_types = frozenset(['warning']) - poller_registered = False - poller_flags = 0 - # don't do network IO with a smaller bucket than this - minTx = 1500 - - def __init__(self, sock=None, map=None): - if map is None: - self._map = socket_map - else: - self._map = map - - self._fileno = None - - if sock: - # Set to nonblocking just to make sure for cases where we - # get a socket from a blocking source. - sock.setblocking(0) - self.set_socket(sock, map) - self.connected = True - # The constructor no longer requires that the socket - # passed be connected. - try: - self.addr = sock.getpeername() - except socket.error as err: - if err.args[0] in (ENOTCONN, EINVAL): - # To handle the case where we got an unconnected - # socket. - self.connected = False - else: - # The socket is broken in some unknown way, alert - # the user and remove it from the map (to prevent - # polling of broken sockets). - self.del_channel(map) - raise - else: - self.socket = None - - def __repr__(self): - status = [self.__class__.__module__ + "." + self.__class__.__name__] - if self.accepting and self.addr: - status.append('listening') - elif self.connected: - status.append('connected') - if self.addr is not None: - try: - status.append('%s:%d' % self.addr) - except TypeError: - status.append(repr(self.addr)) - return '<%s at %#x>' % (' '.join(status), id(self)) - - __str__ = __repr__ - - def add_channel(self, map=None): - """Add a channel""" - # pylint: disable=attribute-defined-outside-init - if map is None: - map = self._map - map[self._fileno] = self - self.poller_flags = 0 - self.poller_filter = 0 - - def del_channel(self, map=None): - """Delete a channel""" - fd = self._fileno - if map is None: - map = self._map - if fd in map: - del map[fd] - if self._fileno: - try: - kqueue_poller.pollster.control([select.kevent( - fd, select.KQ_FILTER_READ, select.KQ_EV_DELETE)], 0) - except(AttributeError, KeyError, TypeError, IOError, OSError): - pass - try: - kqueue_poller.pollster.control([select.kevent( - fd, select.KQ_FILTER_WRITE, select.KQ_EV_DELETE)], 0) - except(AttributeError, KeyError, TypeError, IOError, OSError): - pass - try: - epoll_poller.pollster.unregister(fd) - except (AttributeError, KeyError, TypeError, IOError): - # no epoll used, or not registered - pass - try: - poll_poller.pollster.unregister(fd) - except (AttributeError, KeyError, TypeError, IOError): - # no poll used, or not registered - pass - self._fileno = None - self.poller_flags = 0 - self.poller_filter = 0 - self.poller_registered = False - - def create_socket( - self, family=socket.AF_INET, socket_type=socket.SOCK_STREAM): - """Create a socket""" - # pylint: disable=attribute-defined-outside-init - 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._fileno = sock.fileno() - self.add_channel(map) - - def set_reuse_addr(self): - """try to re-use a server port if possible""" - try: - self.socket.setsockopt( - socket.SOL_SOCKET, socket.SO_REUSEADDR, self.socket.getsockopt( - socket.SOL_SOCKET, socket.SO_REUSEADDR) | 1 - ) - except socket.error: - pass - - # ================================================== - # predicates for select() - # these are used as filters for the lists of sockets - # to pass to select(). - # ================================================== - - def readable(self): - """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 - - # ================================================== - # socket object methods. - # ================================================== - - 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'): - self.addr = address - return - if err in (0, EISCONN): - self.addr = address - self.handle_connect_event() - else: - raise socket.error(err, errorcode[err]) - - def accept(self): - """Accept incoming connections. - Returns either an address pair or None.""" - try: - conn, addr = self.socket.accept() - except TypeError: - return None - except socket.error as why: - if why.args[0] in ( - EWOULDBLOCK, WSAEWOULDBLOCK, ECONNABORTED, - EAGAIN, ENOTCONN): - return None - else: - raise - else: - return conn, addr - - def send(self, data): - """Send data""" - try: - result = self.socket.send(data) - return result - except socket.error as why: - if why.args[0] in (EAGAIN, EWOULDBLOCK, WSAEWOULDBLOCK): - return 0 - elif why.args[0] in _DISCONNECTED: - self.handle_close() - return 0 - else: - raise - - def recv(self, buffer_size): - """Receive data""" - try: - data = self.socket.recv(buffer_size) - if not data: - # a closed connection is indicated by signaling - # a read condition, and having recv() return 0. - self.handle_close() - return b'' - return data - except socket.error as why: - # winsock sometimes raises ENOTCONN - if why.args[0] in (EAGAIN, EWOULDBLOCK, WSAEWOULDBLOCK): - return b'' - if why.args[0] in _DISCONNECTED: - self.handle_close() - return b'' - else: - raise - - def close(self): - """Close connection""" - self.connected = False - self.accepting = False - self.connecting = False - self.del_channel() - try: - self.socket.close() - except socket.error as why: - if why.args[0] not in (ENOTCONN, EBADF): - raise - - # cheap inheritance, used to pass all other attribute - # references to the underlying socket object. - def __getattr__(self, attr): - try: - retattr = getattr(self.socket, attr) - except AttributeError: - raise AttributeError( - "%s instance has no attribute '%s'" - % (self.__class__.__name__, attr)) - else: - msg = "%(me)s.%(attr)s is deprecated; use %(me)s.socket.%(attr)s"\ - " instead" % {'me': self.__class__.__name__, 'attr': attr} - warnings.warn(msg, DeprecationWarning, stacklevel=2) - return retattr - - # log and log_info may be overridden to provide more sophisticated - # logging and warning methods. In general, log is for 'hit' logging - # and 'log_info' is for informational, warning and error logging. - - def log(self, message): - """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 - self.handle_accept() - elif not self.connected: - if self.connecting: - self.handle_connect_event() - self.handle_read() - else: - self.handle_read() - - def handle_connect_event(self): - """Handle a connection event""" - err = self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) - if err != 0: - raise socket.error(err, _strerror(err)) - self.handle_connect() - self.connected = True - self.connecting = False - - def handle_write_event(self): - """Handle a write event""" - if self.accepting: - # Accepting sockets shouldn't get a write event. - # We will pretend it didn't happen. - return - - if not self.connected: - if self.connecting: - self.handle_connect_event() - self.handle_write() - - def handle_expt_event(self): - """Handle 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 - err = self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) - if err != 0: - # we can get here when select.select() says that there is an - # exceptional condition on the socket - # since there is an error, we'll go ahead and close the socket - # like we would in a subclassed handle_read() that received no - # data - self.handle_close() - elif sys.platform.startswith("win"): - # async connect failed - self.handle_close() - else: - self.handle_expt() - - def handle_error(self): - """Handle unexpected exceptions""" - _, t, v, tbinfo = compact_traceback() - - # sometimes a user repr method will crash. - try: - self_repr = repr(self) - except BaseException: - self_repr = '<__repr__(self) failed for object at %0x>' % id(self) - - self.log_info( - 'uncaptured python exception, closing channel %s (%s:%s %s)' % ( - self_repr, t, v, tbinfo), - 'error') - self.handle_close() - - def handle_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() - - -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) - - 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: - 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] - info = ' '.join(['[%s|%s|%s]' % x for x in tbinfo]) - return (filename, 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()): - try: - x.close() - except OSError as e: - if e.args[0] == EBADF: - pass - elif not ignore_all: - raise - except _reraised_exceptions: - raise - except BaseException: - if not ignore_all: - raise - map.clear() - - -# Asynchronous File I/O: -# -# After a little research (reading man pages on various unixen, and -# digging through the linux kernel), I've determined that select() -# isn't meant for doing asynchronous file i/o. -# Heartening, though - reading linux/mm/filemap.c shows that linux -# supports asynchronous read-ahead. So _MOST_ of the time, the data -# will be sitting in memory for us already when we go to read it. -# -# What other OS's (besides NT) support async file i/o? [VMS?] -# -# Regardless, this is useful for pipes, and stdin/stdout... - - -if os.name == 'posix': - import fcntl - - class file_wrapper: # 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 - """ - - 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): - return 0 - 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) - self.connected = True - try: - fd = fd.fileno() - except AttributeError: - pass - self.set_file(fd) - # set it to non-blocking mode - flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0) - flags = flags | os.O_NONBLOCK - fcntl.fcntl(fd, fcntl.F_SETFL, flags) - - def set_file(self, fd): - """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 deleted file mode 100644 index 83311b9b..00000000 --- a/src/network/bmobject.py +++ /dev/null @@ -1,163 +0,0 @@ -""" -BMObject and it's exceptions. -""" -import logging -import time - -import protocol -import state -import connectionpool -from network import dandelion_ins -from highlevelcrypto import calculateInventoryHash - -logger = logging.getLogger('default') - - -class BMObjectInsufficientPOWError(Exception): - """Exception indicating the object - doesn't have sufficient proof of work.""" - errorCodes = ("Insufficient proof of work") - - -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.""" - - # 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 - self.nonce = nonce - self.expiresTime = expiresTime - self.objectType = objectType - self.version = version - self.streamNumber = streamNumber - self.inventoryHash = calculateInventoryHash(data) - # copy to avoid memory issues - self.data = bytearray(data) - self.tag = self.data[payloadOffset:payloadOffset + 32] - - def checkProofOfWorkSufficient(self): - """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 - raise BMObjectExpiredError() - - if self.expiresTime - int(time.time()) < BMObject.minTTL: - logger.info( - 'This object\'s End of Life time was too long ago.' - ' Ignoring the object. Time is %i', self.expiresTime) - # .. todo:: remove from download queue - raise BMObjectExpiredError() - - def checkStream(self): - """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 connectionpool.pool.streams: - 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_ins.hasHash(self.inventoryHash): - return - if self.inventoryHash in state.Inventory: - 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: - self.checkPubkey() - elif self.objectType == protocol.OBJECT_MSG: - self.checkMessage() - elif self.objectType == protocol.OBJECT_BROADCAST: - self.checkBroadcast() - # other objects don't require other types of tests - - def checkMessage(self): # pylint: disable=no-self-use - """"Message" object type checks.""" - return - - def checkGetpubkey(self): - """"Getpubkey" object type checks.""" - if len(self.data) < 42: - 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: - 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.') - raise BMObjectInvalidError() - - # this isn't supported anymore - if self.version < 2: - raise BMObjectInvalidError() diff --git a/src/network/bmproto.py b/src/network/bmproto.py deleted file mode 100644 index e5f38bf5..00000000 --- a/src/network/bmproto.py +++ /dev/null @@ -1,677 +0,0 @@ -""" -Class BMProto defines bitmessage's network protocol workflow. -""" - -import base64 -import hashlib -import logging -import re -import socket -import struct -import time - -# magic imports! -import addresses -import knownnodes -import protocol -import state -import connectionpool -from bmconfigparser import config -from queues import objectProcessorQueue -from randomtrackingdict import RandomTrackingDict -from network.advanceddispatcher import AdvancedDispatcher -from network.bmobject import ( - BMObject, BMObjectAlreadyHaveError, BMObjectExpiredError, - BMObjectInsufficientPOWError, BMObjectInvalidError, - BMObjectUnwantedStreamError -) -from network.proxy import ProxyError -from network import dandelion_ins, invQueue, portCheckerQueue -from node import Node, Peer -from objectracker import ObjectTracker, missingObjects - - -logger = logging.getLogger('default') - - -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 - - 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 - self.checksum = None - self.payload = None - self.invalid = False - self.payloadOffset = 0 - self.expectBytes = protocol.Header.size - self.object = None - - def state_bm_header(self): - """Process incoming header""" - 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: - # skip 1 byte in order to sync - self.set_state("bm_header", length=1) - self.bm_proto_reset() - logger.debug('Bad magic') - if self.socket.type == socket.SOCK_STREAM: - self.close_reason = "Bad magic" - self.set_state("close") - return False - if self.payloadLength > protocol.MAX_MESSAGE_SIZE: - self.invalid = True - 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""" - self.payload = self.read_buf[:self.payloadLength] - if self.checksum != hashlib.sha512(self.payload).digest()[0:4]: - logger.debug('Bad checksum, ignoring') - self.invalid = True - retval = True - if not self.fullyEstablished and self.command not in ( - "error", "version", "verack"): - logger.error( - 'Received command %s before connection was fully' - ' established, ignoring', self.command) - self.invalid = True - if not self.invalid: - try: - retval = getattr( - self, "bm_command_" + str(self.command).lower())() - except AttributeError: - # unimplemented command - logger.debug('unimplemented command %s', self.command) - except BMProtoInsufficientDataError: - logger.debug('packet length too short, skipping') - except BMProtoExcessiveDataError: - logger.debug('too much data, skipping') - except BMObjectInsufficientPOWError: - logger.debug('insufficient PoW, skipping') - except BMObjectExpiredError: - logger.debug('object expired, skipping') - except BMObjectUnwantedStreamError: - logger.debug('object not in wanted stream, skipping') - except BMObjectInvalidError: - logger.debug('object invalid, skipping') - except BMObjectAlreadyHaveError: - logger.debug( - '%(host)s:%(port)i already got object, skipping', - self.destination._asdict()) - except struct.error: - 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 - self.set_state("close") - return False - if retval: - self.set_state("bm_header", length=self.payloadLength) - self.bm_proto_reset() - # else assume the command requires a different state to follow - return True - - def decode_payload_string(self, length): - """Read and return `length` bytes from payload""" - 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:]) - 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])) - elif host[0:6] == '\xfd\x87\xd8\x7e\xeb\x43': - # Onion, based on BMD/bitcoind - host = base64.b32encode(host[6:]).lower() + ".onion" - else: - host = socket.inet_ntop(socket.AF_INET6, str(host)) - if host == "": - # This can happen on Windows systems which are not 64-bit - # compatible so let us drop the IPv6 address. - host = socket.inet_ntop(socket.AF_INET, str(host[12:16])) - - return Node(services, host, port) - - # 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_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] - if char == "I": - self.payloadOffset += 4 - return struct.unpack(">I", self.payload[ - self.payloadOffset - 4:self.payloadOffset])[0] - if char == "Q": - self.payloadOffset += 8 - return struct.unpack(">Q", self.payload[ - self.payloadOffset - 8:self.payloadOffset])[0] - return None - - size = None - isArray = False - - # size - # iterator starting from size counting to 0 - # isArray? - # subpattern - # position of parser in subpattern - # retval (array) - parserStack = [[1, 1, False, pattern, 0, []]] - - while True: - i = parserStack[-1][3][parserStack[-1][4]] - if i in "0123456789" and ( - size is None or parserStack[-1][3][parserStack[-1][4] - 1] - not in "lL"): - try: - size = size * 10 + int(i) - except TypeError: - size = int(i) - isArray = False - elif i in "Ll" and size is None: - size = self.decode_payload_varint() - isArray = i == "L" - elif size is not None: - if isArray: - parserStack.append([ - size, size, isArray, - parserStack[-1][3][parserStack[-1][4]:], 0, [] - ]) - parserStack[-2][4] = len(parserStack[-2][3]) - else: - j = 0 - for j in range( - parserStack[-1][4], len(parserStack[-1][3])): - if parserStack[-1][3][j] not in "lL0123456789": - break - parserStack.append([ - size, size, isArray, - parserStack[-1][3][parserStack[-1][4]:j + 1], 0, [] - ]) - parserStack[-2][4] += len(parserStack[-1][3]) - 1 - size = None - continue - elif i == "s": - # if parserStack[-2][2]: - # parserStack[-1][5].append(self.payload[ - # self.payloadOffset:self.payloadOffset - # + parserStack[-1][0]]) - # else: - parserStack[-1][5] = self.payload[ - self.payloadOffset:self.payloadOffset + parserStack[-1][0]] - self.payloadOffset += parserStack[-1][0] - parserStack[-1][1] = 0 - parserStack[-1][2] = True - # del parserStack[-1] - size = None - elif i in "viHIQ": - parserStack[-1][5].append(decode_simple( - self, parserStack[-1][3][parserStack[-1][4]])) - size = None - else: - size = None - for depth in range(len(parserStack) - 1, -1, -1): - parserStack[depth][4] += 1 - if parserStack[depth][4] >= len(parserStack[depth][3]): - parserStack[depth][1] -= 1 - parserStack[depth][4] = 0 - if depth > 0: - if parserStack[depth][2]: - parserStack[depth - 1][5].append( - parserStack[depth][5]) - else: - parserStack[depth - 1][5].extend( - parserStack[depth][5]) - parserStack[depth][5] = [] - if parserStack[depth][1] <= 0: - if depth == 0: - # we're done, at depth 0 counter is at 0 - # and pattern is done parsing - return parserStack[depth][5] - del parserStack[-1] - continue - break - break - if self.payloadOffset > self.payloadLength: - logger.debug( - 'Insufficient data %i/%i', - self.payloadOffset, self.payloadLength) - raise BMProtoInsufficientDataError() - - def bm_command_error(self): - """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) - 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: - return True - for i in items: - self.pendingUpload[str(i)] = now - return True - - def _command_inv(self, extend_dandelion_stem=False): - """ - Common inv announce implementation: - both inv and dinv depending on *extend_dandelion_stem* kwarg - """ - items = self.decode_payload_content("l32s") - - if len(items) > protocol.MAX_OBJECT_COUNT: - logger.error( - 'Too many items in %sinv message!', 'd' if extend_dandelion_stem else '') - raise BMProtoExcessiveDataError() - - # ignore dinv if dandelion turned off - if extend_dandelion_stem and not dandelion_ins.enabled: - return True - - for i in map(str, items): - if i in state.Inventory and not dandelion_ins.hasHash(i): - continue - if extend_dandelion_stem and not dandelion_ins.hasHash(i): - dandelion_ins.addHash(i, self) - self.handleReceivedInventory(i) - - return True - - def bm_command_inv(self): - """Non-dandelion announce""" - return self._command_inv(False) - - def bm_command_dinv(self): - """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) - - 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) - raise BMProtoExcessiveDataError() - - try: - self.object.checkProofOfWorkSufficient() - self.object.checkEOLSanity() - self.object.checkAlreadyHave() - except (BMObjectExpiredError, BMObjectAlreadyHaveError, - BMObjectInsufficientPOWError): - BMProto.stopDownloadingObject(self.object.inventoryHash) - raise - 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 - - try: - self.object.checkObjectByType() - objectProcessorQueue.put(( - self.object.objectType, buffer(self.object.data))) # noqa: F821 - except BMObjectInvalidError: - BMProto.stopDownloadingObject(self.object.inventoryHash, True) - else: - try: - del missingObjects[self.object.inventoryHash] - except KeyError: - pass - - if self.object.inventoryHash in state.Inventory and dandelion_ins.hasHash( - self.object.inventoryHash): - dandelion_ins.removeHash( - self.object.inventoryHash, "cycle detection") - - state.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)) - 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 connectionpool.pool.streams - # FIXME: should check against complete list - or ip.startswith('bootstrap') - ): - 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)) - return True - - def bm_command_portcheck(self): - """Incoming port check request, queue it.""" - portCheckerQueue.put(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. - """ - # 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 - - 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.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))) - if not self.peerValidityChecks(): - # ABORT afterwards - return True - 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.pool.streams, dandelion_ins.enabled, 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.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 - - # 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) - 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.", - self.destination, self.timeOffset) - BMProto.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.", - self.destination, self.timeOffset) - BMProto.timeOffsetWrongCount += 1 - return False - else: - BMProto.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) - return False - if connectionpool.pool.inboundConnections.get( - self.destination): - 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) - return False - except Exception: # nosec B110 # pylint:disable=broad-exception-caught - 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.pool.inboundConnections - or len(connectionpool.pool) - > 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) - return False - if connectionpool.pool.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) - return False - - return True - - @staticmethod - def stopDownloadingObject(hashId, forwardAnyway=False): - """Stop downloading object *hashId*""" - for connection in connectionpool.pool.connections(): - try: - del connection.objectsNewToMe[hashId] - except KeyError: - pass - if not forwardAnyway: - try: - with connection.objectsNewToThemLock: - del connection.objectsNewToThem[hashId] - except KeyError: - pass - try: - del 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) - except AttributeError: - try: - logger.debug( - '%s:%i: closing', - self.destination.host, self.destination.port) - except AttributeError: - logger.debug('Disconnected socket closing') - AdvancedDispatcher.handle_close(self) diff --git a/src/network/connectionchooser.py b/src/network/connectionchooser.py deleted file mode 100644 index e2981d51..00000000 --- a/src/network/connectionchooser.py +++ /dev/null @@ -1,81 +0,0 @@ -""" -Select which node to connect to -""" -# pylint: disable=too-many-branches -import logging -import random - -from six.moves import queue - -import knownnodes -import protocol -import state - -from bmconfigparser import config -from network import 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 - except (IndexError, KeyError): - raise ValueError - try: - del state.discoveredPeers[peer] - except KeyError: - pass - return peer - - -def chooseConnection(stream): - """Returns an appropriate connection""" - haveOnion = config.safeGet( - "bitmessagesettings", "socksproxytype")[0:5] == 'SOCKS' - onionOnly = config.safeGetBoolean( - "bitmessagesettings", "onionservicesonly") - try: - retval = portCheckerQueue.get(False) - portCheckerQueue.task_done() - return retval - except queue.Empty: - pass - # with a probability of 0.5, connect to a discovered peer - if random.choice((False, True)) and not haveOnion: # nosec B311 - # discovered peers are already filtered by allowed streams - return getDiscoveredPeer() - for _ in range(50): - peer = random.choice( # nosec B311 - knownnodes.knownNodes[stream].keys()) - try: - peer_info = knownnodes.knownNodes[stream][peer] - if peer_info.get('self'): - continue - rating = peer_info["rating"] - except TypeError: - logger.warning('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'): - encodedAddr = protocol.encodeHost(peer.host) - # don't connect to local IPs when using SOCKS - if not protocol.checkIPAddress(encodedAddr, False): - continue - if rating > 1: - rating = 1 - try: - if 0.05 / (1.0 - rating) > random.random(): # nosec B311 - return peer - except ZeroDivisionError: - return peer - raise ValueError diff --git a/src/network/connectionpool.py b/src/network/connectionpool.py deleted file mode 100644 index 519b7b67..00000000 --- a/src/network/connectionpool.py +++ /dev/null @@ -1,405 +0,0 @@ -""" -`BMConnectionPool` class definition -""" -import errno -import logging -import re -import socket -import sys -import time -import random - -import asyncore_pollchoose as asyncore -import knownnodes -import protocol -import state -from bmconfigparser import config -from connectionchooser import chooseConnection -from node import Peer -from proxy import Proxy -from tcp import ( - bootstrap, Socks4aBMConnection, Socks5BMConnection, - TCPConnection, TCPServer) -from udp import UDPSocket - -logger = logging.getLogger('default') - - -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") - ) - 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] - - def connectToStream(self, streamNumber): - """Connect to a bitmessage stream""" - self.streams.append(streamNumber) - - def getConnectionByAddr(self, addr): - """ - Return an (existing) connection object based on a `Peer` object - (IP and port) - """ - try: - return self.inboundConnections[addr] - except KeyError: - pass - try: - return self.inboundConnections[addr.host] - except (KeyError, AttributeError): - pass - try: - return self.outboundConnections[addr] - except KeyError: - pass - try: - return self.udpSockets[addr.host] - except (KeyError, AttributeError): - pass - raise KeyError - - def isAlreadyConnected(self, nodeid): - """Check if we're already connected to this peer""" - for i in self.connections(): - try: - if nodeid == i.nodeid: - return True - except AttributeError: - pass - return False - - def addConnection(self, connection): - """Add a connection object to our internal dict""" - if isinstance(connection, UDPSocket): - return - if connection.isOutbound: - self.outboundConnections[connection.destination] = connection - else: - if connection.destination.host in self.inboundConnections: - self.inboundConnections[connection.destination] = connection - else: - self.inboundConnections[connection.destination.host] = \ - connection - - def removeConnection(self, connection): - """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)] - elif connection.isOutbound: - try: - del self.outboundConnections[connection.destination] - except KeyError: - pass - else: - try: - del self.inboundConnections[connection.destination] - except KeyError: - try: - del self.inboundConnections[connection.destination.host] - except KeyError: - pass - connection.handle_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") - else: - host = '127.0.0.1' - if ( - config.safeGetBoolean("bitmessagesettings", "sockslisten") - or config.safeGet("bitmessagesettings", "socksproxytype") - == "none" - ): - # python doesn't like bind + INADDR_ANY? - # host = socket.INADDR_ANY - host = config.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") - # 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) - else: - if bind is False: - udpSocket = UDPSocket(announcing=False) - else: - 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 = random.choice([ # nosec B311 - '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 = random.choice([8080, 8444]) # nosec B311 - 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 - # defaults to empty loop if outbound connections are maxed - spawnConnections = False - acceptConnections = True - if config.safeGetBoolean( - 'bitmessagesettings', 'dontconnect'): - acceptConnections = False - elif config.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', '') - ): - 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') - ) - # 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)) - pending = len(self.outboundConnections) - established - if established < config.safeGetInt( - 'bitmessagesettings', 'maxoutboundconnections'): - for i in range( - state.maximumNumberOfHalfOpenConnections - pending): - try: - chosen = self.trustedPeer or chooseConnection( - random.choice(self.streams)) # nosec B311 - except ValueError: - continue - if chosen in self.outboundConnections: - continue - if chosen.host in self.inboundConnections: - continue - # don't connect to self - if chosen in state.ownAddresses: - continue - # 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 - - try: - if chosen.host.endswith(".onion") and Proxy.onion_proxy: - if onionsocksproxytype == "SOCKS5": - self.addConnection(Socks5BMConnection(chosen)) - elif onionsocksproxytype == "SOCKS4a": - self.addConnection(Socks4aBMConnection(chosen)) - elif socksproxytype == "SOCKS5": - self.addConnection(Socks5BMConnection(chosen)) - elif socksproxytype == "SOCKS4a": - self.addConnection(Socks4aBMConnection(chosen)) - else: - self.addConnection(TCPConnection(chosen)) - except socket.error as e: - if e.errno == errno.ENETUNREACH: - continue - - self._lastSpawned = time.time() - else: - for i in self.outboundConnections.values(): - # FIXME: rating will be increased after next connection - i.handle_close() - - if acceptConnections: - if not self.listeningSockets: - if config.safeGet('network', 'bind') == '': - self.startListening() - else: - for bind in re.sub( - r'[^\w.]+', ' ', - config.safeGet('network', 'bind') - ).split(): - self.startListening(bind) - logger.info('Listening for incoming connections.') - if not self.udpSockets: - if config.safeGet('network', 'bind') == '': - self.startUDPSocket() - else: - for bind in re.sub( - r'[^\w.]+', ' ', - config.safeGet('network', 'bind') - ).split(): - self.startUDPSocket(bind) - self.startUDPSocket(False) - logger.info('Starting UDP socket(s).') - else: - if self.listeningSockets: - for i in self.listeningSockets.values(): - i.close_reason = "Stopping listening" - i.accepting = i.connecting = i.connected = False - logger.info('Stopped listening for incoming connections.') - if self.udpSockets: - for i in self.udpSockets.values(): - i.close_reason = "Stopping UDP socket" - i.accepting = i.connecting = i.connected = False - logger.info('Stopped udp sockets.') - - loopTime = float(self._spawnWait) - if self._lastSpawned < time.time() - self._spawnWait: - loopTime = 2.0 - asyncore.loop(timeout=loopTime, count=1000) - - reaper = [] - for i in self.connections(): - minTx = time.time() - 20 - if i.fullyEstablished: - minTx -= 300 - 20 - if i.lastTx < minTx: - if i.fullyEstablished: - i.append_write_buf(protocol.CreatePacket('ping')) - else: - i.close_reason = "Timeout (%is)" % ( - time.time() - i.lastTx) - i.set_state("close") - for i in ( - self.connections() - + self.listeningSockets.values() + self.udpSockets.values() - ): - if not (i.accepting or i.connecting or i.connected): - reaper.append(i) - else: - try: - if i.state == "close": - reaper.append(i) - except AttributeError: - pass - for i in reaper: - self.removeConnection(i) - - -pool = BMConnectionPool() diff --git a/src/network/dandelion.py b/src/network/dandelion.py deleted file mode 100644 index 564a35f9..00000000 --- a/src/network/dandelion.py +++ /dev/null @@ -1,208 +0,0 @@ -""" -Dandelion class definition, tracks stages -""" -import logging -from collections import namedtuple -from random import choice, expovariate, sample -from threading import RLock -from time import time - - -# randomise routes after 600 seconds -REASSIGN_INTERVAL = 600 - -# trigger fluff due to expiration -FLUFF_TRIGGER_FIXED_DELAY = 10 -FLUFF_TRIGGER_MEAN_DELAY = 30 - -MAX_STEMS = 2 - -Stem = namedtuple('Stem', ['child', 'stream', 'timeout']) - -logger = logging.getLogger('default') - - -class Dandelion: # pylint: disable=old-style-class - """Dandelion class for tracking stem/fluff stages.""" - def __init__(self): - # currently assignable child stems - self.stem = [] - # currently assigned parent <-> child mappings - self.nodeMap = {} - # currently existing objects in stem mode - self.hashMap = {} - # when to rerandomise routes - self.refresh = time() + REASSIGN_INTERVAL - self.lock = RLock() - self.enabled = None - self.pool = None - - @staticmethod - def poissonTimeout(start=None, average=0): - """Generate deadline using Poisson distribution""" - if start is None: - start = time() - if average == 0: - average = FLUFF_TRIGGER_MEAN_DELAY - return start + expovariate(1.0 / average) + FLUFF_TRIGGER_FIXED_DELAY - - def init_pool(self, pool): - """pass pool instance""" - self.pool = pool - - def init_dandelion_enabled(self, config): - """Check if Dandelion is enabled and set value in enabled attribute""" - dandelion_enabled = config.safeGetInt('network', 'dandelion') - # dandelion requires outbound connections, without them, - # stem objects will get stuck forever - if not config.safeGetBoolean( - 'bitmessagesettings', 'sendoutgoingconnections'): - dandelion_enabled = 0 - self.enabled = dandelion_enabled - - def addHash(self, hashId, source=None, stream=1): - """Add inventory vector to dandelion stem return status of dandelion enabled""" - assert self.enabled is not None - 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, - 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) - with self.lock: - try: - del self.hashMap[hashId] - except KeyError: - 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, invQueue): - """ - 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()) - 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 - ): - self.nodeMap[k] = None - for k, v in { - k: v for k, v in self.hashMap.iteritems() - if v.child == connection - }.iteritems(): - self.hashMap[k] = Stem( - None, v.stream, self.poissonTimeout()) - - def pickStem(self, parent=None): - """ - 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 - if self.stem[stem] == parent: - # one stem available and it's the parent - if len(self.stem) == 1: - return None - # else, pick the other one - return self.stem[1 - stem] - # all ok - return self.stem[stem] - except IndexError: - # no stems available - return None - - def getNodeStem(self, node=None): - """ - 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] - except KeyError: - self.nodeMap[node] = self.pickStem(node) - return self.nodeMap[node] - - def expire(self, invQueue): - """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 - ] - - for row in toDelete: - self.removeHash(row[1], 'expiration') - invQueue.put(row) - return toDelete - - def reRandomiseStems(self): - """Re-shuffle stem mapping (parent <-> child pairs)""" - assert self.pool is not None - if self.refresh > time(): - return - - with self.lock: - try: - # random two connections - self.stem = sample( - self.pool.outboundConnections.values(), MAX_STEMS) - # not enough stems available - except ValueError: - self.stem = self.pool.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 deleted file mode 100644 index 7c8bccb6..00000000 --- a/src/network/downloadthread.py +++ /dev/null @@ -1,83 +0,0 @@ -""" -`DownloadThread` class definition -""" -import time -import random -import state -import addresses -import protocol -import connectionpool -from network import dandelion_ins -from objectracker import missingObjects -from threads import StoppableThread - - -class DownloadThread(StoppableThread): - """Thread-based class for downloading from connections""" - minPending = 200 - maxRequestChunk = 1000 - requestTimeout = 60 - cleanInterval = 60 - requestExpires = 3600 - - def __init__(self): - super(DownloadThread, self).__init__(name="Downloader") - self.lastCleaned = time.time() - - def cleanPending(self): - """Expire pending downloads eventually""" - deadline = time.time() - self.requestExpires - try: - toDelete = [ - k for k, v in missingObjects.iteritems() - if v < deadline] - except RuntimeError: - pass - else: - for i in toDelete: - del missingObjects[i] - self.lastCleaned = time.time() - - def run(self): - while not self._stopped: - requested = 0 - # Choose downloading peers randomly - connections = connectionpool.pool.establishedConnections() - random.shuffle(connections) - requestChunk = max(int( - min(self.maxRequestChunk, len(missingObjects)) - / len(connections)), 1) if connections else 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 - for chunk in request: - if chunk in state.Inventory and not dandelion_ins.hasHash(chunk): - try: - del i.objectsNewToMe[chunk] - except KeyError: - pass - continue - payload.extend(chunk) - chunkCount += 1 - missingObjects[chunk] = now - if not chunkCount: - 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: - self.cleanPending() - if not requested: - self.stop.wait(1) diff --git a/src/network/http.py b/src/network/http.py deleted file mode 100644 index d7a938fa..00000000 --- a/src/network/http.py +++ /dev/null @@ -1,89 +0,0 @@ -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 - - -class HttpError(ProxyError): - pass - - -class HttpConnection(AdvancedDispatcher): - def __init__(self, host, path="/"): # pylint: disable=redefined-outer-name - 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) - - def state_init(self): - self.append_write_buf( - "GET %s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n" % ( - self.path, self.destination[0])) - print("Sending %ib" % len(self.write_buf)) - self.set_state("http_request_sent", 0) - return False - - def state_http_request_sent(self): - if self.read_buf: - print("Received %ib" % len(self.read_buf)) - self.read_buf = b"" - if not self.connected: - self.set_state("close", 0) - return False - - -class Socks5HttpConnection(Socks5Connection, HttpConnection): - def __init__(self, host, path="/"): # pylint: disable=super-init-not-called, redefined-outer-name - self.path = path - Socks5Connection.__init__(self, address=(host, 80)) - - def state_socks_handshake_done(self): - HttpConnection.state_init(self) - return False - - -class Socks4aHttpConnection(Socks4aConnection, HttpConnection): - def __init__(self, host, path="/"): # pylint: disable=super-init-not-called, redefined-outer-name - Socks4aConnection.__init__(self, address=(host, 80)) - self.path = path - - def state_socks_handshake_done(self): - HttpConnection.state_init(self) - return False - - -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))) - 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))) - 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) - asyncore.loop(timeout=1, count=1) - - proxy = Socks5HttpConnection(host) - while asyncore.socket_map: - # 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) - asyncore.loop(timeout=1, count=1) diff --git a/src/network/httpd.py b/src/network/httpd.py deleted file mode 100644 index b69ffa99..00000000 --- a/src/network/httpd.py +++ /dev/null @@ -1,161 +0,0 @@ -""" -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. - - """ - - def __init__(self, sock): - if not hasattr(self, '_map'): - asyncore.dispatcher.__init__(self, sock) - self.inbuf = "" - self.ready = True - self.busy = False - self.respos = 0 - - def handle_close(self): - self.close() - - def readable(self): - return self.ready - - def writable(self): - return self.busy - - def handle_read(self): - self.inbuf += self.recv(8192) - if self.inbuf[-4:] == "\r\n\r\n": - self.busy = True - self.ready = False - self.inbuf = "" - elif self.inbuf == "": - pass - - def handle_write(self): - if self.busy and self.respos < len(HTTPRequestHandler.response): - written = 0 - written = self.send(HTTPRequestHandler.response[self.respos:65536]) - self.respos += written - elif self.busy: - self.busy = False - self.ready = True - self.close() - - -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) - HTTPRequestHandler.__init__(self, sock) - - def handle_connect(self): - TLSHandshake.handle_connect(self) - - def handle_close(self): - if self.tlsDone: - HTTPRequestHandler.close(self) - else: - TLSHandshake.close(self) - - def readable(self): - if self.tlsDone: - return HTTPRequestHandler.readable(self) - return TLSHandshake.readable(self) - - def handle_read(self): - if self.tlsDone: - HTTPRequestHandler.handle_read(self) - else: - TLSHandshake.handle_read(self) - - def writable(self): - if self.tlsDone: - return HTTPRequestHandler.writable(self) - return TLSHandshake.writable(self) - - def handle_write(self): - if self.tlsDone: - HTTPRequestHandler.handle_write(self) - else: - TLSHandshake.handle_write(self) - - -class HTTPServer(asyncore.dispatcher): - """Handling HTTP Server""" - port = 12345 - - def __init__(self): - if not hasattr(self, '_map'): - asyncore.dispatcher.__init__(self) - self.create_socket(socket.AF_INET, socket.SOCK_STREAM) - self.set_reuse_addr() - self.bind(('127.0.0.1', HTTPServer.port)) - self.connections = 0 - self.listen(5) - - def handle_accept(self): - pair = self.accept() - if pair is not None: - sock, addr = pair - # 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)) - HTTPRequestHandler(sock) - - -class HTTPSServer(HTTPServer): - """Handling HTTPS Server""" - port = 12345 - - def __init__(self): - if not hasattr(self, '_map'): - HTTPServer.__init__(self) - - def handle_accept(self): - pair = self.accept() - if pair is not None: - sock, addr = pair - # 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)) - HTTPSRequestHandler(sock) - - -if __name__ == "__main__": - client = HTTPSServer() - asyncore.loop() diff --git a/src/network/https.py b/src/network/https.py deleted file mode 100644 index a7b8b57c..00000000 --- a/src/network/https.py +++ /dev/null @@ -1,71 +0,0 @@ -import asyncore - -from http import HTTPClient -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') -""" - - -class HTTPSClient(HTTPClient, TLSHandshake): - def __init__(self, host, path): - 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') - """ - HTTPClient.__init__(self, host, path, connect=False) - TLSHandshake.__init__(self, address=(host, 443), server_side=False) - - def handle_connect(self): - TLSHandshake.handle_connect(self) - - def handle_close(self): - if self.tlsDone: - HTTPClient.close(self) - else: - TLSHandshake.close(self) - - def readable(self): - if self.tlsDone: - return HTTPClient.readable(self) - else: - return TLSHandshake.readable(self) - - def handle_read(self): - if self.tlsDone: - HTTPClient.handle_read(self) - else: - TLSHandshake.handle_read(self) - - def writable(self): - if self.tlsDone: - return HTTPClient.writable(self) - else: - return TLSHandshake.writable(self) - - def handle_write(self): - if self.tlsDone: - HTTPClient.handle_write(self) - 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 deleted file mode 100644 index 503eefa1..00000000 --- a/src/network/invthread.py +++ /dev/null @@ -1,109 +0,0 @@ -""" -Thread to send inv annoucements -""" -import Queue -import random -from time import time - -import addresses -import protocol -import state -import connectionpool -from network import dandelion_ins, invQueue -from threads import StoppableThread - - -def handleExpiredDandelion(expired): - """For expired dandelion objects, mark all remotes as not having - the object""" - if not expired: - return - for i in connectionpool.pool.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""" - dandelion_ins.addHash(hashId, stream=stream) - for connection in connectionpool.pool.connections(): - if dandelion_ins.enabled and connection != \ - dandelion_ins.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 - chunk = [] - while True: - # Dandelion fluff trigger by expiration - handleExpiredDandelion(dandelion_ins.expire(invQueue)) - try: - data = invQueue.get(False) - chunk.append((data[0], data[1])) - # locally generated - if len(data) == 2 or data[2] is None: - self.handleLocallyGenerated(data[0], data[1]) - except Queue.Empty: - break - - if chunk: - for connection in connectionpool.pool.connections(): - fluffs = [] - stems = [] - for inv in chunk: - if inv[0] not in connection.streams: - continue - try: - with connection.objectsNewToThemLock: - del connection.objectsNewToThem[inv[1]] - except KeyError: - continue - try: - if connection == dandelion_ins.objectChildStem(inv[1]): - # Fluff trigger by RNG - # auto-ignore if config set to 0, i.e. dandelion is off - if random.randint(1, 100) >= dandelion_ins.enabled: # nosec B311 - fluffs.append(inv[1]) - # send a dinv only if the stem node supports dandelion - elif connection.services & protocol.NODE_DANDELION > 0: - stems.append(inv[1]) - else: - fluffs.append(inv[1]) - except KeyError: - fluffs.append(inv[1]) - - if fluffs: - random.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))) - - invQueue.iterate() - for _ in range(len(chunk)): - invQueue.task_done() - - dandelion_ins.reRandomiseStems() - - self.stop.wait(1) diff --git a/src/network/knownnodes.py b/src/network/knownnodes.py deleted file mode 100644 index c53be2cd..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(pool): - """ - 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 pool.streams: - 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/multiqueue.py b/src/network/multiqueue.py deleted file mode 100644 index 3fad4e34..00000000 --- a/src/network/multiqueue.py +++ /dev/null @@ -1,49 +0,0 @@ -""" -A queue with multiple internal subqueues. -Elements are added into a random subqueue, and retrieval rotates -""" -import random -from collections import deque - -from six.moves import queue - - -class MultiQueue(queue.Queue): - """A base queue class""" - # pylint: disable=redefined-builtin,attribute-defined-outside-init - defaultQueueCount = 10 - - def __init__(self, maxsize=0, count=0): - if not count: - self.queueCount = MultiQueue.defaultQueueCount - else: - self.queueCount = count - queue.Queue.__init__(self, maxsize) - - # Initialize the queue representation - def _init(self, maxsize): - self.iter = 0 - self.queues = [] - for _ in range(self.queueCount): - self.queues.append(deque()) - - def _qsize(self, len=len): - return len(self.queues[self.iter]) - - # Put a new item in the queue - def _put(self, item): - # self.queue.append(item) - self.queues[random.randrange(self.queueCount)].append( # nosec B311 - (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/network/networkthread.py b/src/network/networkthread.py deleted file mode 100644 index 640d47a1..00000000 --- a/src/network/networkthread.py +++ /dev/null @@ -1,41 +0,0 @@ -""" -A thread to handle network concerns -""" -import network.asyncore_pollchoose as asyncore -import connectionpool -from queues import excQueue -from threads import StoppableThread - - -class BMNetworkThread(StoppableThread): - """Main network thread""" - name = "Asyncore" - - def run(self): - try: - while not self._stopped: - connectionpool.pool.loop() - except Exception as e: - excQueue.put((self.name, e)) - raise - - def stopThread(self): - super(BMNetworkThread, self).stopThread() - for i in connectionpool.pool.listeningSockets.values(): - try: - i.close() - except: # nosec B110 # pylint:disable=bare-except - pass - for i in connectionpool.pool.outboundConnections.values(): - try: - i.close() - except: # nosec B110 # pylint:disable=bare-except - pass - for i in connectionpool.pool.inboundConnections.values(): - try: - i.close() - except: # nosec B110 # pylint:disable=bare-except - pass - - # just in case - asyncore.close_all() diff --git a/src/network/node.py b/src/network/node.py deleted file mode 100644 index 4c532b81..00000000 --- a/src/network/node.py +++ /dev/null @@ -1,7 +0,0 @@ -""" -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 deleted file mode 100644 index 91bb0552..00000000 --- a/src/network/objectracker.py +++ /dev/null @@ -1,136 +0,0 @@ -""" -Module for tracking objects -""" -import time -from threading import RLock - -import connectionpool -from network import dandelion_ins -from randomtrackingdict import RandomTrackingDict - -haveBloom = False - -try: - # pybloomfiltermmap - from pybloomfilter import BloomFilter - haveBloom = True -except ImportError: - try: - # pybloom - from pybloom import BloomFilter - haveBloom = True - except ImportError: - pass - -# it isn't actually implemented yet so no point in turning it on -haveBloom = False - -# tracking pending downloads globally, for stats -missingObjects = {} - - -class ObjectTracker(object): - """Object tracker mixin""" - invCleanPeriod = 300 - invInitialCapacity = 50000 - invErrorRate = 0.03 - trackingExpires = 3600 - initialTimeOffset = 60 - - def __init__(self): - self.objectsNewToMe = RandomTrackingDict() - self.objectsNewToThem = {} - self.objectsNewToThemLock = RLock() - self.initInvBloom() - self.initAddrBloom() - self.lastCleaned = time.time() - - def initInvBloom(self): - """Init bloom filter for tracking. WIP.""" - if haveBloom: - # lock? - 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) - - def clean(self): - """Clean up tracking to prevent memory bloat""" - if self.lastCleaned < time.time() - ObjectTracker.invCleanPeriod: - if haveBloom: - if missingObjects == 0: - self.initInvBloom() - self.initAddrBloom() - else: - # release memory - deadline = time.time() - ObjectTracker.trackingExpires - with self.objectsNewToThemLock: - self.objectsNewToThem = { - k: v - for k, v in self.objectsNewToThem.iteritems() - if v >= deadline} - self.lastCleaned = time.time() - - def hasObj(self, hashid): - """Do we already have object?""" - if haveBloom: - return hashid in self.invBloom - return hashid in self.objectsNewToMe - - def handleReceivedInventory(self, hashId): - """Handling received inventory""" - if haveBloom: - self.invBloom.add(hashId) - try: - with self.objectsNewToThemLock: - del self.objectsNewToThem[hashId] - except KeyError: - pass - if hashId not in missingObjects: - missingObjects[hashId] = time.time() - self.objectsNewToMe[hashId] = True - - def handleReceivedObject(self, streamNumber, hashid): - """Handling received object""" - for i in connectionpool.pool.connections(): - if not i.fullyEstablished: - continue - try: - del i.objectsNewToMe[hashid] - except KeyError: - if streamNumber in i.streams and ( - not dandelion_ins.hasHash(hashid) - or dandelion_ins.objectChildStem(hashid) == i): - with i.objectsNewToThemLock: - i.objectsNewToThem[hashid] = time.time() - # update stream number, - # which we didn't have when we just received the dinv - # also resets expiration of the stem mode - dandelion_ins.setHashStream(hashid, streamNumber) - - if i == self: - try: - with i.objectsNewToThemLock: - 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) diff --git a/src/network/proxy.py b/src/network/proxy.py deleted file mode 100644 index ed1af127..00000000 --- a/src/network/proxy.py +++ /dev/null @@ -1,148 +0,0 @@ -""" -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') - - -class ProxyError(Exception): - """Base proxy exception class""" - errorCodes = ("Unknown error",) - - def __init__(self, code=-1): - self.code = code - try: - self.message = self.errorCodes[code] - except IndexError: - self.message = self.errorCodes[-1] - super(ProxyError, self).__init__(self.message) - - -class GeneralProxyError(ProxyError): - """General proxy error class (not specfic to an implementation)""" - errorCodes = ( - "Success", - "Invalid data", - "Not connected", - "Not available", - "Bad proxy type", - "Bad input", - "Timed out", - "Network unreachable", - "Connection refused", - "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 - _proxy = ("127.0.0.1", 9050) - _auth = None - _onion_proxy = None - _onion_auth = None - _remote_dns = True - - @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)): - 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) - ): - 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): - 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")) - else: - self.auth = None - self.connect( - self.onion_proxy - if address.host.endswith(".onion") and self.onion_proxy else - 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) - 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 deleted file mode 100644 index 88d3b740..00000000 --- a/src/network/receivequeuethread.py +++ /dev/null @@ -1,55 +0,0 @@ -""" -Process data incoming from network -""" -import errno -import Queue -import socket - -import connectionpool -from network.advanceddispatcher import UnknownStateError -from network import receiveDataQueue -from threads import StoppableThread - - -class ReceiveQueueThread(StoppableThread): - """This thread processes data received from the network - (which is done by the asyncore thread)""" - def __init__(self, num=0): - super(ReceiveQueueThread, self).__init__(name="ReceiveQueue_%i" % num) - - def run(self): - while not self._stopped: - try: - dest = receiveDataQueue.get(block=True, timeout=1) - except Queue.Empty: - continue - - if self._stopped: - break - - # cycle as long as there is data - # methods should return False if there isn't enough data, - # or the connection is to be aborted - - # state_* methods should return False if there isn't - # enough data, or the connection is to be aborted - - try: - connection = connectionpool.pool.getConnectionByAddr(dest) - # connection object not found - except KeyError: - receiveDataQueue.task_done() - continue - try: - connection.process() - # state isn't implemented - except UnknownStateError: - pass - except socket.error as err: - if err.errno == errno.EBADF: - connection.set_state("close", 0) - else: - self.logger.error('Socket error: %s', err) - except: # noqa:E722 - self.logger.error('Error processing', exc_info=True) - receiveDataQueue.task_done() diff --git a/src/network/socks4a.py b/src/network/socks4a.py deleted file mode 100644 index e9786168..00000000 --- a/src/network/socks4a.py +++ /dev/null @@ -1,147 +0,0 @@ -""" -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') - - -class Socks4aError(ProxyError): - """SOCKS4a error base class""" - errorCodes = ( - "Request granted", - "Request rejected or failed", - "Request rejected because SOCKS server cannot connect to identd" - " on the client", - "Request rejected because the client program and identd report" - " different user-ids", - "Unknown error" - ) - - -class Socks4a(Proxy): - """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 - self.close() - raise GeneralProxyError(1) - elif self.read_buf[1:2] != chr(0x5A).encode(): - # Connection failed - self.close() - if ord(self.read_buf[1:2]) in (91, 92, 93): - # socks 4 error - raise Socks4aError(ord(self.read_buf[1:2]) - 90) - else: - raise Socks4aError(4) - # Get the bound address/port - self.boundport = struct.unpack(">H", self.read_buf[2:4])[0] - self.boundaddr = self.read_buf[4:] - self.__proxysockname = (self.boundaddr, self.boundport) - if self.ipaddr: - self.__proxypeername = ( - socket.inet_ntoa(self.ipaddr), self.destination[1]) - else: - self.__proxypeername = (self.destination[0], self.destport) - self.set_state("proxy_handshake_done", length=8) - return True - - def proxy_sock_name(self): - """ - Handle return value when using SOCKS4a for DNS resolving - instead of connecting. - """ - 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])) - # If the given destination address is an IP address, we'll - # use the IPv4 address request even if remote resolving was specified. - try: - self.ipaddr = socket.inet_aton(self.destination[0]) - self.append_write_buf(self.ipaddr) - except socket.error: - # Well it's not an IP number, so it's probably a DNS name. - if self._remote_dns: - # Resolve remotely - rmtrslv = True - self.ipaddr = None - self.append_write_buf( - struct.pack("BBBB", 0x00, 0x00, 0x00, 0x01)) - else: - # Resolve locally - self.ipaddr = socket.inet_aton( - socket.gethostbyname(self.destination[0])) - self.append_write_buf(self.ipaddr) - if self._auth: - self.append_write_buf(self._auth[0]) - self.append_write_buf(chr(0x00).encode()) - if rmtrslv: - self.append_write_buf(self.destination[0] + chr(0x00).encode()) - self.set_state("pre_connect", length=0, expectBytes=8) - return True - - def state_pre_connect(self): - """Tell SOCKS4a to initiate a connection""" - try: - return Socks4a.state_pre_connect(self) - except Socks4aError as e: - self.close_reason = e.message - self.set_state("close") - - -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("BBBB", 0x00, 0x00, 0x00, 0x01)) - if self._auth: - self.append_write_buf(self._auth[0]) - self.append_write_buf(chr(0x00).encode()) - self.append_write_buf(self.host + chr(0x00).encode()) - self.set_state("pre_connect", length=0, expectBytes=8) - return True - - def resolved(self): - """ - 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()) diff --git a/src/network/socks5.py b/src/network/socks5.py deleted file mode 100644 index d1daae42..00000000 --- a/src/network/socks5.py +++ /dev/null @@ -1,224 +0,0 @@ -""" -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') - - -class Socks5AuthError(ProxyError): - """Rised when the socks5 protocol encounters an authentication error""" - errorCodes = ( - "Succeeded", - "Authentication is required", - "All offered authentication methods were rejected", - "Unknown username or invalid password", - "Unknown error" - ) - - -class Socks5Error(ProxyError): - """Rised when socks5 protocol encounters an error""" - errorCodes = ( - "Succeeded", - "General SOCKS server failure", - "Connection not allowed by ruleset", - "Network unreachable", - "Host unreachable", - "Connection refused", - "TTL expired", - "Command not supported", - "Address type not supported", - "Unknown error" - ) - - -class Socks5(Proxy): - """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: - self.append_write_buf(struct.pack('BBB', 0x05, 0x01, 0x00)) - self.set_state("auth_1", length=0, expectBytes=2) - return True - - def state_auth_1(self): - """Perform authentication if peer is requesting it.""" - ret = struct.unpack('BB', self.read_buf[:2]) - if ret[0] != 5: - # general error - raise GeneralProxyError(1) - elif ret[1] == 0: - # no auth required - self.set_state("auth_done", length=2) - elif ret[1] == 2: - # username/password - self.append_write_buf( - struct.pack( - 'BB', 1, len(self._auth[0])) + self._auth[0] + struct.pack( - 'B', len(self._auth[1])) + self._auth[1]) - self.set_state("auth_needed", length=2, expectBytes=2) - else: - if ret[1] == 0xff: - # auth error - raise Socks5AuthError(2) - else: - # other error - raise GeneralProxyError(1) - return True - - def state_auth_needed(self): - """Handle response to authentication attempt""" - ret = struct.unpack('BB', self.read_buf[0:2]) - if ret[0] != 1: - # general error - raise GeneralProxyError(1) - if ret[1] != 0: - # auth error - raise Socks5AuthError(3) - # all ok - self.set_state("auth_done", length=2) - return True - - def state_pre_connect(self): - """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() - raise GeneralProxyError(1) - elif self.read_buf[1:2] != chr(0x00).encode(): - # Connection failed - self.close() - if ord(self.read_buf[1:2]) <= 8: - raise Socks5Error(ord(self.read_buf[1:2])) - else: - raise Socks5Error(9) - # Get the bound address/port - elif self.read_buf[3:4] == chr(0x01).encode(): - self.set_state("proxy_addr_1", length=4, expectBytes=4) - elif self.read_buf[3:4] == chr(0x03).encode(): - self.set_state("proxy_addr_2_1", length=4, expectBytes=1) - else: - self.close() - raise GeneralProxyError(1) - return True - - def state_proxy_addr_1(self): - """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) - 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]) - 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 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 - # use the IPv4 address request even if remote resolving was specified. - try: - self.ipaddr = socket.inet_aton(self.destination[0]) - self.append_write_buf(chr(0x01).encode() + self.ipaddr) - except socket.error: # may be IPv6! - # Well it's not an IP number, so it's probably a DNS name. - if self._remote_dns: - # Resolve remotely - self.ipaddr = None - self.append_write_buf(chr(0x03).encode() + chr( - len(self.destination[0])).encode() + self.destination[0]) - else: - # Resolve locally - self.ipaddr = socket.inet_aton( - socket.gethostbyname(self.destination[0])) - self.append_write_buf(chr(0x01).encode() + self.ipaddr) - self.append_write_buf(struct.pack(">H", self.destination[1])) - self.set_state("pre_connect", length=0, expectBytes=4) - return True - - def state_pre_connect(self): - """Tell socks5 to initiate a connection""" - try: - return Socks5.state_pre_connect(self) - except Socks5Error as e: - self.close_reason = e.message - self.set_state("close") - - -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)) - - 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(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()) diff --git a/src/network/stats.py b/src/network/stats.py deleted file mode 100644 index 0ab1ae0f..00000000 --- a/src/network/stats.py +++ /dev/null @@ -1,78 +0,0 @@ -""" -Network statistics -""" -import time - -import asyncore_pollchoose as asyncore -import connectionpool -from objectracker import missingObjects - - -lastReceivedTimestamp = time.time() -lastReceivedBytes = 0 -currentReceivedSpeed = 0 -lastSentTimestamp = time.time() -lastSentBytes = 0 -currentSentSpeed = 0 - - -def connectedHostsList(): - """List of all the connected hosts""" - return connectionpool.pool.establishedConnections() - - -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)) - 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)) - lastReceivedBytes = currentReceivedBytes - lastReceivedTimestamp = currentTimestamp - return currentReceivedSpeed - - -def pendingDownload(): - """Getting pending downloads""" - return len(missingObjects) - - -def pendingUpload(): - """Getting pending uploads""" - # tmp = {} - # for connection in connectionpool.pool.inboundConnections.values() + \ - # connectionpool.pool.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 deleted file mode 100644 index db7d6595..00000000 --- a/src/network/tcp.py +++ /dev/null @@ -1,448 +0,0 @@ -""" -TCP protocol handler -""" -# pylint: disable=too-many-ancestors - -import logging -import math -import random -import socket -import time - -# magic imports! -import addresses -import l10n -import protocol -import state -import connectionpool -from bmconfigparser import config -from highlevelcrypto import randomBytes -from network import dandelion_ins, invQueue, receiveDataQueue -from queues import UISignalQueue -from tr import _translate - -import asyncore_pollchoose as asyncore -import knownnodes -from network.advanceddispatcher import AdvancedDispatcher -from network.bmproto import BMProto -from network.objectracker import ObjectTracker -from network.socks4a import Socks4aConnection -from network.socks5 import Socks5Connection -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 - - -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.skipUntil = 0 - if address is None and sock is not None: - self.destination = Peer(*sock.getpeername()) - self.isOutbound = False - TLSDispatcher.__init__(self, sock, server_side=True) - self.connectedAt = time.time() - logger.debug( - 'Received connection from %s:%i', - self.destination.host, self.destination.port) - self.nodeid = randomBytes(8) - elif address is not None and sock is not None: - TLSDispatcher.__init__(self, sock, server_side=False) - self.isOutbound = True - logger.debug( - 'Outbound proxy connection to %s:%i', - self.destination.host, self.destination.port) - else: - self.destination = address - self.isOutbound = True - self.create_socket( - socket.AF_INET6 if ":" in address.host else 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 - 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) - # take the stream with maximum amount of nodes - # +2 is to avoid problems with log(0) and log(1) - # 20 is avg connected nodes count - # 0.2 is avg message transmission time - if delay > 0: - if initial: - self.skipUntil = self.connectedAt + delay - if self.skipUntil > time.time(): - logger.debug( - 'Initial skipping processing getdata for %.2fs', - self.skipUntil - time.time()) - else: - logger.debug( - 'Skipping processing getdata due to missing object' - ' for %.2fs', delay) - self.skipUntil = time.time() + delay - - def 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 - UISignalQueue.put(('setStatusIcon', 'green')) - 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: - knownnodes.increaseRating(self.destination) - knownnodes.addKnownNode( - self.streams, self.destination, time.time()) - dandelion_ins.maybeAddStem(self, invQueue) - 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) - - 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 - # 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] = random.sample(filtered, elemCount) - for substream in addrs: - for peer, params in addrs[substream]: - templist.append((substream, peer, params["lastseen"])) - if templist: - self.append_write_buf(protocol.assembleAddrMessage(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)) - - # Select all hashes for objects in this stream. - bigInvList = {} - for stream in self.streams: - # may lock for a long time, but I think it's better than - # thousands of small locks - with self.objectsNewToThemLock: - for objHash in state.Inventory.unexpired_hashes_by_stream(stream): - # don't advertise stem objects on bigInv - if dandelion_ins.hasHash(objHash): - continue - bigInvList[objHash] = 0 - 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 - 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: - sendChunk() - payload = b'' - objectCount = 0 - - # flush - 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) - return - self.nodeid = randomBytes(8) - self.append_write_buf( - protocol.assembleVersionMessage( - self.destination.host, self.destination.port, - connectionpool.pool.streams, dandelion_ins.enabled, - False, nodeid=self.nodeid)) - self.connectedAt = time.time() - receiveDataQueue.put(self.destination) - - def handle_read(self): - """Callback for reading from a socket""" - TLSDispatcher.handle_read(self) - 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.fullyEstablished: - UISignalQueue.put(( - 'updateNetworkStatusTab', - (self.isOutbound, False, self.destination) - )) - if host_is_global: - knownnodes.addKnownNode( - self.streams, self.destination, time.time()) - dandelion_ins.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.pool.streams, dandelion_ins.enabled, - 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.pool.streams, dandelion_ins.enabled, - 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) - self.create_socket(socket.AF_INET, socket.SOCK_STREAM) - self.set_reuse_addr() - 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 - 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() - break - self.destination = 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 - - state.ownAddresses[Peer(*sock.getsockname())] = True - if ( - len(connectionpool.pool) - > 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.pool.addConnection( - TCPConnection(sock=sock)) - except socket.error: - pass 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 deleted file mode 100644 index 2d5d5e1b..00000000 --- a/src/network/tls.py +++ /dev/null @@ -1,220 +0,0 @@ -""" -SSL/TLS negotiation. -""" -import logging -import os -import socket -import ssl -import sys - -import network.asyncore_pollchoose as asyncore -import paths -from network.advanceddispatcher import AdvancedDispatcher -from network import receiveDataQueue - -logger = logging.getLogger('default') - -_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): - 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') - self.server_side = server_side - self.ciphers = ciphers - self.tlsStarted = False - self.tlsDone = False - self.tlsVersion = "N/A" - 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) - 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) - 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.setblocking(0) - self.want_read = self.want_write = True - self.set_state("tls_handshake") - return False - - @staticmethod - def state_tls_handshake(): - """ - Do nothing while TLS handshake is pending, as during this phase - we need to react to callbacks instead - """ - 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 - except AttributeError: - pass - 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 - if self.tlsStarted and not self.tlsDone and not self.write_buf: - logger.debug('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()): - return False - except AttributeError: - pass - 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: - self.tls_handshake() - else: - AdvancedDispatcher.handle_read(self) - except AttributeError: - 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" - self.handle_close() - - 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: - self.tls_handshake() - else: - AdvancedDispatcher.handle_write(self) - except AttributeError: - 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" - self.handle_close() - - 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)") - 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) - self.want_read = self.want_write = False - if err.args[0] == ssl.SSL_ERROR_WANT_READ: - logger.debug("want read") - self.want_read = True - if err.args[0] == ssl.SSL_ERROR_WANT_WRITE: - logger.debug("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) - else: - self.tlsVersion = "TLSv1" - logger.debug( - '%s:%i: TLS handshake success', - self.destination.host, self.destination.port) - # The handshake has completed, so remove this channel and... - self.del_channel() - self.set_socket(self.sslSocket) - self.tlsDone = True - - self.bm_proto_reset() - self.set_state("connection_fully_established") - receiveDataQueue.put(self.destination) - return False diff --git a/src/network/udp.py b/src/network/udp.py deleted file mode 100644 index 30643d40..00000000 --- a/src/network/udp.py +++ /dev/null @@ -1,150 +0,0 @@ -""" -UDP protocol handler -""" -import logging -import socket -import time - -# magic imports! -import protocol -import state -import connectionpool - -from network import receiveDataQueue -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)""" - port = 8444 - - 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 - self.streams = [1] - self.fullyEstablished = True - 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 - ) - self.set_socket_reuse() - logger.info("Binding UDP socket to %s:%i", host, self.port) - self.socket.bind((host, self.port)) - else: - self.socket = sock - self.set_socket_reuse() - self.listening = Peer(*self.socket.getsockname()) - self.destination = Peer(*self.socket.getsockname()) - ObjectTracker.__init__(self) - self.connecting = False - self.connected = True - self.announcing = announcing - self.set_state("bm_header", expectBytes=protocol.Header.size) - - def set_socket_reuse(self): - """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: - self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) - except AttributeError: - pass - - # disable most commands before doing research / testing - # only addr (peer discovery), error and object are implemented - - def bm_command_getdata(self): - # return BMProto.bm_command_getdata(self) - return True - - def bm_command_inv(self): - # return BMProto.bm_command_inv(self) - return True - - def bm_command_addr(self): - addresses = self._decode_addr() - # only allow peer discovery from private IPs in order to avoid - # attacks from random IPs on the internet - if not self.local: - return True - remoteport = False - for seenTime, stream, _, ip, port in addresses: - decodedIP = protocol.checkIPAddress(str(ip)) - if stream not in connectionpool.pool.streams: - continue - if (seenTime < time.time() - protocol.MAX_TIME_OFFSET - or seenTime > time.time() + protocol.MAX_TIME_OFFSET): - continue - if decodedIP is False: - # if the address isn't local, interpret it as - # the host's 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() - return True - - def bm_command_portcheck(self): - return True - - def bm_command_ping(self): - return True - - def bm_command_pong(self): - return True - - def bm_command_verack(self): - return True - - def bm_command_version(self): - return True - - def handle_connect(self): - return - - def writable(self): - return self.write_buf - - def readable(self): - return len(self.read_buf) < self._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) - return - - self.destination = Peer(*addr) - 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 - 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) - self.slice_write_buf(retval) diff --git a/src/network/uploadthread.py b/src/network/uploadthread.py deleted file mode 100644 index 60209832..00000000 --- a/src/network/uploadthread.py +++ /dev/null @@ -1,69 +0,0 @@ -""" -`UploadThread` class definition -""" -import time - -import random -import protocol -import state -import connectionpool -from randomtrackingdict import RandomTrackingDict -from network import dandelion_ins -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 = connectionpool.pool.establishedConnections() - random.shuffle(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_ins.hasHash(chunk) and \ - i != dandelion_ins.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', state.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..b643ca57 100644 --- a/src/openclpow.py +++ b/src/openclpow.py @@ -1,111 +1,95 @@ -""" -Module for Proof of Work using OpenCL -""" -import logging -import os -from struct import pack - -import paths -from bmconfigparser import config -from state import shutdown - -try: - import numpy - import pyopencl as cl - libAvailable = True -except ImportError: - libAvailable = False - - -logger = logging.getLogger('default') - -ctx = False -queue = False -program = False -gpus = [] -enabledGpus = [] -vendors = [] -hash_dt = None - - -def initCL(): - """Initlialise OpenCL engine""" - global ctx, queue, program, hash_dt # pylint: disable=global-statement - if libAvailable is False: - return - del enabledGpus[:] - del vendors[:] - del gpus[:] - ctx = False - try: - hash_dt = numpy.dtype([('target', numpy.uint64), ('v', numpy.str_, 73)]) - try: - for platform in cl.get_platforms(): - gpus.extend(platform.get_devices(device_type=cl.device_type.GPU)) - if config.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 - pass - if enabledGpus: - ctx = cl.Context(devices=enabledGpus) - queue = cl.CommandQueue(ctx) - f = open(os.path.join(paths.codePath(), "bitmsghash", 'bitmsghash.cl'), 'r') - fstr = ''.join(f.readlines()) - program = cl.Program(ctx, fstr).build(options="") - logger.info("Loaded OpenCL kernel") - else: - logger.info("No OpenCL GPUs found") - del enabledGpus[:] - except Exception: - logger.error("OpenCL fail: ", exc_info=True) - del enabledGpus[:] - - -def openclAvailable(): - """Are there any OpenCL GPUs available?""" - return bool(gpus) - - -def openclEnabled(): - """Is OpenCL enabled (and available)?""" - return bool(enabledGpus) - - -def do_opencl_pow(hash_, target): - """Perform PoW using OpenCL""" - output = numpy.zeros(1, dtype=[('v', numpy.uint64, 1)]) - if not enabledGpus: - return output[0][0] - - data = numpy.zeros(1, dtype=hash_dt, order='C') - data[0]['v'] = ("0000000000000000" + hash_).decode("hex") - data[0]['target'] = target - - hash_buf = cl.Buffer(ctx, cl.mem_flags.READ_ONLY | cl.mem_flags.COPY_HOST_PTR, hostbuf=data) - dest_buf = cl.Buffer(ctx, cl.mem_flags.WRITE_ONLY, output.nbytes) - - kernel = program.kernel_sha512 - worksize = kernel.get_work_group_info(cl.kernel_work_group_info.WORK_GROUP_SIZE, enabledGpus[0]) - - kernel.set_arg(0, hash_buf) - kernel.set_arg(1, dest_buf) - - progress = 0 - globamt = worksize * 2000 - - while output[0][0] == 0 and shutdown == 0: - kernel.set_arg(2, pack(" 0): + ctx = cl.Context(devices=gpus) + queue = cl.CommandQueue(ctx) + f = open(os.path.join(codePath(), "bitmsghash", 'bitmsghash.cl'), 'r') + fstr = ''.join(f.readlines()) + program = cl.Program(ctx, fstr).build(options="") + logger.info("Loaded OpenCL kernel") + else: + logger.info("No OpenCL GPUs found") + ctx = False + except Exception as e: + logger.error("OpenCL fail: ", exc_info=True) + ctx = False + +def has_opencl(): + global ctx + return (ctx != False) + +def do_opencl_pow(hash, target): + global ctx, queue, program, gpus, hash_dt + + output = numpy.zeros(1, dtype=[('v', numpy.uint64, 1)]) + if (ctx == False): + return output[0][0] + + data = numpy.zeros(1, dtype=hash_dt, order='C') + data[0]['v'] = ("0000000000000000" + hash).decode("hex") + data[0]['target'] = target + + hash_buf = cl.Buffer(ctx, cl.mem_flags.READ_ONLY | cl.mem_flags.COPY_HOST_PTR, hostbuf=data) + dest_buf = cl.Buffer(ctx, cl.mem_flags.WRITE_ONLY, output.nbytes) + + kernel = program.kernel_sha512 + worksize = kernel.get_work_group_info(cl.kernel_work_group_info.WORK_GROUP_SIZE, gpus[0]) + + kernel.set_arg(0, hash_buf) + kernel.set_arg(1, dest_buf) + + start = time.time() + progress = 0 + globamt = worksize*2000 + + while output[0][0] == 0 and shutdown == 0: + kernel.set_arg(2, pack("Q',hashlib.sha512(hashlib.sha512(pack('>Q',nonce) + initialHash).digest()).digest()[0:8]) + print "{} - value {} < {}".format(nonce, trialValue, target) + diff --git a/src/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 deleted file mode 100644 index e0d43334..00000000 --- a/src/paths.py +++ /dev/null @@ -1,129 +0,0 @@ -""" -Path related functions -""" -import logging -import os -import re -import sys -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 -# binary distributions vs source distributions. -frozen = getattr(sys, 'frozen', None) - - -def lookupExeFolder(): - """Returns executable folder path""" - if frozen: - exeFolder = ( - # 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')) - elif __file__: - exeFolder = os.path.dirname(__file__) - else: - return '' - return exeFolder + os.path.sep - - -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 - elif sys.platform == 'darwin': - try: - dataFolder = os.path.join( - os.environ['HOME'], - 'Library/Application Support/', APPNAME - ) + '/' - - 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 - else: - try: - dataFolder = os.path.join(os.environ['XDG_CONFIG_HOME'], APPNAME) - except KeyError: - dataFolder = os.path.join(os.environ['HOME'], '.config', APPNAME) - - # 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) - except IOError: - # Old directory may not exist. - pass - dataFolder = dataFolder + os.path.sep - 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) - - -def tail(f, lines=20): - """Returns last lines in the f file object""" - total_lines_wanted = lines - - BLOCK_SIZE = 1024 - f.seek(0, 2) - 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 = [] - while lines_to_go > 0 and block_end_byte > 0: - if block_end_byte - BLOCK_SIZE > 0: - # read the last block we haven't yet read - f.seek(block_number * BLOCK_SIZE, 2) - blocks.append(f.read(BLOCK_SIZE)) - else: - # file too small, start from begining - f.seek(0, 0) - # only read what was not read - blocks.append(f.read(block_end_byte)) - lines_found = blocks[-1].count('\n') - lines_to_go -= lines_found - block_end_byte -= BLOCK_SIZE - block_number -= 1 - all_read_text = ''.join(reversed(blocks)) - return '\n'.join(all_read_text.splitlines()[-total_lines_wanted:]) - - -def lastCommit(): - """ - Returns last commit information as dict with 'commit' and 'time' keys - """ - githeadfile = os.path.join(codePath(), '..', '.git', 'logs', 'HEAD') - result = {} - if os.path.isfile(githeadfile): - try: - with open(githeadfile, 'rt') as githead: - line = tail(githead, 1) - result['commit'] = line.split()[1] - result['time'] = datetime.fromtimestamp( - float(re.search(r'>\s*(.*?)\s', line).group(1)) - ) - except (IOError, AttributeError, TypeError): - pass - return result diff --git a/src/plugins/__init__.py b/src/plugins/__init__.py deleted file mode 100644 index 285009df..00000000 --- a/src/plugins/__init__.py +++ /dev/null @@ -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 deleted file mode 100644 index b471d2ef..00000000 --- a/src/plugins/indicator_libmessaging.py +++ /dev/null @@ -1,78 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Indicator plugin using libmessaging -""" - -import gi -gi.require_version('MessagingMenu', '1.0') # noqa:E402 -from gi.repository import MessagingMenu - -from pybitmessage.bitmessageqt.utils import str_broadcast_subscribers -from pybitmessage.tr import _translate - - -class IndicatorLibmessaging(object): - """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 - self.app = None - return - - self._menu = { - 'send': unicode(_translate('MainWindow', 'Send')), - 'messages': unicode(_translate('MainWindow', 'Messages')), - 'subscriptions': unicode(_translate('MainWindow', 'Subscriptions')) - } - - self.new_message_item = self.new_broadcast_item = None - self.form = form - self.show_unread() - - def __del__(self): - if self.app: - self.app.unregister() - - def activate(self, app, source): # pylint: disable=unused-argument - """Activate the libmessaging indicator plugin""" - self.form.appIndicatorInbox( - self.new_message_item if source == 'messages' - else self.new_broadcast_item - ) - - 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() - ): - if count > 0: - if self.app.has_source(source): - self.app.set_source_count(source, count) - else: - self.app.append_source_with_count( - source, None, self._menu[source], count) - if draw_attention: - self.app.draw_attention(source) - - # update the Ubuntu messaging menu - def __call__(self, draw_attention, item=None, to_label=None): - if not self.app: - return - # remember this item to that the activate() can find it - if item: - if to_label == str_broadcast_subscribers: - self.new_broadcast_item = item - else: - self.new_message_item = item - - self.show_unread(draw_attention) - - -connect_plugin = IndicatorLibmessaging diff --git a/src/plugins/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 deleted file mode 100644 index f851737d..00000000 --- a/src/plugins/notification_notify2.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Notification plugin using notify2 -""" - -import gi -gi.require_version('Notify', '0.7') -from gi.repository import Notify # noqa:E402 - -Notify.init('pybitmessage') - - -def connect_plugin(title, subtitle, category, _, icon): - """Plugin for notify2""" - 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 deleted file mode 100644 index 629de0a6..00000000 --- a/src/plugins/plugin.py +++ /dev/null @@ -1,51 +0,0 @@ -# -*- 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. - """ - for ep in pkg_resources.iter_entry_points('bitmessage.' + group): - if name and ep.name == name or not point or ep.name.startswith(point): - try: - plugin = ep.load().connect_plugin - if ep.name == fallback: - _fallback = plugin - else: - yield plugin - except (AttributeError, - ImportError, - ValueError, - pkg_resources.DistributionNotFound, - pkg_resources.UnknownExtra): - logger.debug( - 'Problem while loading %s', ep.name, exc_info=True) - continue - try: - yield _fallback - except NameError: - pass - - -def get_plugin(*args, **kwargs): - """ - :return: first available plugin from :func:`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/sound_canberra.py b/src/plugins/sound_canberra.py deleted file mode 100644 index 9fea8197..00000000 --- a/src/plugins/sound_canberra.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Sound theme plugin using pycanberra -""" - -import pycanberra -from pybitmessage.bitmessageqt import sound - -_canberra = pycanberra.Canberra() - -_theme = { - sound.SOUND_UNKNOWN: 'message-new-email', - sound.SOUND_CONNECTED: 'network-connectivity-established', - sound.SOUND_DISCONNECTED: 'network-connectivity-lost', - sound.SOUND_CONNECTION_GREEN: 'network-connectivity-established' -} - - -def connect_plugin(category, label=None): # pylint: disable=unused-argument - """This function implements the entry point.""" - try: - _canberra.play(0, pycanberra.CA_PROP_EVENT_ID, _theme[category], None) - except (KeyError, pycanberra.CanberraException): - pass diff --git a/src/plugins/sound_gstreamer.py b/src/plugins/sound_gstreamer.py deleted file mode 100644 index 8f3606dd..00000000 --- a/src/plugins/sound_gstreamer.py +++ /dev/null @@ -1,17 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Sound notification plugin using gstreamer -""" -import gi -gi.require_version('Gst', '1.0') -from gi.repository import Gst # noqa: E402 - -Gst.init(None) -_player = Gst.ElementFactory.make("playbin", "player") - - -def connect_plugin(sound_file): - """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 deleted file mode 100644 index c6b70f66..00000000 --- a/src/plugins/sound_playfile.py +++ /dev/null @@ -1,45 +0,0 @@ -# -*- 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 - - play_cmd = {} - - def _subprocess(*args): - FNULL = open(os.devnull, 'wb') - subprocess.call( - args, stdout=FNULL, stderr=subprocess.STDOUT, close_fds=True) # nosec B603 - - def connect_plugin(sound_file): - """This function implements the entry point.""" - global play_cmd # pylint: disable=global-statement - - ext = os.path.splitext(sound_file)[-1] - try: - return _subprocess(play_cmd[ext], sound_file) - except (KeyError, AttributeError): - pass - - programs = ['gst123', 'gst-play-1.0'] - if ext == '.wav': - programs.append('aplay') - elif ext == '.mp3': - programs += ['mpg123', 'mpg321', 'mpg321-mpg123'] - for cmd in programs: - try: - _subprocess(cmd, sound_file) - except OSError: - pass # log here! - else: - play_cmd[ext] = cmd - break diff --git a/src/proofofwork.py b/src/proofofwork.py index 5e157db9..3bc99c83 100644 --- a/src/proofofwork.py +++ b/src/proofofwork.py @@ -1,215 +1,128 @@ -# pylint: disable=too-many-branches,too-many-statements,protected-access -""" -Proof of work calculation -""" - -import ctypes -import os -import subprocess # nosec B404 +#import shared +#import time +#from multiprocessing import Pool, cpu_count +import hashlib +from struct import unpack, pack import sys -import tempfile -import time -from struct import pack, unpack - -import highlevelcrypto -import openclpow -import paths -import queues -import state -import tr -from bmconfigparser import config from debug import logger - -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) - +from shared import config, frozen, codePath, shutdown, safeConfigGetBoolean, UISignalQueue +import openclpow +import tr +import os +import ctypes def _set_idle(): if 'linux' in sys.platform: - os.nice(20) + import os + os.nice(20) # @UndefinedVariable 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 trial_value(nonce, initialHash): - """Calculate PoW trial value""" - trialValue, = unpack( - '>Q', highlevelcrypto.double_sha512( - pack('>Q', nonce) + initialHash)[0:8]) - return trialValue - - def _pool_worker(nonce, initialHash, target, pool_size): _set_idle() trialValue = float('inf') - while trialValue > target: + while trialValue > target and shutdown == 0: nonce += pool_size - trialValue = trial_value(nonce, initialHash) + 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: + while trialValue > target and shutdown == 0: nonce += 1 - trialValue = trial_value(nonce, initialHash) - if state.shutdown != 0: - raise StopIteration("Interrupted") + trialValue, = unpack('>Q',hashlib.sha512(hashlib.sha512(pack('>Q',nonce) + initialHash).digest()).digest()[0:8]) + if shutdown != 0: + raise Exception("Interrupted") logger.debug("Safe PoW done") return [trialValue, nonce] - def _doFastPoW(target, initialHash): logger.debug("Fast PoW start") + import time 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 + except: maxCores = 99999 if pool_size > maxCores: pool_size = maxCores + # temporarily disable handlers + int_handler = signal.getsignal(signal.SIGINT) + term_handler = signal.getsignal(signal.SIGTERM) + signal.signal(signal.SIGINT, signal.SIG_IGN) + signal.signal(signal.SIGTERM, signal.SIG_IGN) + pool = Pool(processes=pool_size) result = [] for i in range(pool_size): - result.append(pool.apply_async(_pool_worker, args=(i, initialHash, target, pool_size))) + result.append(pool.apply_async(_pool_worker, args = (i, initialHash, target, pool_size))) + + # re-enable handlers + signal.signal(signal.SIGINT, int_handler) + signal.signal(signal.SIGTERM, term_handler) while True: - if state.shutdown > 0: - try: - pool.terminate() - pool.join() - except: # nosec B110 # noqa:E722 # pylint:disable=bare-except - pass - raise StopIteration("Interrupted") + if shutdown >= 1: + pool.terminate() + raise Exception("Interrupted") for i in range(pool_size): if result[i].ready(): - try: - result[i].successful() - except AssertionError: - pool.terminate() - pool.join() - raise StopIteration("Interrupted") result = result[i].get() pool.terminate() - pool.join() + pool.join() #Wait for the workers to exit... 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 = trial_value(nonce, initialHash) - if state.shutdown != 0: - raise StopIteration("Interrupted") + 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 shutdown != 0: + raise Exception("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 = trial_value(nonce, initialHash) + 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) - openclpow.enabledGpus = [] + deviceNames = ", ".join(gpu.name for gpu in openclpow.gpus) + UISignalQueue.put(('updateStatusBar', tr._translate("MainWindow",'Your GPU(s) did not calculate correctly, disabling OpenCL. Please report to the developers.'))) + logger.error("Your GPUs (%s) did not calculate correctly, disabling OpenCL. Please report to the developers.", deviceNames) + openclpow.ctx = False raise Exception("GPU did not calculate correctly.") - if state.shutdown != 0: - raise StopIteration("Interrupted") + if shutdown != 0: + raise Exception("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 @@ -227,184 +140,88 @@ 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 - - -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))) - elif tried: - 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))) - - -def buildCPoW(): - """Attempt to build the PoW C module""" - if bmpow is not None: - return - if paths.frozen is not None: - notifyBuild(False) - return - if sys.platform in ["win32", "win64"]: - notifyBuild(False) - return - try: - if "bsd" in sys.platform: - # BSD make - subprocess.check_call([ # nosec B607, B603 - "make", "-C", os.path.join(paths.codePath(), "bitmsghash"), - '-f', 'Makefile.bsd']) - else: - # GNU make - subprocess.check_call([ # nosec B607, B603 - "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 (OSError, subprocess.CalledProcessError): - notifyBuild(True) - except: # noqa:E722 - logger.warning( - 'Unexpected exception rised when tried to build bitmsghash lib', - exc_info=True) - notifyBuild(True) - + return ret def run(target, initialHash): - """Run the proof of work thread""" - - if state.shutdown != 0: - raise # pylint: disable=misplaced-bare-raise target = int(target) - if openclpow.openclEnabled(): + if safeConfigGetBoolean('bitmessagesettings', 'opencl') and openclpow.has_opencl(): +# 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: + if shutdown != 0: + raise + pass # fallback if bmpow: try: return _doCPoW(target, initialHash) - except StopIteration: - raise - except: # nosec B110 # noqa:E722 # pylint:disable=bare-except - pass # fallback - if paths.frozen == "macosx_app" or not paths.frozen: + except: + if shutdown != 0: + raise + pass # fallback + if frozen == "macosx_app" or not frozen: # on my (Peter Surda) Windows 10, Windows Defender # does not like this and fights with PyBitmessage # over CPU, resulting in very slow PoW # added on 2015-11-29: multiprocesing.freeze_support() doesn't help try: return _doFastPoW(target, initialHash) - except StopIteration: - logger.error("Fast PoW got StopIteration") - raise - except: # noqa:E722 # pylint:disable=bare-except - logger.error("Fast PoW got exception:", exc_info=True) + except: + if shutdown != 0: + raise + pass #fallback try: return _doSafePoW(target, initialHash) - except StopIteration: - raise - except: # nosec B110 # noqa:E722 # pylint:disable=bare-except - pass # fallback - - -def resetPoW(): - """Initialise the OpenCL PoW""" - openclpow.initCL() - + except: + if shutdown != 0: + raise + pass #fallback # init - - -def init(): - """Initialise PoW""" - # pylint: disable=global-statement - global bitmsglib, bmpow - - openclpow.initCL() - if sys.platform == "win32": - if ctypes.sizeof(ctypes.c_voidp) == 4: - bitmsglib = 'bitmsghash32.dll' - else: - bitmsglib = 'bitmsghash64.dll' +bitmsglib = 'bitmsghash.so' +if "win32" == sys.platform: + if ctypes.sizeof(ctypes.c_voidp) == 4: + bitmsglib = 'bitmsghash32.dll' + else: + bitmsglib = 'bitmsghash64.dll' + try: + # MSVS + bso = ctypes.WinDLL(os.path.join(codePath(), "bitmsghash", bitmsglib)) + logger.info("Loaded C PoW DLL (stdcall) %s", bitmsglib) + bmpow = bso.BitmessagePOW + bmpow.restype = ctypes.c_ulonglong + _doCPoW(2**63, "") + logger.info("Successfully tested C PoW DLL (stdcall) %s", bitmsglib) + except: + logger.error("C PoW test fail.", exc_info=True) try: - # MSVS - bso = ctypes.WinDLL(os.path.join(paths.codePath(), "bitmsghash", bitmsglib)) - logger.info("Loaded C PoW DLL (stdcall) %s", bitmsglib) + # MinGW + bso = ctypes.CDLL(os.path.join(codePath(), "bitmsghash", bitmsglib)) + logger.info("Loaded C PoW DLL (cdecl) %s", bitmsglib) bmpow = bso.BitmessagePOW bmpow.restype = ctypes.c_ulonglong _doCPoW(2**63, "") - logger.info("Successfully tested C PoW DLL (stdcall) %s", bitmsglib) - except ValueError: - try: - # MinGW - bso = ctypes.CDLL(os.path.join(paths.codePath(), "bitmsghash", bitmsglib)) - logger.info("Loaded C PoW DLL (cdecl) %s", bitmsglib) - bmpow = bso.BitmessagePOW - 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) - bso = None - except Exception as e: - logger.error("Error: %s", e, exc_info=True) + logger.info("Successfully tested C PoW DLL (cdecl) %s", bitmsglib) + except: + logger.error("C PoW test fail.", exc_info=True) bso = None - else: - try: - bso = ctypes.CDLL(os.path.join(paths.codePath(), "bitmsghash", bitmsglib)) - except OSError: - import glob - try: - bso = ctypes.CDLL(glob.glob(os.path.join( - paths.codePath(), "bitmsghash", "bitmsghash*.so" - ))[0]) - except (OSError, IndexError): - bso = None - except: # noqa:E722 - bso = None - else: - logger.info("Loaded C PoW DLL %s", bitmsglib) - if bso: - try: - bmpow = bso.BitmessagePOW - bmpow.restype = ctypes.c_ulonglong - except: # noqa:E722 - bmpow = None - else: +else: + try: + bso = ctypes.CDLL(os.path.join(codePath(), "bitmsghash", bitmsglib)) + logger.info("Loaded C PoW DLL %s", bitmsglib) + except: + bso = None +if bso: + try: + bmpow = bso.BitmessagePOW + bmpow.restype = ctypes.c_ulonglong + except: bmpow = None - if bmpow is None: - buildCPoW() +else: + bmpow = None + diff --git a/src/protocol.py b/src/protocol.py index 96c980bb..40feaaf4 100644 --- a/src/protocol.py +++ b/src/protocol.py @@ -1,574 +1,14 @@ -""" -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 -import hashlib -import random -import socket -import sys -import time -from binascii import hexlify -from struct import Struct, pack, unpack - -import defaults -import highlevelcrypto -import state -from addresses import ( - encodeVarint, decodeVarint, decodeAddress, varintDecodeError) -from bmconfigparser import config -from debug import logger -from helper_sql import sqlExecute -from network.node import Peer -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 -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_DOESACK = 1 - -# Error types -STATUS_WARNING = 0 -STATUS_ERROR = 1 -STATUS_FATAL = 2 - -# Object types -OBJECT_GETPUBKEY = 0 -OBJECT_PUBKEY = 1 -OBJECT_MSG = 2 -OBJECT_BROADCAST = 3 -OBJECT_ONIONPEER = 0x746f72 -OBJECT_I2P = 0x493250 -OBJECT_ADDR = 0x61646472 - -eightBytesOfRandomDataUsedToDetectConnectionsToSelf = pack( - '>Q', random.randrange(1, 18446744073709551615)) # nosec B311 - -# Compiled struct for packing/unpacking headers -# New code should use CreatePacket instead of Header.pack -Header = Struct('!L12sL4s') - -VersionPacket = Struct('>LqQ20s4s36sH') - -# Bitfield - +import struct +import shared 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'): - bitfield |= BITFIELD_DOESACK - return pack('>I', bitfield) - + if not shared.safeConfigGetBoolean(address, 'dontsendack'): + bitfield |= shared.BITFIELD_DOESACK + return struct.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 - - -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) - elif host.find(':') == -1: - return b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF' + \ - socket.inet_aton(host) - 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'): - 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 - - -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': - 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': - # 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. - 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 not private: - logger.debug( - 'Ignoring IP address in loopback range: %s', - hostStandardFormat) - return hostStandardFormat if private else False - if host[0:1] == b'\x0A': # 10/8 - if not private: - logger.debug( - 'Ignoring IP address in private range: %s', hostStandardFormat) - return hostStandardFormat if private else False - if host[0:2] == b'\xC0\xA8': # 192.168/16 - if not private: - logger.debug( - 'Ignoring IP address in private range: %s', 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 not private: - logger.debug( - 'Ignoring IP address in private range: %s', 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 not private: - logger.debug('Ignoring loopback address: %s', 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 not private: - logger.debug('Ignoring local address: %s', hostStandardFormat) - return hostStandardFormat if private else False - if host[0] & 0xfe == 0xfc: - if not private: - logger.debug( - 'Ignoring unique local address: %s', hostStandardFormat) - return hostStandardFormat if private else False - return False if private else hostStandardFormat - - -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: - return True - 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 - 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 - """ - 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 time.time()) - if TTL < 300: - TTL = 300 - POW, = unpack('>Q', highlevelcrypto.double_sha512( - data[:8] + hashlib.sha512(data[8:]).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""" - 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) - 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( # pylint: disable=too-many-arguments - remoteHost, remotePort, participatingStreams, dandelion_enabled=True, server=False, nodeid=None, -): - """ - Construct the payload of a version message, - return the resulting bytes of running `CreatePacket` on it - """ - payload = b'' - 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 dandelion_enabled 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 += 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 += 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 dandelion_enabled 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')) - - if nodeid is not None: - payload += nodeid[0:8] - else: - payload += eightBytesOfRandomDataUsedToDetectConnectionsToSelf - userAgent = ('/PyBitmessage:%s/' % softwareVersion).encode('utf-8') - payload += encodeVarint(len(userAgent)) - payload += userAgent - - # Streams - payload += encodeVarint(len(participatingStreams)) - count = 0 - for stream in sorted(participatingStreams): - payload += encodeVarint(stream) - count += 1 - # protocol limit, see specification - if count >= 160000: - break - - return CreatePacket(b'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) - - -# 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. - """ - try: - addressVersion, streamNumber, ripe = decodeAddress(address)[1:] - - 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 - # We'll store the address version and stream number - # (and some more) in the pubkeys table. - storedData = data[20:readPosition] - - 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 - # the time through the tag. More data is appended onto - # signedData below after the decryption. - signedData = data[8:readPosition] - 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: # noqa:E722 - # FIXME: use a proper exception after `pyelliptic.ecc` is refactored. - # 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 - pubSigningKey = '\x04' + decryptedData[readPosition:readPosition + 64] - readPosition += 64 - pubEncryptionKey = '\x04' + decryptedData[readPosition:readPosition + 64] - readPosition += 64 - specifiedNonceTrialsPerByteLength = decodeVarint( - decryptedData[readPosition:readPosition + 10])[1] - readPosition += specifiedNonceTrialsPerByteLength - specifiedPayloadLengthExtraBytesLength = decodeVarint( - decryptedData[readPosition:readPosition + 10])[1] - readPosition += specifiedPayloadLengthExtraBytesLength - storedData += decryptedData[:readPosition] - signedData += decryptedData[:readPosition] - signatureLength, signatureLengthLength = decodeVarint( - decryptedData[readPosition:readPosition + 10]) - readPosition += signatureLengthLength - signature = decryptedData[readPosition:readPosition + signatureLength] - - if not highlevelcrypto.verify( - signedData, signature, hexlify(pubSigningKey)): - logger.info( - 'ECDSA verify failed (within decryptAndCheckPubkeyPayload)') - return 'failed' - - logger.info( - 'ECDSA verify passed (within decryptAndCheckPubkeyPayload)') - - embeddedRipe = highlevelcrypto.to_ripe(pubSigningKey, pubEncryptionKey) - - 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\nripe %s\n' - 'publicSigningKey in hex: %s\npublicEncryptionKey in hex: %s', - addressVersion, streamNumber, hexlify(ripe), - hexlify(pubSigningKey), hexlify(pubEncryptionKey) - ) - - 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.') - return 'failed' - except Exception: - logger.critical( - 'Pubkey decryption was UNsuccessful because of' - ' an unhandled exception! This is definitely a bug!', - exc_info=True - ) - return 'failed' + bitfield, = struct.unpack('>I', bitfieldBinary) + return (bitfield & flags) == flags \ No newline at end of file diff --git a/src/pybitmessage b/src/pybitmessage deleted file mode 100644 index decebfff..00000000 --- a/src/pybitmessage +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/python2.7 - -import os -import pkg_resources - -dist = pkg_resources.get_distribution('pybitmessage') -script_file = os.path.join(dist.location, dist.key, 'bitmessagemain.py') -new_globals = globals() -new_globals.update(__file__=script_file) - -execfile(script_file, new_globals) 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..fb8d0d46 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,9 +77,5 @@ 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: - OpenSSL.EVP_CIPHER_CTX_cleanup(self.ctx) + OpenSSL.EVP_CIPHER_CTX_cleanup(self.ctx) OpenSSL.EVP_CIPHER_CTX_free(self.ctx) diff --git a/src/pyelliptic/ecc.py b/src/pyelliptic/ecc.py index c670d023..269db952 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,37 +196,34 @@ 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: - OpenSSL.EC_KEY_set_method(own_key, OpenSSL.EC_KEY_OpenSSL()) - else: - OpenSSL.ECDH_set_method(own_key, OpenSSL.ECDH_OpenSSL()) + OpenSSL.ECDH_set_method(own_key, OpenSSL.ECDH_OpenSSL()) ecdh_keylen = OpenSSL.ECDH_compute_key( ecdh_keybuffer, 32, other_pub_key, own_key, 0) @@ -268,50 +243,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 @@ -323,7 +299,7 @@ class ECC(object): if privkey is not None: OpenSSL.BN_free(priv_key) - def sign(self, inputb, digest_alg=OpenSSL.digest_ecdsa_sha1): + def sign(self, inputb, digest_alg=OpenSSL.EVP_ecdsa): """ Sign the input with ECDSA method and returns the signature """ @@ -331,10 +307,7 @@ class ECC(object): size = len(inputb) buff = OpenSSL.malloc(inputb, size) digest = OpenSSL.malloc(0, 64) - if OpenSSL._hexversion > 0x10100000 and not OpenSSL._libreSSL: - md_ctx = OpenSSL.EVP_MD_CTX_new() - else: - md_ctx = OpenSSL.EVP_MD_CTX_create() + md_ctx = OpenSSL.EVP_MD_CTX_create() dgst_len = OpenSSL.pointer(OpenSSL.c_int(0)) siglen = OpenSSL.pointer(OpenSSL.c_int(0)) sig = OpenSSL.malloc(0, 151) @@ -343,42 +316,36 @@ 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: - OpenSSL.EVP_MD_CTX_new(md_ctx) - else: - OpenSSL.EVP_MD_CTX_init(md_ctx) + 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] @@ -389,53 +356,44 @@ class ECC(object): OpenSSL.BN_free(pub_key_y) OpenSSL.BN_free(priv_key) OpenSSL.EC_POINT_free(pub_key) - if OpenSSL._hexversion > 0x10100000 and not OpenSSL._libreSSL: - OpenSSL.EVP_MD_CTX_free(md_ctx) - else: - OpenSSL.EVP_MD_CTX_destroy(md_ctx) + OpenSSL.EVP_MD_CTX_destroy(md_ctx) - def verify(self, sig, inputb, digest_alg=OpenSSL.digest_ecdsa_sha1): + def verify(self, sig, inputb, digest_alg=OpenSSL.EVP_ecdsa): """ Verify the signature with the input and the local public key. - Returns a boolean. + Returns a boolean """ try: bsig = OpenSSL.malloc(sig, len(sig)) binputb = OpenSSL.malloc(inputb, len(inputb)) digest = OpenSSL.malloc(0, 64) dgst_len = OpenSSL.pointer(OpenSSL.c_int(0)) - if OpenSSL._hexversion > 0x10100000 and not OpenSSL._libreSSL: - md_ctx = OpenSSL.EVP_MD_CTX_new() - else: - md_ctx = OpenSSL.EVP_MD_CTX_create() + md_ctx = OpenSSL.EVP_MD_CTX_create() + key = OpenSSL.EC_KEY_new_by_curve_name(self.curve) 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_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,53 +401,42 @@ 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) OpenSSL.BN_free(pub_key_x) OpenSSL.BN_free(pub_key_y) OpenSSL.EC_POINT_free(pub_key) - if OpenSSL._hexversion > 0x10100000 and not OpenSSL._libreSSL: - OpenSSL.EVP_MD_CTX_free(md_ctx) - else: - OpenSSL.EVP_MD_CTX_destroy(md_ctx) + OpenSSL.EVP_MD_CTX_destroy(md_ctx) @staticmethod def encrypt(data, pubkey, ephemcurve=None, ciphername='aes-256-cbc'): """ 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 +445,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 851dfa15..be2f2afc 100644 --- a/src/pyelliptic/openssl.py +++ b/src/pyelliptic/openssl.py @@ -1,112 +1,45 @@ +#!/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_VERSION = 0 - OPENSSL_CFLAGS = 1 - library.OpenSSL_version.argtypes = [ctypes.c_int] - library.OpenSSL_version.restype = ctypes.c_char_p - version = library.OpenSSL_version(OPENSSL_VERSION) - cflags = library.OpenSSL_version(OPENSSL_CFLAGS) - library.OpenSSL_version_num.restype = ctypes.c_long - hexversion = library.OpenSSL_version_num() - except AttributeError: - try: - # OpenSSL 1.0 - SSLEAY_VERSION = 0 - SSLEAY_CFLAGS = 2 - library.SSLeay.restype = ctypes.c_long - library.SSLeay_version.restype = ctypes.c_char_p - library.SSLeay_version.argtypes = [ctypes.c_int] - version = library.SSLeay_version(SSLEAY_VERSION) - cflags = library.SSLeay_version(SSLEAY_CFLAGS) - hexversion = library.SSLeay() - except AttributeError: - # 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.pointer = ctypes.pointer self.c_int = ctypes.c_int @@ -114,38 +47,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,149 +84,81 @@ 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.ECDH_OpenSSL = self._lib.ECDH_OpenSSL + self._lib.ECDH_OpenSSL.restype = ctypes.c_void_p + self._lib.ECDH_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] - else: - self.ECDH_OpenSSL = self._lib.ECDH_OpenSSL - self._lib.ECDH_OpenSSL.restype = ctypes.c_void_p - self._lib.ECDH_OpenSSL.argtypes = [] + 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_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.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.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 +181,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 @@ -357,14 +209,9 @@ class _OpenSSL(object): 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 - self.EVP_CIPHER_CTX_reset.argtypes = [ctypes.c_void_p] - else: - self.EVP_CIPHER_CTX_cleanup = self._lib.EVP_CIPHER_CTX_cleanup - self.EVP_CIPHER_CTX_cleanup.restype = ctypes.c_int - self.EVP_CIPHER_CTX_cleanup.argtypes = [ctypes.c_void_p] + self.EVP_CIPHER_CTX_cleanup = self._lib.EVP_CIPHER_CTX_cleanup + self.EVP_CIPHER_CTX_cleanup.restype = ctypes.c_int + self.EVP_CIPHER_CTX_cleanup.argtypes = [ctypes.c_void_p] self.EVP_CIPHER_CTX_free = self._lib.EVP_CIPHER_CTX_free self.EVP_CIPHER_CTX_free.restype = None @@ -373,8 +220,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 +234,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,60 +249,38 @@ 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.EVP_ecdsa = self._lib.EVP_ecdsa + self._lib.EVP_ecdsa.restype = ctypes.c_void_p + self._lib.EVP_ecdsa.argtypes = [] 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_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_reset = self._lib.EVP_MD_CTX_reset - self.EVP_MD_CTX_reset.restype = None - self.EVP_MD_CTX_reset.argtypes = [ctypes.c_void_p] + 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_free = self._lib.EVP_MD_CTX_free - self.EVP_MD_CTX_free.restype = None - self.EVP_MD_CTX_free.argtypes = [ctypes.c_void_p] - - self.EVP_sha1 = self._lib.EVP_sha1 - self.EVP_sha1.restype = ctypes.c_void_p - self.EVP_sha1.argtypes = [] - - self.digest_ecdsa_sha1 = self.EVP_sha1 - else: - 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] - - self.EVP_ecdsa = self._lib.EVP_ecdsa - self._lib.EVP_ecdsa.restype = ctypes.c_void_p - self._lib.EVP_ecdsa.argtypes = [] - - self.digest_ecdsa_sha1 = self.EVP_ecdsa + 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] self.RAND_bytes = self._lib.RAND_bytes self.RAND_bytes.restype = ctypes.c_int self.RAND_bytes.argtypes = [ctypes.c_void_p, ctypes.c_int] + self.EVP_sha256 = self._lib.EVP_sha256 self.EVP_sha256.restype = ctypes.c_void_p self.EVP_sha256.argtypes = [] @@ -472,174 +296,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 +369,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 +385,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,96 +402,60 @@ 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.0.0.dylib') + ]) elif 'win32' in sys.platform or 'win64' in sys.platform: libdir.append(path.join(sys._MEIPASS, 'libeay32.dll')) else: libdir.extend([ path.join(sys._MEIPASS, 'libcrypto.so'), path.join(sys._MEIPASS, 'libssl.so'), - path.join(sys._MEIPASS, 'libcrypto.so.1.1.0'), - path.join(sys._MEIPASS, 'libssl.so.1.1.0'), - path.join(sys._MEIPASS, 'libcrypto.so.1.0.2'), - path.join(sys._MEIPASS, 'libssl.so.1.0.2'), - path.join(sys._MEIPASS, 'libcrypto.so.1.0.1'), - path.join(sys._MEIPASS, 'libssl.so.1.0.1'), path.join(sys._MEIPASS, 'libcrypto.so.1.0.0'), path.join(sys._MEIPASS, 'libssl.so.1.0.0'), - path.join(sys._MEIPASS, 'libcrypto.so.0.9.8'), - 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') - # kivy - elif 'ANDROID_ARGUMENT' in environ: - libdir.append('libcrypto1.1.so') - libdir.append('libssl1.1.so') 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 'freebsd' in sys.platform: libdir.append(find_library('ssl')) elif 'win32' in sys.platform or 'win64' in sys.platform: libdir.append(find_library('libeay32')) @@ -823,10 +463,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() +loadOpenSSL() \ No newline at end of file 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 0348d3f0..00000000 --- a/src/pyelliptic/tests/samples.py +++ /dev/null @@ -1,111 +0,0 @@ -"""Testing samples""" - -from binascii import unhexlify - - -# 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' - -# [chan] bitmessage -sample_privsigningkey_wif = \ - b'5K42shDERM5g7Kbi3JT5vsAWpXMqRhWZpX835M2pdSoqQQpJMYm' -sample_privencryptionkey_wif = \ - b'5HwugVWm31gnxtoYcvcK7oywH2ezYTh6Y4tzRxsndAeMi6NHqpA' -sample_wif_privsigningkey = \ - b'a2e8b841a531c1c558ee0680c396789c7a2ea3ac4795ae3f000caf9fe367d144' -sample_wif_privencryptionkey = \ - b'114ec0e2dca24a826a0eed064b0405b0ac148abc3b1d52729697f4d7b873fdc6' - -sample_factor = \ - 66858749573256452658262553961707680376751171096153613379801854825275240965733 -# G * sample_factor -sample_point = ( - 33567437183004486938355437500683826356288335339807546987348409590129959362313, - 94730058721143827257669456336351159718085716196507891067256111928318063085006 -) - -sample_deterministic_addr3 = b'2DBPTgeSawWYZceFD69AbDT5q4iUWtj1ZN' -sample_deterministic_addr4 = b'2cWzSnwjJ7yRP3nLEWUV5LisTZyREWSzUK' -sample_daddr3_512 = 18875720106589866286514488037355423395410802084648916523381 -sample_daddr4_512 = 25152821841976547050350277460563089811513157529113201589004 - - -# 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 1d1aecaf..00000000 --- a/src/pyelliptic/tests/test_arithmetic.py +++ /dev/null @@ -1,100 +0,0 @@ -""" -Test the arithmetic functions -""" - -from binascii import unhexlify -import unittest - -try: - from pyelliptic import arithmetic -except ImportError: - from pybitmessage.pyelliptic import arithmetic - -from .samples import ( - sample_deterministic_addr3, sample_deterministic_addr4, - sample_daddr3_512, sample_daddr4_512, - sample_factor, sample_point, sample_pubsigningkey, sample_pubencryptionkey, - sample_privsigningkey, sample_privencryptionkey, - sample_privsigningkey_wif, sample_privencryptionkey_wif, - sample_wif_privsigningkey, sample_wif_privencryptionkey -) - - -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_base58(self): - """Test encoding/decoding base58 using arithmetic functions""" - self.assertEqual( - arithmetic.decode(arithmetic.changebase( - sample_deterministic_addr4, 58, 256), 256), sample_daddr4_512) - self.assertEqual( - arithmetic.decode(arithmetic.changebase( - sample_deterministic_addr3, 58, 256), 256), sample_daddr3_512) - self.assertEqual( - arithmetic.changebase( - arithmetic.encode(sample_daddr4_512, 256), 256, 58), - sample_deterministic_addr4) - self.assertEqual( - arithmetic.changebase( - arithmetic.encode(sample_daddr3_512, 256), 256, 58), - sample_deterministic_addr3) - - def test_wif(self): - """Decode WIFs of [chan] bitmessage and check the keys""" - self.assertEqual( - sample_wif_privsigningkey, - arithmetic.changebase(arithmetic.changebase( - sample_privsigningkey_wif, 58, 256)[1:-4], 256, 16)) - self.assertEqual( - sample_wif_privencryptionkey, - arithmetic.changebase(arithmetic.changebase( - sample_privencryptionkey_wif, 58, 256)[1:-4], 256, 16)) - - 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 deleted file mode 100644 index cee5ce8b..00000000 --- a/src/queues.py +++ /dev/null @@ -1,46 +0,0 @@ -"""Most of the queues used by bitmessage threads are defined here.""" - -import threading -import time - -from six.moves import queue - - -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. -objectProcessorQueue = ObjectProcessorQueue() -#: The address generator thread uses this queue to get information back -#: to the API thread. -apiAddressGeneratorReturnQueue = queue.Queue() -#: for exceptions -excQueue = queue.Queue() diff --git a/src/randomtrackingdict.py b/src/randomtrackingdict.py deleted file mode 100644 index 5bf19181..00000000 --- a/src/randomtrackingdict.py +++ /dev/null @@ -1,132 +0,0 @@ -""" -Track randomize ordered dict -""" -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): - self.dictionary = {} - self.indexDict = [] - self.len = 0 - self.pendingLen = 0 - self.lastPoll = 0 - self.lastObject = 0 - self.lock = RLock() - - def __len__(self): - return self.len - - def __contains__(self, key): - return key in self.dictionary - - def __getitem__(self, key): - return self.dictionary[key][1] - - def _swap(self, i1, i2): - with self.lock: - key1 = self.indexDict[i1] - key2 = self.indexDict[i2] - self.indexDict[i1] = key2 - self.indexDict[i2] = key1 - self.dictionary[key1][0] = i2 - self.dictionary[key2][0] = i1 - # for quick reassignment - return i2 - - def __setitem__(self, key, value): - with self.lock: - if key in self.dictionary: - self.dictionary[key][1] = value - else: - self.indexDict.append(key) - self.dictionary[key] = [self.len, value] - self._swap(self.len, self.len - self.pendingLen) - self.len += 1 - - def __delitem__(self, key): - if key not in self.dictionary: - raise KeyError - with self.lock: - index = self.dictionary[key][0] - # not pending - if index < self.len - self.pendingLen: - # left of pending part - index = self._swap(index, self.len - self.pendingLen - 1) - # pending - else: - self.pendingLen -= 1 - # end - self._swap(index, self.len - 1) - # if the following del is batched, performance of this single - # operation can improve 4x, but it's already very fast so we'll - # ignore it for the time being - del self.indexDict[-1] - del self.dictionary[key] - self.len -= 1 - - def setMaxPending(self, maxPending): - """ - 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()): - raise KeyError - - # pylint: disable=redefined-outer-name - 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(): - 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) - retval = [self.indexDict[i] for i in randomIndex] - - for i in sorted(randomIndex, reverse=True): - # swap with one below lowest pending - self._swap(i, self.len - self.pendingLen - 1) - self.pendingLen += 1 - self.lastPoll = time() - return retval diff --git a/src/shared.py b/src/shared.py index b85ddb20..ca9200e5 100644 --- a/src/shared.py +++ b/src/shared.py @@ -1,184 +1,581 @@ -""" -Some shared functions +from __future__ import division + +softwareVersion = '0.6.0' +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 base64 +import collections +import ConfigParser import os -import stat -import subprocess # nosec B404 +import pickle +import Queue +import random +import socket import sys +import stat +import threading +import time +import shutil # used for moving the data folder and copying keys.dat +import datetime +from os import path, environ +from struct import Struct +import traceback from binascii import hexlify # Project imports. +from addresses import * +from class_objectProcessorQueue import ObjectProcessorQueue import highlevelcrypto -import state -from addresses import decodeAddress, encodeVarint -from bmconfigparser import config -from debug import logger -from helper_sql import sqlQuery +import shared +#import helper_startup +from helper_sql import * +from helper_threading import * +config = ConfigParser.SafeConfigParser() 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 = {} +workerQueue = Queue.Queue() +UISignalQueue = Queue.Queue() +addressGeneratorQueue = Queue.Queue() +knownNodesLock = threading.Lock() +knownNodes = {} +sendDataQueues = [] #each sendData thread puts its queue in this list. +inventoryLock = threading.RLock() #Guarantees that two receiveDataThreads don't receive and process the same message concurrently (probably sent by a malicious individual) +printLock = threading.Lock() +appdata = '' #holds the location of the application data storage directory +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. +shutdown = 0 #Set to 1 by the doCleanShutdown function. Used to tell the proof of work worker threads to exit. +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. +numberOfObjectsThatWeHaveYetToGetPerPeer = {} +neededPubkeys = {} +eightBytesOfRandomDataUsedToDetectConnectionsToSelf = pack( + '>Q', random.randrange(1, 18446744073709551615)) +successfullyDecryptMessageTimings = [ + ] # A list of the amounts of time it took to successfully decrypt msg messages +apiAddressGeneratorReturnQueue = Queue.Queue( + ) # The address generator thread uses this queue to get information back to the API thread. +ackdataForWhichImWatching = {} +clientHasReceivedIncomingConnections = False #used by API command clientStatus +numberOfMessagesProcessed = 0 +numberOfBroadcastsProcessed = 0 +numberOfPubkeysProcessed = 0 +numberOfInventoryLookupsPerformed = 0 +numberOfBytesReceived = 0 # Used for the 'network status' page +numberOfBytesSent = 0 # Used for the 'network status' page +numberOfBytesReceivedLastSecond = 0 # used for the bandwidth rate limit +numberOfBytesSentLastSecond = 0 # used for the bandwidth rate limit +lastTimeWeResetBytesReceived = 0 # used for the bandwidth rate limit +lastTimeWeResetBytesSent = 0 # used for the bandwidth rate limit +sendDataLock = threading.Lock() # used for the bandwidth rate limit +receiveDataLock = threading.Lock() # used for the bandwidth rate limit +daemon = False +needToWriteKnownNodesToDisk = False # If True, the singleCleaner will write it to disk eventually. +maximumLengthOfTimeToBotherResendingMessages = 0 +objectProcessorQueue = ObjectProcessorQueue() # receiveDataThreads dump objects they hear on the network into this queue to be processed. +streamsInWhichIAmParticipating = {} +# sanity check, prevent doing ridiculous PoW +# 20 million PoWs equals approximately 2 days on dev's dual R9 290 +ridiculousDifficulty = 20000000 + +#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. + +# 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" + +# 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 +# binary distributions vs source distributions. +frozen = getattr(sys,'frozen', 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 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 + +# For UPnP +extPort = None + +# for Tor hidden service +socksIP = None + +#Compiled struct for packing/unpacking headers +#New code should use CreatePacket instead of Header.pack +Header = Struct('!L12sL4s') + +#Service flags +NODE_NETWORK = 1 +NODE_SSL = 2 + +#Bitfield flags +BITFIELD_DOESACK = 1 + +import collections + +InventoryItem = collections.namedtuple('InventoryItem', 'type stream payload expires tag') + + +class Inventory(collections.MutableMapping): + def __init__(self): + super(Inventory, 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._streams = collections.defaultdict(set) # key = streamNumer, value = a set which holds the inventory object hashes that we are aware of. This is used whenever we receive an inv message from a peer to check to see what items are new to us. We don't delete things out of it; instead, the singleCleaner thread clears and refills it every couple hours. + + def __contains__(self, hash): + global numberOfInventoryLookupsPerformed + with inventoryLock: + numberOfInventoryLookupsPerformed += 1 + if hash in self._inventory: + return True + return bool(sqlQuery('SELECT 1 FROM inventory WHERE hash=?', hash)) + + def __getitem__(self, hash): + with inventoryLock: + if hash in self._inventory: + return self._inventory[hash] + rows = sqlQuery('SELECT objecttype, streamnumber, payload, expirestime, tag FROM inventory WHERE hash=?', hash) + if not rows: + raise KeyError(hash) + return InventoryItem(*rows[0]) + + def __setitem__(self, hash, value): + with inventoryLock: + value = InventoryItem(*value) + self._inventory[hash] = value + self._streams[value.stream].add(hash) + + def __delitem__(self, hash): + raise NotImplementedError + + def __iter__(self): + with inventoryLock: + hashes = self._inventory.keys()[:] + hashes += (hash for hash, in sqlQuery('SELECT hash FROM inventory')) + return hashes.__iter__() + + def __len__(self): + with inventoryLock: + return len(self._inventory) + sqlQuery('SELECT count(*) FROM inventory')[0][0] + + def by_type_and_tag(self, type, tag): + with inventoryLock: + values = [value for value in self._inventory.values() if value.type == type and value.tag == tag] + values += (InventoryItem(*value) for value in sqlQuery('SELECT objecttype, streamnumber, payload, expirestime, tag FROM inventory WHERE objecttype=? AND tag=?', type, tag)) + return values + + def hashes_by_stream(self, stream): + with inventoryLock: + return self._streams[stream] + + def unexpired_hashes_by_stream(self, stream): + with inventoryLock: + t = int(time.time()) + hashes = [hash for hash, value in self._inventory.items() if value.stream == stream and value.expires > t] + hashes += (payload for payload, in sqlQuery('SELECT hash FROM inventory WHERE streamnumber=? AND expirestime>?', stream, t)) + return hashes + + def flush(self): + with inventoryLock: # If you use both the inventoryLock and the sqlLock, always use the inventoryLock OUTSIDE of the sqlLock. + with SqlBulkExecute() as sql: + for hash, value in self._inventory.items(): + sql.execute('INSERT INTO inventory VALUES (?, ?, ?, ?, ?, ?)', hash, *value) + self._inventory.clear() + + def clean(self): + with inventoryLock: + sqlExecute('DELETE FROM inventory WHERE expirestime -1: + return '\xfd\x87\xd8\x7e\xeb\x43' + base64.b32decode(host.split(".")[0], True) + elif host.find(':') == -1: + return '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF' + \ + socket.inet_aton(host) + else: + return socket.inet_pton(socket.AF_INET6, host) + +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): + return True + return False + +def checkSocksIP(host): + try: + if socksIP is None or not socksIP: + socksIP = socket.gethostbyname(config.get("bitmessagesettings", "sockshostname")) + except NameError: + socksIP = socket.gethostbyname(config.get("bitmessagesettings", "sockshostname")) + return socksIP == host + +def assembleVersionMessage(remoteHost, remotePort, myStreamNumber, server = False): + payload = '' + payload += pack('>L', 3) # protocol version. + payload += pack('>q', NODE_NETWORK|(NODE_SSL if haveSSL(server) else 0)) # bitflags of the services I offer. + payload += pack('>q', int(time.time())) + + 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: + payload += encodeHost(remoteHost) + payload += pack('>H', remotePort) # remote IPv6 and port + + payload += pack('>q', 1) # bitflags of the services I offer. + 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 safeConfigGetBoolean('bitmessagesettings', 'upnp') and 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', shared.config.getint('bitmessagesettings', 'onionport')) + else: # no extPort and not incoming over Tor + payload += pack('>H', shared.config.getint('bitmessagesettings', 'port')) + + random.seed() + payload += eightBytesOfRandomDataUsedToDetectConnectionsToSelf + userAgent = '/PyBitmessage:' + shared.softwareVersion + '/' + payload += encodeVarint(len(userAgent)) + payload += userAgent + payload += encodeVarint( + 1) # The number of streams about which I care. PyBitmessage currently only supports 1 per connection. + payload += encodeVarint(myStreamNumber) + + return CreatePacket('version', payload) + +def assembleErrorMessage(fatal=0, banTime=0, inventoryVector='', errorText=''): + payload = encodeVarint(fatal) + payload += encodeVarint(banTime) + payload += encodeVarint(len(inventoryVector)) + payload += inventoryVector + payload += encodeVarint(len(errorText)) + payload += errorText + return CreatePacket('error', payload) + +def lookupExeFolder(): + if frozen: + if frozen == "macosx_app": + # targetdir/Bitmessage.app/Contents/MacOS/Bitmessage + exeFolder = path.dirname(path.dirname(path.dirname(path.dirname(sys.executable)))) + path.sep + else: + exeFolder = path.dirname(sys.executable) + path.sep + elif __file__: + exeFolder = path.dirname(__file__) + path.sep + else: + exeFolder = '' + return exeFolder + +def lookupAppdataFolder(): + APPNAME = "PyBitmessage" + if "BITMESSAGE_HOME" in environ: + dataFolder = environ["BITMESSAGE_HOME"] + if dataFolder[-1] not in [os.path.sep, os.path.altsep]: + dataFolder += os.path.sep + elif sys.platform == 'darwin': + if "HOME" in environ: + dataFolder = path.join(os.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() + + 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 = path.join(environ["XDG_CONFIG_HOME"], APPNAME) + except KeyError: + dataFolder = path.join(environ["HOME"], ".config", APPNAME) + + # Migrate existing data to the proper location if this is an existing install + try: + 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 + '/' + return dataFolder + +def codePath(): + if frozen == "macosx_app": + codePath = os.environ.get("RESOURCEPATH") + elif frozen: # windows + codePath = sys._MEIPASS + else: + codePath = os.path.dirname(__file__) + return codePath 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 safeConfigGetBoolean(section,field): + try: + return config.getboolean(section,field) + except Exception, err: + return False + +def safeConfigGet(section, option, default = None): + if config.has_option (section, option): + return config.get(section, option) + else: + return default + +def decodeWalletImportFormat(WIFstring): + 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) + 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(appdata + 'keys.dat') + configSections = config.sections() hasEnabledKeys = False - for addressInKeysFile in config.addresses(): - if not config.getboolean(addressInKeysFile, 'enabled'): - continue + for addressInKeysFile in configSections: + if addressInKeysFile <> 'bitmessagesettings': + isEnabled = config.getboolean(addressInKeysFile, 'enabled') + if isEnabled: + hasEnabledKeys = True + status,addressVersionNumber,streamNumber,hash = decodeAddress(addressInKeysFile) + if addressVersionNumber == 2 or addressVersionNumber == 3 or addressVersionNumber == 4: + # Returns a simple 32 bytes of information encoded in 64 Hex characters, + # or null if there was an error. + privEncryptionKey = hexlify(decodeWalletImportFormat( + config.get(addressInKeysFile, 'privencryptionkey'))) - hasEnabledKeys = True + 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 - addressVersionNumber, streamNumber, hashobj = decodeAddress( - addressInKeysFile)[1:] - if addressVersionNumber not in (2, 3, 4): - logger.error( - 'Error in reloadMyAddressHashes: Can\'t handle' - ' address versions other than 2, 3, or 4.') - continue - - # Returns a simple 32 bytes of information encoded in 64 Hex characters - try: - privEncryptionKey = hexlify( - highlevelcrypto.decodeWalletImportFormat(config.get( - addressInKeysFile, 'privencryptionkey').encode() - )) - except ValueError: - logger.error( - 'Error in reloadMyAddressHashes: failed to decode' - ' one of the private keys for address %s', addressInKeysFile) - continue - # It is 32 bytes encoded as 64 hex characters - if len(privEncryptionKey) == 64: - myECCryptorObjects[hashobj] = \ - highlevelcrypto.makeCryptor(privEncryptionKey) - myAddressesByHash[hashobj] = addressInKeysFile - tag = highlevelcrypto.double_sha512( - encodeVarint(addressVersionNumber) - + encodeVarint(streamNumber) + hashobj)[32:] - myAddressesByTag[tag] = addressInKeysFile + else: + 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(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 = highlevelcrypto.double_sha512( - encodeVarint(addressVersionNumber) - + encodeVarint(streamNumber) + hashobj - ) + 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 isProofOfWorkSufficient(data, + nonceTrialsPerByte=0, + payloadLengthExtraBytes=0): + if nonceTrialsPerByte < networkDefaultProofOfWorkNonceTrialsPerByte: + nonceTrialsPerByte = networkDefaultProofOfWorkNonceTrialsPerByte + if payloadLengthExtraBytes < networkDefaultPayloadLengthExtraBytes: + payloadLengthExtraBytes = networkDefaultPayloadLengthExtraBytes + endOfLifeTime, = unpack('>Q', data[8:16]) + 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)))) + +def doCleanShutdown(): + global shutdown, thisapp + shutdown = 1 #Used to tell proof of work worker threads and the objectProcessorThread to exit. + broadcastToSendDataQueues((0, 'shutdown', 'no data')) + objectProcessorQueue.put(('checkShutdownVariable', 'no data')) + for thread in threading.enumerate(): + if thread.isAlive() and isinstance(thread, StoppableThread): + thread.stopThread() + + knownNodesLock.acquire() + UISignalQueue.put(('updateStatusBar','Saving the knownNodes list of peers to disk...')) + output = open(appdata + 'knownnodes.dat', 'wb') + logger.info('finished opening knownnodes.dat. Now pickle.dump') + pickle.dump(knownNodes, output) + logger.info('Completed pickle.dump. Closing output...') + output.close() + knownNodesLock.release() + logger.info('Finished closing knownnodes.dat output file.') + 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...')) + 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. + while shutdown == 1: + time.sleep(.1) + + # 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) + + from class_outgoingSynSender import outgoingSynSender + for thread in threading.enumerate(): + if thread is not threading.currentThread() and isinstance(thread, StoppableThread) and not isinstance(thread, outgoingSynSender): + logger.debug("Waiting for thread %s", thread.name) + thread.join() + + if safeConfigGetBoolean('bitmessagesettings','daemon'): + logger.info('Clean shutdown complete.') + thisapp.cleanup() + os._exit(0) + else: + logger.info('Core shutdown complete.') + +# 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 sendDataQueues: + q.put(data) 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': @@ -186,47 +583,400 @@ 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 = shared.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' + +Peer = collections.namedtuple('Peer', ['host', 'port']) + +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 streamsInWhichIAmParticipating: + logger.debug('The streamNumber %s isn\'t one we are interested in.' % streamNumber) + return + + inventoryHash = calculateInventoryHash(data) + shared.numberOfInventoryLookupsPerformed += 1 + inventoryLock.acquire() + if inventoryHash in inventory: + logger.debug('We have already received this undefined object. Ignoring.') + inventoryLock.release() + return + objectType, = unpack('>I', data[16:20]) + inventory[inventoryHash] = ( + objectType, streamNumber, data, embeddedTime,'') + inventoryLock.release() + 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 streamsInWhichIAmParticipating: + logger.debug('The streamNumber %s isn\'t one we are interested in.' % streamNumber) + return + readPosition += streamNumberLength + inventoryHash = calculateInventoryHash(data) + shared.numberOfInventoryLookupsPerformed += 1 + inventoryLock.acquire() + if inventoryHash in inventory: + logger.debug('We have already received this msg message. Ignoring.') + inventoryLock.release() + return + # This msg message is valid. Let's let our peers know about it. + objectType = 2 + inventory[inventoryHash] = ( + objectType, streamNumber, data, embeddedTime,'') + inventoryLock.release() + 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 streamsInWhichIAmParticipating: + logger.debug('The streamNumber %s isn\'t one we are interested in.' % streamNumber) + return + readPosition += streamNumberLength + + shared.numberOfInventoryLookupsPerformed += 1 + inventoryHash = calculateInventoryHash(data) + inventoryLock.acquire() + if inventoryHash in inventory: + logger.debug('We have already received this getpubkey request. Ignoring it.') + inventoryLock.release() + return + + objectType = 0 + inventory[inventoryHash] = ( + objectType, streamNumber, data, embeddedTime,'') + inventoryLock.release() + # 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 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 = '' + + shared.numberOfInventoryLookupsPerformed += 1 + inventoryHash = calculateInventoryHash(data) + inventoryLock.acquire() + if inventoryHash in inventory: + logger.debug('We have already received this pubkey. Ignoring it.') + inventoryLock.release() + return + objectType = 1 + inventory[inventoryHash] = ( + objectType, streamNumber, data, embeddedTime, tag) + inventoryLock.release() + # 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 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 = '' + shared.numberOfInventoryLookupsPerformed += 1 + inventoryLock.acquire() + inventoryHash = calculateInventoryHash(data) + if inventoryHash in inventory: + logger.debug('We have already received this broadcast object. Ignoring.') + inventoryLock.release() + return + # It is valid. Let's let our peers know about it. + objectType = 3 + inventory[inventoryHash] = ( + objectType, streamNumber, data, embeddedTime, tag) + inventoryLock.release() + # 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 openKeysFile(): + if 'linux' in sys.platform: + subprocess.call(["xdg-open", shared.appdata + 'keys.dat']) + else: + os.startfile(shared.appdata + 'keys.dat') + +def writeKeysFile(): + fileName = shared.appdata + 'keys.dat' + 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: + # The backup failed. This can happen if the file didn't exist before. + fileNameExisted = False + # write the file + with open(fileName, 'wb') as configfile: + shared.config.write(configfile) + # delete the backup + if fileNameExisted: + os.remove(fileNameBak) + +from debug import logger diff --git a/src/shutdown.py b/src/shutdown.py deleted file mode 100644 index 441d655e..00000000 --- a/src/shutdown.py +++ /dev/null @@ -1,90 +0,0 @@ -"""shutdown function""" - -import os -import threading -import time - -from six.moves import queue - -import state -from debug import logger -from helper_sql import sqlQuery, sqlStoredProcedure -from network import StoppableThread -from network.knownnodes import saveKnownNodes -from queues import ( - addressGeneratorQueue, objectProcessorQueue, UISignalQueue, workerQueue) - - -def doCleanShutdown(): - """ - Used to tell all the treads to finish work and exit. - """ - state.shutdown = 1 - - 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...')) - 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.')) - 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...')) - state.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. - 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 - # objectProcessorThread committed before we close the program. - sqlQuery('SELECT address FROM subscriptions') - logger.info('Finished flushing inventory.') - sqlStoredProcedure('exit') - - # flush queues - for q in ( - workerQueue, UISignalQueue, addressGeneratorQueue, - objectProcessorQueue): - while True: - try: - q.get(False) - q.task_done() - except queue.Empty: - break - - if state.thisapp.daemon or not state.enableGUI: - logger.info('Clean shutdown complete.') - state.thisapp.cleanup() - os._exit(0) # pylint: disable=protected-access - else: - logger.info('Core shutdown complete.') - for thread in threading.enumerate(): - logger.debug('Thread %s still running', thread.name) diff --git a/src/singleinstance.py b/src/singleinstance.py deleted file mode 100644 index cff9d794..00000000 --- a/src/singleinstance.py +++ /dev/null @@ -1,111 +0,0 @@ -""" -This is based upon the singleton class from -`tendo `_ -which is under the Python Software Foundation License version 2 -""" - -import atexit -import os -import sys - -import state - -try: - import fcntl # @UnresolvedImport -except ImportError: - pass - - -class singleinstance(object): - """ - Implements a single instance application by creating a lock file - at appdata. - """ - 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)) - - if state.enableGUI and not self.daemon and not state.curses: - # Tells the already running (if any) application to get focus. - import bitmessageqt - bitmessageqt.init() - - self.lock() - - self.initialized = True - 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) - 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: - if e.errno == 13: - sys.exit( - 'Another instance of this application is' - ' already running') - raise - else: - pidLine = "%i\n" % self.lockPid - os.write(self.fd, pidLine) - else: # non Windows - 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) - 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') - else: - pidLine = "%i\n" % self.lockPid - self.fp.truncate(0) - self.fp.write(pidLine) - self.fp.flush() - - def cleanup(self): - """Release single instance lock""" - if not self.initialized: - return - if self.daemon and self.lockPid == os.getpid(): - # these are the two initial forks while daemonizing - try: - if sys.platform == 'win32': - if hasattr(self, 'fd'): - os.close(self.fd) - else: - fcntl.lockf(self.fp, fcntl.LOCK_UN) - except (IOError, OSError): - pass - - return - - try: - if sys.platform == 'win32': - if hasattr(self, 'fd'): - os.close(self.fd) - os.unlink(self.lockfile) - else: - fcntl.lockf(self.fp, fcntl.LOCK_UN) - if os.path.isfile(self.lockfile): - os.unlink(self.lockfile) - except (IOError, OSError): - pass diff --git a/src/singleton.py b/src/singleton.py index 5c6c43be..8bf7ed01 100644 --- a/src/singleton.py +++ b/src/singleton.py @@ -1,22 +1,84 @@ -""" -Singleton decorator definition -""" +#! /usr/bin/env python -from functools import wraps +import atexit +import errno +from multiprocessing import Process +import os +import sys +import shared +try: + import fcntl # @UnresolvedImport +except: + pass -def Singleton(cls): +class singleinstance: """ - Decorator implementing the singleton pattern: - it restricts the instantiation of a class to one "single" instance. - """ - instances = {} + Implements a single instance application by creating a lock file at appdata. - # 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] - return getinstance + 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(shared.appdata, 'singleton%s.lock' % flavor_id)) + + if not self.daemon and not shared.curses: + # Tells the already running (if any) application to get focus. + import bitmessageqt + bitmessageqt.init() + + self.lock() + + self.initialized = True + atexit.register(self.cleanup) + + def lock(self): + 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) + 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) + except OSError: + type, e, tb = sys.exc_info() + if e.errno == 13: + print 'Another instance of this application is already running' + sys.exit(-1) + print(e.errno) + raise + else: # non Windows + self.fp = open(self.lockfile, 'w') + try: + if self.daemon and self.lockPid != os.getpid(): + 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: + print 'Another instance of this application is already running' + sys.exit(-1) + + def cleanup(self): + if not self.initialized: + return + if self.daemon and self.lockPid == os.getpid(): + # these are the two initial forks while daemonizing + return + print "Cleaning up lockfile" + try: + if sys.platform == 'win32': + if hasattr(self, 'fd'): + os.close(self.fd) + os.unlink(self.lockfile) + else: + fcntl.lockf(self.fp, fcntl.LOCK_UN) + if os.path.isfile(self.lockfile): + os.unlink(self.lockfile) + except Exception, e: + pass 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..2bd03f48 --- /dev/null +++ b/src/socks/__init__.py @@ -0,0 +1,442 @@ +"""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") + +_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: + if e[0] == 101: + raise Socks5Error((3, _socks5errors[3])) + if e[0] == 111: + raise Socks5Error((5, _socks5errors[5])) + 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 + _orgsocket.connect(self,(self.__proxy[1], portnum)) + 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 deleted file mode 100644 index 90c9cf0d..00000000 --- a/src/state.py +++ /dev/null @@ -1,96 +0,0 @@ -""" -Global runtime variables. -""" - -neededPubkeys = {} - -extPort = None -"""For UPnP""" - -socksIP = None -"""for Tor hidden service""" - -appdata = "" -"""holds the location of the application data storage directory""" - -shutdown = 0 -""" -Set to 1 by the `.shutdown.doCleanShutdown` function. -Used to tell the 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 - -maximumNumberOfHalfOpenConnections = 0 - -maximumLengthOfTimeToBotherResendingMessages = 0 - -ownAddresses = {} - -discoveredPeers = {} - -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 - - -class Placeholder(object): # pylint:disable=too-few-public-methods - """Placeholder class""" - - def __init__(self, className): - self.className = className - - def __getattr__(self, name): - self._raise() - - def __setitem__(self, key, value): - self._raise() - - def __getitem__(self, key): - self._raise() - - def _raise(self): - raise NotImplementedError( - "Probabaly you forgot to initialize state variable for {}".format( - self.className - ) - ) - - -Inventory = Placeholder("Inventory") diff --git a/src/storage/__init__.py b/src/storage/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/storage/filesystem.py b/src/storage/filesystem.py deleted file mode 100644 index e756a820..00000000 --- a/src/storage/filesystem.py +++ /dev/null @@ -1,268 +0,0 @@ -""" -Module for using filesystem (directory with files) for inventory storage -""" -import logging -import os -import time -from binascii import hexlify, unhexlify -from threading import RLock - -from paths import lookupAppdataFolder -from .storage import InventoryItem, InventoryStorage - -logger = logging.getLogger('default') - - -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) - 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() - self._inventory = {} - self._load() - - def __contains__(self, hashval): - for streamDict in self._inventory.values(): - if hashval in streamDict: - return True - return False - - def __delitem__(self, hash_): - raise NotImplementedError - - def __getitem__(self, hashval): - for streamDict in self._inventory.values(): - try: - retval = streamDict[hashval] - except KeyError: - continue - if retval.payload is None: - retval = InventoryItem( - retval.type, - retval.stream, - self.getData(hashval), - retval.expires, - retval.tag) - return retval - raise KeyError(hashval) - - def __setitem__(self, hashval, value): - with self.lock: - value = InventoryItem(*value) - try: - os.makedirs(os.path.join( - self.baseDir, - FilesystemInventory.objectDir, - hexlify(hashval).decode())) - 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: - f.write(value.payload) - except IOError: - raise KeyError - try: - self._inventory[value.stream][hashval] = value - except KeyError: - self._inventory[value.stream] = {} - self._inventory[value.stream][hashval] = value - - def delHashId(self, hashval): - """Remove object from inventory""" - for stream in self._inventory: - try: - del self._inventory[stream][hashval] - except KeyError: - pass - with self.lock: - try: - os.remove( - os.path.join( - self.baseDir, - FilesystemInventory.objectDir, - hexlify(hashval).decode(), - FilesystemInventory.metadataFilename)) - except IOError: - pass - try: - os.remove( - os.path.join( - self.baseDir, - FilesystemInventory.objectDir, - hexlify(hashval).decode(), - FilesystemInventory.dataFilename)) - except IOError: - pass - try: - os.rmdir(os.path.join( - self.baseDir, - FilesystemInventory.objectDir, - hexlify(hashval).decode())) - except IOError: - pass - - def __iter__(self): - elems = [] - for streamDict in self._inventory.values(): - elems.extend(streamDict.keys()) - return elems.__iter__() - - def __len__(self): - retval = 0 - for streamDict in self._inventory.values(): - retval += len(streamDict) - return retval - - def _load(self): - newInventory = {} - for hashId in self.object_list(): - try: - objectType, streamNumber, expiresTime, tag = self.getMetadata( - hashId) - try: - newInventory[streamNumber][hashId] = InventoryItem( - objectType, streamNumber, None, expiresTime, tag) - except KeyError: - newInventory[streamNumber] = {} - newInventory[streamNumber][hashId] = InventoryItem( - objectType, streamNumber, None, expiresTime, tag) - except KeyError: - logger.debug( - 'error loading %s', hexlify(hashId), exc_info=True) - self._inventory = newInventory - - 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))] - - 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: - 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)] - 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 hashId, item in streamDict: - if item.type == objectType and item.tag == tag: - try: - if item.payload is None: - item.payload = self.getData(hashId) - except IOError: - continue - retval.append(InventoryItem( - item.type, - item.stream, - item.payload, - item.expires, - item.tag)) - return retval - - def hashes_by_stream(self, stream): - """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""" - try: - return [ - x for x, value in self._inventory[stream].items() - if value.expires > int(time.time())] - 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 - deletes = [] - for streamDict in self._inventory.values(): - for hashId, item in streamDict.items(): - if item.expires < minTime: - deletes.append(hashId) - for hashId in deletes: - self.delHashId(hashId) diff --git a/src/storage/sqlite.py b/src/storage/sqlite.py deleted file mode 100644 index eb5df098..00000000 --- a/src/storage/sqlite.py +++ /dev/null @@ -1,123 +0,0 @@ -""" -Sqlite Inventory -""" -import sqlite3 -import time -from threading import RLock - -from helper_sql import SqlBulkExecute, sqlExecute, sqlQuery -from .storage import InventoryItem, InventoryStorage - - -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() - - def __contains__(self, hash_): - with self.lock: - if hash_ in self._objects: - return True - rows = sqlQuery( - 'SELECT streamnumber FROM inventory WHERE hash=?', - sqlite3.Binary(hash_)) - if not rows: - return False - self._objects[hash_] = rows[0][0] - return True - - def __getitem__(self, hash_): - with self.lock: - if hash_ in self._inventory: - return self._inventory[hash_] - rows = sqlQuery( - 'SELECT objecttype, streamnumber, payload, expirestime, tag' - ' FROM inventory WHERE hash=?', sqlite3.Binary(hash_)) - if not rows: - raise KeyError(hash_) - return InventoryItem(*rows[0]) - - def __setitem__(self, hash_, value): - with self.lock: - value = InventoryItem(*value) - self._inventory[hash_] = value - self._objects[hash_] = value.stream - - def __delitem__(self, hash_): - raise NotImplementedError - - def __iter__(self): - with self.lock: - hashes = self._inventory.keys()[:] - hashes += (x for x, in sqlQuery('SELECT hash FROM inventory')) - return hashes.__iter__() - - def __len__(self): - with self.lock: - return len(self._inventory) + sqlQuery( - 'SELECT count(*) FROM inventory')[0][0] - - def by_type_and_tag(self, objectType, tag=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)) - 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)] - 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)) - 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 SqlBulkExecute() as sql: - for objectHash, value in self._inventory.items(): - sql.execute( - 'INSERT INTO inventory VALUES (?, ?, ?, ?, ?, ?)', - sqlite3.Binary(objectHash), *value) - self._inventory.clear() - - def clean(self): - """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 fd9b0d08..00000000 --- a/src/tests/core.py +++ /dev/null @@ -1,437 +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 -import network.connectionpool as connectionpool -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(connectionpool.pool) - 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(connectionpool.pool) - 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 connectionpool.pool.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(connectionpool.pool.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 connectionpool.pool.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""" - dandelion_enabled = True - # with single stream - msg = protocol.assembleVersionMessage('127.0.0.1', 8444, [1], dandelion_enabled) - decoded = self._decode_msg(msg, "IQQiiQlsLv") - peer, _, ua, streams = self._decode_msg(msg, "IQQiiQlsLv")[4:] - self.assertEqual( - peer, Node(11 if dandelion_enabled 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], dandelion_enabled) - 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 dd862318..00000000 --- a/src/tests/samples.py +++ /dev/null @@ -1,93 +0,0 @@ -"""Various sample data""" - -from binascii import unhexlify - -# hello, page 1 of the Specification -sample_hash_data = b'hello' -sample_double_sha512 = unhexlify( - '0592a10584ffabf96539f3d780d776828c67da1ab5b169e9e8aed838aaecc9ed36d49ff14' - '23c55f019e050c66c6324f53588be88894fef4dcffdb74b98e2b200') - -sample_bm160 = unhexlify('79a324faeebcbf9849f310545ed531556882487e') - -# 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( - '044a367f049ec16cb6b6118eb734a9962d10b8db59c890cd08f210c43ff08bdf09' - 'd16f502ca26cd0713f38988a1237f1fc8fa07b15653c996dc4013af6d15505ce') -sample_pubencryptionkey = unhexlify( - '044597d59177fc1d89555d38915f581b5ff2286b39d022ca0283d2bdd5c36be5d3' - 'ce7b9b97792327851a562752e4b79475d1f51f5a71352482b241227f45ed36a9') -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' -# RIPE hash on step 22 with signing key nonce 42 -sample_deterministic_ripe = b'00cfb69416ae76f68a81c459de4e13460c7d17eb' -# Deterministic addresses with stream 1 and versions 3, 4 -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') - -# [chan] bitmessage -sample_wif_privsigningkey = unhexlify( - b'a2e8b841a531c1c558ee0680c396789c7a2ea3ac4795ae3f000caf9fe367d144') -sample_wif_privencryptionkey = unhexlify( - b'114ec0e2dca24a826a0eed064b0405b0ac148abc3b1d52729697f4d7b873fdc6') -sample_privsigningkey_wif = \ - b'5K42shDERM5g7Kbi3JT5vsAWpXMqRhWZpX835M2pdSoqQQpJMYm' -sample_privencryptionkey_wif = \ - b'5HwugVWm31gnxtoYcvcK7oywH2ezYTh6Y4tzRxsndAeMi6NHqpA' 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 dd989562..00000000 --- a/src/tests/test_addresses.py +++ /dev/null @@ -1,86 +0,0 @@ - -import unittest -from binascii import unhexlify - -from pybitmessage import addresses, highlevelcrypto - -from .samples import ( - sample_address, sample_daddr3_512, sample_daddr4_512, - sample_deterministic_addr4, sample_deterministic_addr3, - sample_deterministic_ripe, sample_ripe, - sample_privsigningkey_wif, sample_privencryptionkey_wif, - sample_wif_privsigningkey, sample_wif_privencryptionkey) - -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)) - - def test_wif(self): - """Decode WIFs of [chan] bitmessage and check the keys""" - self.assertEqual( - sample_wif_privsigningkey, - highlevelcrypto.decodeWalletImportFormat( - sample_privsigningkey_wif)) - self.assertEqual( - sample_wif_privencryptionkey, - highlevelcrypto.decodeWalletImportFormat( - sample_privencryptionkey_wif)) - self.assertEqual( - sample_privsigningkey_wif, - highlevelcrypto.encodeWalletImportFormat( - sample_wif_privsigningkey)) - self.assertEqual( - sample_privencryptionkey_wif, - highlevelcrypto.encodeWalletImportFormat( - sample_wif_privencryptionkey)) - - with self.assertRaises(ValueError): - highlevelcrypto.decodeWalletImportFormat( - sample_privencryptionkey_wif[:-2]) diff --git a/src/tests/test_addressgenerator.py b/src/tests/test_addressgenerator.py deleted file mode 100644 index d7366fe4..00000000 --- a/src/tests/test_addressgenerator.py +++ /dev/null @@ -1,102 +0,0 @@ -"""Tests for AddressGenerator (with thread or not)""" - -from binascii import unhexlify - -import six -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')) - - def test_random(self): - """Test random address""" - self.command_queue.put(( - 'createRandomAddress', 4, 1, 'test_random', 1, '', False, 0, 0)) - addr = self.return_queue.get() - six.assertRegex(self, addr, r'^BM-') - six.assertRegex(self, addr[3:], r'[a-zA-Z1-9]+$') - self.assertLessEqual(len(addr[3:]), 40) - - self.assertEqual( - self.worker_queue.get(), ('sendOutOrStoreMyV4Pubkey', addr)) - self.assertEqual(self.config.get(addr, 'label'), 'test_random') - self.assertTrue(self.config.getboolean(addr, 'enabled')) diff --git a/src/tests/test_api.py b/src/tests/test_api.py deleted file mode 100644 index 0df145bc..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 - -import psutil -import six -from six.moves import xmlrpc_client # nosec - -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) - six.assertRegex( - self, self.api.getDeterministicAddress(self._seed, 2, 1), - r'^API Error 0002:') - - # This is here until the streams will be implemented - six.assertRegex( - self, self.api.getDeterministicAddress(self._seed, 3, 2), - r'API Error 0003:') - six.assertRegex( - self, self.api.createDeterministicAddresses(self._seed, 1, 4, 2), - r'API Error 0003:') - - six.assertRegex( - self, self.api.createDeterministicAddresses('', 1), - r'API Error 0001:') - six.assertRegex( - self, self.api.createDeterministicAddresses(self._seed, 1, 2), - r'API Error 0002:') - six.assertRegex( - self, self.api.createDeterministicAddresses(self._seed, 0), - r'API Error 0004:') - six.assertRegex( - self, 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') - six.assertRegex(self, addr, r'^BM-') - six.assertRegex(self, 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) - six.assertRegex(self, 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) - six.assertRegex(self, 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 - six.assertRegex( - self, 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 6e453b19..00000000 --- a/src/tests/test_api_thread.py +++ /dev/null @@ -1,96 +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.highlevelcrypto import calculateInventoryHash - -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 - import state - state.Inventory = Inventory() - - proofofwork.init() - self.assertEqual( - unhexlify(self.api.disseminatePreparedObject( - hexlify(sample_object_data).decode())), - calculateInventoryHash(sample_object_data)) - update_object = b'\x00' * 8 + pack( - '>Q', int(time.time() + 7200)) + sample_object_data[16:] - invhash = unhexlify(self.api.disseminatePreEncryptedMsg( - hexlify(update_object).decode() - )) - obj_type, obj_stream, obj_data = state.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 6dbb2f31..00000000 --- a/src/tests/test_crypto.py +++ /dev/null @@ -1,143 +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_bm160, sample_deterministic_ripe, sample_double_sha512, - sample_hash_data, sample_msg, sample_pubsigningkey, - sample_pubencryptionkey, sample_privsigningkey, sample_privencryptionkey, - sample_ripe, sample_seed, 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_double_sha512(self): - """Reproduce the example on page 1 of the Specification""" - self.assertEqual( - highlevelcrypto.double_sha512(sample_hash_data), - sample_double_sha512) - - def test_bm160(self): - """Formally check highlevelcrypto._bm160()""" - # pylint: disable=protected-access - self.assertEqual( - highlevelcrypto._bm160(sample_hash_data), sample_bm160) - - def test_to_ripe(self): - """Formally check highlevelcrypto.to_ripe()""" - self.assertEqual( - hexlify(highlevelcrypto.to_ripe( - sample_pubsigningkey, sample_pubencryptionkey)), - sample_ripe) - - def test_randomBytes(self): - """Dummy checks for random bytes""" - for n in (8, 32, 64): - data = highlevelcrypto.randomBytes(n) - self.assertEqual(len(data), n) - self.assertNotEqual(len(set(data)), 1) - self.assertNotEqual(data, highlevelcrypto.randomBytes(n)) - - def test_random_keys(self): - """Dummy checks for random keys""" - priv, pub = highlevelcrypto.random_keys() - self.assertEqual(len(priv), 32) - self.assertEqual(highlevelcrypto.pointMult(priv), pub) - - def test_deterministic_keys(self): - """Generate deterministic keys, make ripe and compare it to sample""" - # encodeVarint(42) = b'*' - sigkey = highlevelcrypto.deterministic_keys(sample_seed, b'*')[1] - enkey = highlevelcrypto.deterministic_keys(sample_seed, b'+')[1] - self.assertEqual( - sample_deterministic_ripe, - hexlify(highlevelcrypto.to_ripe(sigkey, enkey))) - - 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 d0b9ff6d..00000000 --- a/src/tests/test_inventory.py +++ /dev/null @@ -1,60 +0,0 @@ -"""Tests for inventory""" - -import os -import shutil -import struct -import tempfile -import time -import unittest - -import six - -from pybitmessage import highlevelcrypto -from pybitmessage.storage import storage - -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 = highlevelcrypto.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 six.assertRaisesRegex( - self, 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 7fbb91c8..00000000 --- a/src/tests/test_logger.py +++ /dev/null @@ -1,58 +0,0 @@ -""" -Testing the logger configuration -""" - -import os -import tempfile - -import six - -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() - six.assertRegex(self, data, self.pattern) - six.assertRegex(self, 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 4b041f1c..00000000 --- a/src/tests/test_multiqueue.py +++ /dev/null @@ -1,65 +0,0 @@ -"""Test cases for multiqueue""" - -import unittest -from pybitmessage.network.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 206117e0..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 = connectionpool.pool - 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 4770072e..00000000 --- a/src/tests/test_openclpow.py +++ /dev/null @@ -1,29 +0,0 @@ -""" -Tests for openclpow module -""" - -import unittest - -from pybitmessage import openclpow, proofofwork - - -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_) - self.assertLess( - nonce - proofofwork.trial_value(nonce, initialHash), 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 69e1e82f..00000000 --- a/src/tests/test_protocol.py +++ /dev/null @@ -1,115 +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.assertFalse( - protocol.checkIPAddress(protocol.encodeHost('127.0.0.1'))) - 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_shared.py b/src/tests/test_shared.py deleted file mode 100644 index 39bedf32..00000000 --- a/src/tests/test_shared.py +++ /dev/null @@ -1,152 +0,0 @@ -"""Test cases for shared.py""" - -import unittest -from pybitmessage.shared import ( - isAddressInMyAddressBook, - isAddressInMySubscriptionsList, - checkSensitiveFilePermissions, - reloadBroadcastSendersForWhichImWatching, - fixSensitiveFilePermissions, - MyECSubscriptionCryptorObjects, - stat, - os, -) - -from .samples import sample_address - -try: - # Python 3 - from unittest.mock import patch, PropertyMock -except ImportError: - # Python 2 - from mock import patch, PropertyMock - -# mock os.stat data for file -PERMISSION_MODE1 = stat.S_IRUSR # allow Read permission for the file owner. -PERMISSION_MODE2 = ( - stat.S_IRWXO -) # allow read, write, serach & execute permission for other users -INODE = 753 -DEV = 1795 -NLINK = 1 -UID = 1000 -GID = 0 -SIZE = 1021 -ATIME = 1711587560 -MTIME = 1709449249 -CTIME = 1709449603 - - -class TestShared(unittest.TestCase): - """Test class for shared.py""" - - @patch("pybitmessage.shared.sqlQuery") - def test_isaddress_in_myaddressbook(self, mock_sql_query): - """Test if address is in MyAddressbook""" - address = sample_address - - # if address is in MyAddressbook - mock_sql_query.return_value = [address] - return_val = isAddressInMyAddressBook(address) - mock_sql_query.assert_called_once() - self.assertTrue(return_val) - - # if address is not in MyAddressbook - mock_sql_query.return_value = [] - return_val = isAddressInMyAddressBook(address) - self.assertFalse(return_val) - self.assertEqual(mock_sql_query.call_count, 2) - - @patch("pybitmessage.shared.sqlQuery") - def test_isaddress_in_mysubscriptionslist(self, mock_sql_query): - """Test if address is in MySubscriptionsList""" - - address = sample_address - - # if address is in MySubscriptionsList - mock_sql_query.return_value = [address] - return_val = isAddressInMySubscriptionsList(address) - self.assertTrue(return_val) - - # if address is not in MySubscriptionsList - mock_sql_query.return_value = [] - return_val = isAddressInMySubscriptionsList(address) - self.assertFalse(return_val) - self.assertEqual(mock_sql_query.call_count, 2) - - @patch("pybitmessage.shared.sqlQuery") - def test_reloadBroadcastSendersForWhichImWatching(self, mock_sql_query): - """Test for reload Broadcast Senders For Which Im Watching""" - mock_sql_query.return_value = [ - (sample_address,), - ] - # before reload - self.assertEqual(len(MyECSubscriptionCryptorObjects), 0) - - # reloading with addressVersionNumber 1 - reloadBroadcastSendersForWhichImWatching() - self.assertGreater(len(MyECSubscriptionCryptorObjects), 0) - - @patch("pybitmessage.shared.os.stat") - @patch( - "pybitmessage.shared.sys", - new_callable=PropertyMock, # pylint: disable=used-before-assignment - ) - def test_check_sensitive_file_permissions(self, mock_sys, mock_os_stat): - """Test to check file permissions""" - fake_filename = "path/to/file" - - # test for windows system - mock_sys.platform = "win32" - result = checkSensitiveFilePermissions(fake_filename) - self.assertTrue(result) - - # test for freebsd system - mock_sys.platform = "freebsd7" - # returning file permission mode stat.S_IRUSR - MOCK_OS_STAT_RETURN = os.stat_result( - sequence=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], - dict={ - "st_mode": PERMISSION_MODE1, - "st_ino": INODE, - "st_dev": DEV, - "st_nlink": NLINK, - "st_uid": UID, - "st_gid": GID, - "st_size": SIZE, - "st_atime": ATIME, - "st_mtime": MTIME, - "st_ctime": CTIME, - }, - ) - mock_os_stat.return_value = MOCK_OS_STAT_RETURN - result = checkSensitiveFilePermissions(fake_filename) - self.assertTrue(result) - - @patch("pybitmessage.shared.os.chmod") - @patch("pybitmessage.shared.os.stat") - def test_fix_sensitive_file_permissions( # pylint: disable=no-self-use - self, mock_os_stat, mock_chmod - ): - """Test to fix file permissions""" - fake_filename = "path/to/file" - - # returning file permission mode stat.S_IRWXO - MOCK_OS_STAT_RETURN = os.stat_result( - sequence=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], - dict={ - "st_mode": PERMISSION_MODE2, - "st_ino": INODE, - "st_dev": DEV, - "st_nlink": NLINK, - "st_uid": UID, - "st_gid": GID, - "st_size": SIZE, - "st_atime": ATIME, - "st_mtime": MTIME, - "st_ctime": CTIME, - }, - ) - mock_os_stat.return_value = MOCK_OS_STAT_RETURN - fixSensitiveFilePermissions(fake_filename, False) - mock_chmod.assert_called_once() 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/tr.py b/src/tr.py index eec82c37..d61d6c34 100644 --- a/src/tr.py +++ b/src/tr.py @@ -1,59 +1,34 @@ -""" -Translating text -""" +import shared import os -try: - import state -except ImportError: - from . import state - - +# 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""" - try: - enableGUI = state.enableGUI - except AttributeError: # inside the plugin - enableGUI = True - if enableGUI: +def translateText(context, text, n = None): + if not shared.safeConfigGetBoolean('bitmessagesettings', '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..df8ab176 100644 --- a/src/translations/bitmessage.pro +++ b/src/translations/bitmessage.pro @@ -1,47 +1,48 @@ 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_msgcoding.py\ - ../helper_search.py\ - ../namecoin.py\ - ../proofofwork.py\ + ../helper_bitcoin.py\ + ../helper_bootstrap.py\ + ../helper_generic.py\ + ../helper_inbox.py\ + ../helper_sent.py\ + ../helper_startup.py\ + ../shared.py\ ../upnp.py\ ../bitmessageqt/__init__.py\ + ../bitmessageqt/about.py\ ../bitmessageqt/account.py\ - ../bitmessageqt/address_dialogs.py\ - ../bitmessageqt/addressvalidator.py\ + ../bitmessageqt/addaddressdialog.py\ ../bitmessageqt/bitmessageui.py\ ../bitmessageqt/blacklist.py\ - ../bitmessageqt/dialogs.py\ + ../bitmessageqt/connect.py\ + ../bitmessageqt/emailgateway.py\ ../bitmessageqt/foldertree.py\ + ../bitmessageqt/help.py\ + ../bitmessageqt/iconglossary.py\ ../bitmessageqt/languagebox.py\ ../bitmessageqt/messagecompose.py\ ../bitmessageqt/messageview.py\ ../bitmessageqt/networkstatus.py\ + ../bitmessageqt/newaddressdialog.py\ ../bitmessageqt/newchandialog.py\ - ../bitmessageqt/settings.py\ - ../bitmessageqt/support.py\ - ../plugins/indicator_libmessaging.py\ - ../plugins/menu_qrcode.py + ../bitmessageqt/newsubscriptiondialog.py\ + ../bitmessageqt/regenerateaddresses.py\ + ../bitmessageqt/safehtmlparser.py\ + ../bitmessageqt/settings.py\ + ../bitmessageqt/specialaddressbehavior.py FORMS = \ - ../bitmessageqt/about.ui\ - ../bitmessageqt/addaddressdialog.ui\ ../bitmessageqt/blacklist.ui\ - ../bitmessageqt/connect.ui\ - ../bitmessageqt/emailgateway.ui\ - ../bitmessageqt/help.ui\ - ../bitmessageqt/iconglossary.ui\ - ../bitmessageqt/networkstatus.ui\ - ../bitmessageqt/newaddressdialog.ui\ - ../bitmessageqt/newchandialog.ui\ - ../bitmessageqt/newsubscriptiondialog.ui\ - ../bitmessageqt/regenerateaddresses.ui\ - ../bitmessageqt/specialaddressbehavior.ui + ../bitmessageqt/networkstatus.ui TRANSLATIONS = \ bitmessage_ar.ts \ @@ -56,12 +57,9 @@ TRANSLATIONS = \ bitmessage_ja.ts \ bitmessage_nl.ts \ bitmessage_no.ts \ - bitmessage_pl.ts \ bitmessage_pt.ts \ bitmessage_sk.ts \ bitmessage_ru.ts \ - bitmessage_uk.ts \ bitmessage_zh_cn.ts CODECFORTR = UTF-8 -CODECFORSRC = UTF-8 diff --git a/src/translations/bitmessage_ar.qm b/src/translations/bitmessage_ar.qm index 892f6160..4d71ded2 100644 Binary files a/src/translations/bitmessage_ar.qm and b/src/translations/bitmessage_ar.qm differ diff --git a/src/translations/bitmessage_ar.ts b/src/translations/bitmessage_ar.ts index 6bf906d7..3742d190 100644 --- a/src/translations/bitmessage_ar.ts +++ b/src/translations/bitmessage_ar.ts @@ -1,5 +1,4 @@ - - + AddAddressDialog @@ -23,61 +22,61 @@ 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. - + Desired email address (including @mailchuck.com): - + EmailGatewayRegistrationDialog - + Registration failed: - + - + The requested email address is not available, please try a new one. Fill out the new desired email address (including @mailchuck.com) below: - + Email 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: - + @@ -124,58 +123,58 @@ Please type the desired email address (including @mailchuck.com) below: # the money directly. To turn it off again, set "feeamount" to 0. Requires # subscription. - + 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 جديد @@ -200,14 +199,14 @@ Please type the desired email address (including @mailchuck.com) below: نسخ العنوان إلى الحافظة - + Special address behavior... سلوك عنوان خاص - + Email gateway - + @@ -215,39 +214,39 @@ Please type the desired email address (including @mailchuck.com) below: حذف - + Send message to this address أرسل رسالة لهذا العنوان - + Subscribe to this address متابعة هذا العنوان - + Add New Address إضافة جهة إتصال - + Copy destination address to clipboard نسخ عنوان المرسل إليه إلى الحافظة - + Force send إرسال قصري - + One of your addresses, %1, is an old version 1 address. Version 1 addresses are no longer supported. May we delete it now? واحد من العناوين، %1، حاصل على رقم إصدار 1، العناوين ذات رقم الإصدار 1 غير مدعومه حالياً، هل باستطاعتنا حذفه الآن؟ Waiting for their encryption key. Will request it again soon. - + @@ -262,7 +261,7 @@ Please type the desired email address (including @mailchuck.com) below: Message sent. Waiting for acknowledgement. Sent at %1 - + @@ -332,7 +331,7 @@ Please type the desired email address (including @mailchuck.com) below: Channel - + @@ -395,17 +394,17 @@ It is important that you back up this file. Would you like to open the file now? Bad address version number - + Your address version number must be a number: either 3 or 4. - + Your address version number must be either 3 or 4. - + @@ -493,22 +492,22 @@ It is important that you back up this file. Would you like to open the file now? 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. - + 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. - + 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. - + @@ -543,7 +542,7 @@ It is important that you back up this file. Would you like to open the file now? Error: Some data encoded in the address %1 is malformed. There might be something wrong with the software of your acquaintance. - + @@ -583,7 +582,7 @@ It is important that you back up this file. Would you like to open the file now? Message queued. - + @@ -596,24 +595,24 @@ It is important that you back up this file. Would you like to open the file now? أنقر يميناً على واحد أو أكثر من جهات الاتصال في دفتر العناوين و اختر "إرسال رسالة لهذا العنوان". - + Fetched address from namecoin identity. تم تحصيل العنوان من هوية namecoin. - + New Message رسالة جديدة - + From من - + Sending email gateway registration request - + @@ -626,241 +625,241 @@ It is important that you back up this file. Would you like to open the file now? العنوان الذي أدخلته غير صالح، سيتم تجاهله. - + Error: You cannot add the same address to your address book twice. Try renaming the existing one if you want. خطأ: لا يمكنك إضافة نفس العنوان إلى دفتر العناوين مرتين، يمكنك إعادة تسمية العنوان. - + Error: You cannot add the same address to your subscriptions twice. Perhaps rename the existing one if you want. - + - + Restart إعادة تشغيل - + You must restart Bitmessage for the port number change to take effect. لتفعيل تغيير رقم نقطة العبور (port) يجب عليك إعادة تشغيل برنامج Bitmessage. - + Bitmessage will use your proxy from now on but you may want to manually restart Bitmessage now to close existing connections (if any). سيقوم Bitmessage باستخدام البروكسي الخاص بك من الآن فصاعداً و لكن يمكنك إعادة تشغيل Bitmessage يدوياً لإغلاق الروابط الحالية -إن وجدت. - + Number needed - + - + Your maximum download and upload rate must be numbers. Ignoring what you typed. - + - + Will not resend ever - + - + Note that the time limit you entered is less than the amount of time Bitmessage waits for the first resend attempt therefore your messages will never be resent. - + - + 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? لم يستطع Bitmessage العثور على عنوانك %1, ربما قمت بحذف العنوان؟ - + Address disabled تم تعطيل العنوان - + Error: The address from which you are trying to send is disabled. You'll have to enable it on the 'Your Identities' tab before using it. خطأ: العنوان المستخدم للإرسال منه معطل، يجب عليك تفعيله في تبويب "هوياتك" قبل استخدامه. - + Entry added to the Address Book. Edit the label to your liking. تم إضافة جهة الاتصال لدفتر العناوين، يمكنك تعديل الإسم المستعار إذا أحببت. - + Entry added to the blacklist. Edit the label to your liking. - + - + Error: You cannot add the same address to your blacklist twice. Try renaming the existing one if you want. - + - + Moved items to trash. تم نقل المادة لسلة المهملات. - + Undeleted item. - + - + Save As... حفظ بإسم - + Write error. خطأ كتابة. - + No addresses selected. لم يتم اختيار عناوين - + If you delete the subscription, messages that you already received will become inaccessible. Maybe you can consider disabling the subscription instead. Disabled subscriptions will not receive new messages, but you can still view messages you already received. Are you sure you want to delete the subscription? - + - + 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). لم يتم إدخال أو نسخ العنوان بالطريقة الصحيحة - اختبار checksum فشل. - + 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 - يمكنك تغييره في قائمة الضبط. @@ -872,12 +871,12 @@ Are you sure you want to delete the channel? Identities - + New Identity - + @@ -917,12 +916,12 @@ Are you sure you want to delete the channel? Messages - + Address book - + @@ -932,7 +931,7 @@ Are you sure you want to delete the channel? Add Contact - + @@ -957,17 +956,17 @@ Are you sure you want to delete the channel? Send ordinary Message - + Send Message to your Subscribers - + TTL: - + @@ -982,12 +981,12 @@ Are you sure you want to delete the channel? Chans - + Add Chan - + @@ -1027,7 +1026,7 @@ Are you sure you want to delete the channel? Contact support - + @@ -1052,243 +1051,219 @@ Are you sure you want to delete the channel? All accounts - + Zoom level %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. - + New version of PyBitmessage is available: %1. Download it from https://github.com/Bitmessage/PyBitmessage/releases/latest - + - + Waiting for PoW to finish... %1% - + - + Shutting down Pybitmessage... %1% - + - + Waiting for objects to be sent... %1% - + - + Saving settings... %1% - + - + Shutting down core... %1% - + - + Stopping notifications... %1% - + - + Shutdown imminent... %1% - + %n hour(s) - - - - - - - - + - + %n day(s) - - - - - - - - + - + Shutting down PyBitmessage... %1% - + Sent - + Generating one new address - + Done generating address. Doing work necessary to broadcast it... - + Generating %1 new addresses. - + %1 is already in 'Your Identities'. Not adding it again. - + Done generating address - + - + SOCKS5 Authentication problem: %1 - + - + 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. - + Doing work necessary to send broadcast... - + Broadcast sent on %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 - + Doing work necessary to send message. There is no required difficulty for version 2 addresses like this. - + Doing work necessary to send message. Receiver's required difficulty: %1 and %2 - + Problem: The work demanded by the recipient (%1 and %2) is more difficult than you are willing to do. %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 - + Doing work necessary to send message. - + Message sent. Waiting for acknowledgement. Sent on %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 - + - + UPnP port mapping established on port %1 - + - + UPnP port mapping removed - - - - - Mark all messages as read - - - - - Are you sure you would like to mark all messages read? - + @@ -1308,7 +1283,7 @@ The 'Random Number' option is selected by default but deterministic ad <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> - + @@ -1333,7 +1308,7 @@ The 'Random Number' option is selected by default but deterministic ad Address version number: 4 - + @@ -1411,7 +1386,7 @@ The 'Random Number' option is selected by default but deterministic ad Enter an address above. - + @@ -1442,79 +1417,75 @@ The 'Random Number' option is selected by default but deterministic ad إسم القائمة البريدية المستعار: - - Ui_aboutDialog - - - aboutDialog - <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 The Bitmessage Developers</p></body></html> - - - aboutDialog - + About عن البرنامج - + PyBitmessage PyBitmessage - + version ? الإصدار ؟ - + <html><head/><body><p>Distributed under the MIT/X11 software license; see <a href="http://www.opensource.org/licenses/mit-license.php"><span style=" text-decoration: underline; color:#0000ff;">http://www.opensource.org/licenses/mit-license.php</span></a></p></body></html> - + - + This is Beta software. هذه نسخة تجريبة للبرنامج + + + <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 The Bitmessage Developers</p></body></html> + + blacklist Use a Blacklist (Allow all incoming messages except those on the Blacklist) - + Use a Whitelist (Block all incoming messages except those on the Whitelist) - + Add new entry - إضافة مدخل جديد + Name or Label - + Address - + Blacklist - + Whitelist - + @@ -1550,7 +1521,7 @@ The 'Random Number' option is selected by default but deterministic ad <a href="https://bitmessage.org/wiki/PyBitmessage_Help">https://bitmessage.org/wiki/PyBitmessage_Help</a> - + @@ -1591,147 +1562,112 @@ The 'Random Number' option is selected by default but deterministic ad Total connections: - + Since startup: - + Processed 0 person-to-person messages. - + Processed 0 public keys. - + Processed 0 broadcasts. - + Inventory lookups per second: 0 - + Objects to be synced: - + Stream # - + Connections - + Since startup on %1 - + Down: %1/s Total: %2 - + Up: %1/s Total: %2 - + Total Connections: %1 - + Inventory lookups per second: %1 - + Up: 0 kB/s - + Down: 0 kB/s - + Network Status - + byte(s) - - - - - - - - + Object(s) to be synced: %n - - - - - - - - + Processed %n person-to-person message(s). - - - - - - - - + Processed %n broadcast message(s). - - - - - - - - + Processed %n public key(s). - - - - - - - - + @@ -1759,7 +1695,7 @@ The 'Random Number' option is selected by default but deterministic ad <html><head/><body><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. If you and someone else both create a chan with the same chan name then it is currently very likely that they will be the same chan.</p></body></html> - + @@ -1845,7 +1781,7 @@ The 'Random Number' option is selected by default but deterministic ad Tray - + @@ -1860,7 +1796,7 @@ The 'Random Number' option is selected by default but deterministic ad Close to tray - + @@ -1890,7 +1826,7 @@ The 'Random Number' option is selected by default but deterministic ad Reply below Quote - + @@ -1901,7 +1837,7 @@ The 'Random Number' option is selected by default but deterministic ad System Settings system - + @@ -1921,22 +1857,22 @@ The 'Random Number' option is selected by default but deterministic ad UPnP: - + Bandwidth limit - + Maximum download rate (kB/s): [0: unlimited] - + Maximum upload rate (kB/s): [0: unlimited] - + @@ -2051,12 +1987,12 @@ The 'Random Number' option is selected by default but deterministic ad Hardware GPU acceleration (OpenCL) - + <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> - + @@ -2096,7 +2032,7 @@ The 'Random Number' option is selected by default but deterministic ad <html><head/><body><p>By default, if you send a message to someone and he is offline for more than two days, Bitmessage will send the message again after an additional two days. This will be continued with exponential backoff forever; messages will be resent after 5, 10, 20 days ect. until the receiver acknowledges them. Here you may change that behavior by having Bitmessage give up after a certain number of days or months.</p><p>Leave these input fields blank for the default behavior. </p></body></html> - + @@ -2124,4 +2060,4 @@ The 'Random Number' option is selected by default but deterministic ad إنتهاء صلاحية إعادة الإرسال - + \ No newline at end of file diff --git a/src/translations/bitmessage_cs.qm b/src/translations/bitmessage_cs.qm index c25ccafa..df4019e4 100644 Binary files a/src/translations/bitmessage_cs.qm and b/src/translations/bitmessage_cs.qm differ diff --git a/src/translations/bitmessage_cs.ts b/src/translations/bitmessage_cs.ts index 11ab163a..ac959a64 100644 --- a/src/translations/bitmessage_cs.ts +++ b/src/translations/bitmessage_cs.ts @@ -1,5 +1,4 @@ - - + AddAddressDialog @@ -28,56 +27,56 @@ 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. - + Desired email address (including @mailchuck.com): - + EmailGatewayRegistrationDialog - + Registration failed: - + - + The requested email address is not available, please try a new one. Fill out the new desired email address (including @mailchuck.com) below: - + Email 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: - + @@ -124,58 +123,58 @@ Please type the desired email address (including @mailchuck.com) below: # the money directly. To turn it off again, set "feeamount" to 0. Requires # subscription. - + MainWindow - + Reply to sender - + - + Reply to channel - + - + Add sender to your Address Book Přidat odesilatele do Vašeho adresáře - + Add sender to your Blacklist - + - + Move to Trash Přesunout do koše - + Undelete - + - + View HTML code as formatted text Zobrazit HTML kód jako formátovaný text - + Save message as... Uložit zprávu jako... - + Mark Unread Označit jako nepřečtené - + New Nové @@ -200,12 +199,12 @@ Please type the desired email address (including @mailchuck.com) below: Zkopírovat adresu do clipboardu - + Special address behavior... Speciální chování adresy... - + Email gateway Email brána @@ -215,32 +214,32 @@ Please type the desired email address (including @mailchuck.com) below: Odstranit - + Send message to this address Poslat zprávu na tuto adresu - + Subscribe to this address Přihlásit se k odběru této adresy - + Add New Address Přidat novou adresu - + Copy destination address to clipboard Zkopírovat cílovou adresu do clipboardu - + Force send Přesto odeslat - + One of your addresses, %1, is an old version 1 address. Version 1 addresses are no longer supported. May we delete it now? Jedna z Vašich adres, %1, je stará adresa verze 1. Adresy verze 1 již nejsou podporovány. Můžeme ji nyní smazat? @@ -332,7 +331,7 @@ Please type the desired email address (including @mailchuck.com) below: Channel - + @@ -493,7 +492,7 @@ Je důležité si tento soubor zazálohovat. Přejete si tento soubor nyní otev 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. - + @@ -508,7 +507,7 @@ Je důležité si tento soubor zazálohovat. Přejete si tento soubor nyní otev 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. - + @@ -583,7 +582,7 @@ Je důležité si tento soubor zazálohovat. Přejete si tento soubor nyní otev Message queued. - + @@ -596,24 +595,24 @@ Je důležité si tento soubor zazálohovat. Přejete si tento soubor nyní otev Klikněte pravým tlačítkem na jeden nebo více záznamů v adresáři, a vyberte "Poslat zprávu na tuto adresu". - + Fetched address from namecoin identity. Adresa načtena z namecoinové identity. - + New Message Nová zpráva - + From Od - + Sending email gateway registration request - + @@ -626,241 +625,241 @@ Je důležité si tento soubor zazálohovat. Přejete si tento soubor nyní otev Zadaná adresa je neplatná, ignoruji jí. - + Error: You cannot add the same address to your address book twice. Try renaming the existing one if you want. Chyba: Nemůžete do adresáře přidat adresu, která tam již je. Můžete ale tu existující přejmenovat. - + Error: You cannot add the same address to your subscriptions twice. Perhaps rename the existing one if you want. - + - + Restart Restart - + You must restart Bitmessage for the port number change to take effect. Je třeba restartovat Bitmessage, aby se změna portu projevila. - + Bitmessage will use your proxy from now on but you may want to manually restart Bitmessage now to close existing connections (if any). Bitmessage bude od teď používat Váš proxy server. Bude ale jistější, když nyní Bitmessage restartujete, abyste zavřel(a) všechna aktivní připojení (pokud nějaká jsou). - + Number needed Je třeba zadat číslo - + Your maximum download and upload rate must be numbers. Ignoring what you typed. Limity pro rychlost stahování a odesílání musejí být čísla. Vámi zadané hodnoty nelze použít. - + Will not resend ever Nikdy nebude nic posláno znovu - + 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. Všimněte si, že časový limit, který jste zadal(a), je menší, než čas po kterém Bitmessage poprvé zkusí opětovné odeslání, proto Vaše zprávy nebudou nikdy poslány znovu. - + Sending email gateway unregistration request - + - + Sending email gateway status request - + - + Passphrase mismatch Hesla nejsou stejná - + The passphrase you entered twice doesn't match. Try again. Zadaná hesla nejsou stejná. Zkuste to znovu. - + Choose a passphrase Zvolte heslo - + You really do need a passphrase. Opravdu je nutné zvolit heslo. - + Address is gone Adresa je pryč - + Bitmessage cannot find your address %1. Perhaps you removed it? Bitmessage nemůže najít Vaši adresu %1. Možná jste ji odstranil(a)? - + Address disabled Adresa je vypnutá - + Error: The address from which you are trying to send is disabled. You'll have to enable it on the 'Your Identities' tab before using it. Chyba: Adresa, ze které se snažíte poslat zprávu, je vypnutá. Před použitím ji musíte zapnout na kartě "Vaše identity". - + Entry added to the Address Book. Edit the label to your liking. Položka byla přidána do adresáře. Popisku můžete upravit dle svého přání. - + 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. Položky byly přesunuty do koše. - + Undeleted item. - + - + Save As... Uložit jako... - + Write error. Chyba zápisu. - + No addresses selected. Není vybrána žádná adresa. - + If you delete the subscription, messages that you already received will become inaccessible. Maybe you can consider disabling the subscription instead. Disabled subscriptions will not receive new messages, but you can still view messages you already received. Are you sure you want to delete the subscription? - + - + 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? Opravdu chcete odstranit tento avatar? - + You have already set an avatar for this address. Do you really want to overwrite it? Pro tuto adresu již avatar máte. Opravdu ho chcete přepsat? - + Start-on-login not yet supported on your OS. Spuštění po přihlášení není zatím na Vašem operačním systému podporováno. - + Minimize-to-tray not yet supported on your OS. Minimalizace na lištu není zatím na Vašem operačním systému podporována. - + Tray notifications not yet supported on your OS. Upozornění v liště nejsou zatím na Vašem operačním systému podporována. - + Testing... Zkouším... - + This is a chan address. You cannot use it as a pseudo-mailing list. Toto je adresa kanálu. Není možné ji použít jako pseudo-mailing list. - + The address should start with ''BM-'' Adresa by měla začínat "BM-" - + The address is not typed or copied correctly (the checksum failed). Adresa nebyla správně opsána nebo zkopírována (kontrolní součet nesouhlasí). - + The version number of this address is higher than this software can support. Please upgrade Bitmessage. Verze této adresy je vyšší než s jakou tento software umí pracovat. Prosím aktualizujte Bitmessage. - + The address contains invalid characters. Adresa obsahuje neplatné znaky. - + Some data encoded in the address is too short. Některá data zakódovaná v této adrese jsou příliš krátká. - + Some data encoded in the address is too long. Některá data zakódovaná v této adrese jsou příliš dlouhá. - + Some data encoded in the address is malformed. Některá data zakódovaná v této adrese mají neplatný formát. - + Enter an address above. Zadejte adresu výše. - + Address is an old type. We cannot display its past broadcasts. Toto je starý typ adresy. Neumíme zobrazit její rozesílané zprávy. - + There are no recent broadcasts from this address to display. Z této adresy nebyly v poslední době rozesílány žádné zprávy. - + You are using TCP port %1. (This can be changed in the settings). Používáte TCP port %1. (To lze změnit v nastavení). @@ -872,12 +871,12 @@ Are you sure you want to delete the channel? Identities - + New Identity - + @@ -917,12 +916,12 @@ Are you sure you want to delete the channel? Messages - + Address book - + @@ -932,7 +931,7 @@ Are you sure you want to delete the channel? Add Contact - + @@ -957,17 +956,17 @@ Are you sure you want to delete the channel? Send ordinary Message - + Send Message to your Subscribers - + TTL: - + @@ -982,12 +981,12 @@ Are you sure you want to delete the channel? Chans - + Add Chan - + @@ -1027,7 +1026,7 @@ Are you sure you want to delete the channel? Contact support - + @@ -1052,237 +1051,219 @@ Are you sure you want to delete the channel? All accounts - + Zoom level %1% - + Error: You cannot add the same address to your list twice. Perhaps rename the existing one if you want. - + Add new entry - Přidat novou položku + - + Display the %1 recent broadcast(s) from this address. - + New version of PyBitmessage is available: %1. Download it from https://github.com/Bitmessage/PyBitmessage/releases/latest - + - + Waiting for PoW to finish... %1% - + - + Shutting down Pybitmessage... %1% - + - + Waiting for objects to be sent... %1% - + - + Saving settings... %1% - + - + Shutting down core... %1% - + - + Stopping notifications... %1% - + - + Shutdown imminent... %1% - + %n hour(s) - - - - - + - + %n day(s) - - - - - + - + Shutting down PyBitmessage... %1% - + Sent - + Generating one new address - + Done generating address. Doing work necessary to broadcast it... - + Generating %1 new addresses. - + %1 is already in 'Your Identities'. Not adding it again. - + Done generating address - + - + SOCKS5 Authentication problem: %1 - + - + 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. - + Doing work necessary to send broadcast... - + Broadcast sent on %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 - + Doing work necessary to send message. There is no required difficulty for version 2 addresses like this. - + Doing work necessary to send message. Receiver's required difficulty: %1 and %2 - + Problem: The work demanded by the recipient (%1 and %2) is more difficult than you are willing to do. %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 - + Doing work necessary to send message. - + Message sent. Waiting for acknowledgement. Sent on %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 - + - + UPnP port mapping established on port %1 - + - + UPnP port mapping removed - - - - - Mark all messages as read - - - - - Are you sure you would like to mark all messages read? - + @@ -1405,7 +1386,7 @@ Možnost "Náhodné číslo" je nastavena jako výchozí, deterministi Enter an address above. - Zadejte adresu výše. + @@ -1436,79 +1417,75 @@ Možnost "Náhodné číslo" je nastavena jako výchozí, deterministi Jméno pseudo-mailing-listu: - - Ui_aboutDialog - - - aboutDialog - <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 The Bitmessage Developers</p></body></html> - - - aboutDialog - + About O aplikaci - + PyBitmessage PyBitmessage - + version ? verze ? - + <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>Distribuováno pod softwarovou licencí MIT/X11; viz <a href="http://www.opensource.org/licenses/mit-license.php"><span style=" text-decoration: underline; color:#0000ff;">http://www.opensource.org/licenses/mit-license.php</span></a></p></body></html> - + This is Beta software. Toto je Beta software. + + + <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 The Bitmessage Developers</p></body></html> + + blacklist Use a Blacklist (Allow all incoming messages except those on the Blacklist) - + Use a Whitelist (Block all incoming messages except those on the Whitelist) - + Add new entry - Přidat novou položku + Name or Label - + Address - Adresa + Blacklist - + Whitelist - + @@ -1544,7 +1521,7 @@ Možnost "Náhodné číslo" je nastavena jako výchozí, deterministi <a href="https://bitmessage.org/wiki/PyBitmessage_Help">https://bitmessage.org/wiki/PyBitmessage_Help</a> - + @@ -1585,132 +1562,112 @@ Možnost "Náhodné číslo" je nastavena jako výchozí, deterministi Total connections: - + Since startup: - + Processed 0 person-to-person messages. - + Processed 0 public keys. - + Processed 0 broadcasts. - + Inventory lookups per second: 0 - + Objects to be synced: - + Stream # - + Connections - + Since startup on %1 - + Down: %1/s Total: %2 - + Up: %1/s Total: %2 - + Total Connections: %1 - + Inventory lookups per second: %1 - + Up: 0 kB/s - + Down: 0 kB/s - + Network Status - + byte(s) - - - - - + Object(s) to be synced: %n - - - - - + Processed %n person-to-person message(s). - - - - - + Processed %n broadcast message(s). - - - - - + Processed %n public key(s). - - - - - + @@ -1824,7 +1781,7 @@ Možnost "Náhodné číslo" je nastavena jako výchozí, deterministi Tray - + @@ -1839,7 +1796,7 @@ Možnost "Náhodné číslo" je nastavena jako výchozí, deterministi Close to tray - + @@ -1900,7 +1857,7 @@ Možnost "Náhodné číslo" je nastavena jako výchozí, deterministi UPnP: - + @@ -2030,7 +1987,7 @@ Možnost "Náhodné číslo" je nastavena jako výchozí, deterministi Hardware GPU acceleration (OpenCL) - + @@ -2103,4 +2060,4 @@ Možnost "Náhodné číslo" je nastavena jako výchozí, deterministi Lhůta pro opětovné poslání - + \ No newline at end of file diff --git a/src/translations/bitmessage_da.qm b/src/translations/bitmessage_da.qm index e5588987..6aaaaf25 100644 Binary files a/src/translations/bitmessage_da.qm and b/src/translations/bitmessage_da.qm differ diff --git a/src/translations/bitmessage_da.ts b/src/translations/bitmessage_da.ts index fcf80470..9bfd1787 100644 --- a/src/translations/bitmessage_da.ts +++ b/src/translations/bitmessage_da.ts @@ -1,5 +1,4 @@ - - + AddAddressDialog @@ -48,7 +47,7 @@ Email gateway allows you to communicate with email users. Currently, only the Mailchuck email gateway (@mailchuck.com) is available. - + @@ -59,14 +58,14 @@ EmailGatewayRegistrationDialog - + Registration failed: Registrering mislykkedes: - + The requested email address is not available, please try a new one. Fill out the new desired email address (including @mailchuck.com) below: - + @@ -77,7 +76,7 @@ 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: - + @@ -124,58 +123,58 @@ Please type the desired email address (including @mailchuck.com) below: # the money directly. To turn it off again, set "feeamount" to 0. Requires # subscription. - + MainWindow - + Reply to sender Svar til afsender - + Reply to channel Svar til kanal - + Add sender to your Address Book Tilføj afsender til dn adressebog - + Add sender to your Blacklist Tilføj afsender til din blacklist - + Move to Trash Flyt til papirkurv - + Undelete Gendan - + View HTML code as formatted text Vis HTML-kode som formatteret tekst - + Save message as... Gem besked som... - + Mark Unread Marker som ulæst - + New Ny @@ -200,12 +199,12 @@ Please type the desired email address (including @mailchuck.com) below: Kopiér adresse til udklipsholder - + Special address behavior... Speciel addressefunktion... - + Email gateway Email gateway @@ -215,32 +214,32 @@ Please type the desired email address (including @mailchuck.com) below: Slet - + Send message to this address Send besked til denne adresse - + Subscribe to this address Abonner på denne adresse - + Add New Address Tilføj ny adresse - + Copy destination address to clipboard Kopier modtageraddresse til udklipsholder - + Force send Gennemtving afsendelse - + One of your addresses, %1, is an old version 1 address. Version 1 addresses are no longer supported. May we delete it now? En af dine adresser, %1 er en gammel version 1-addresse. Version 1-addresser understøttes ikke længere. Må vi slette den? @@ -493,7 +492,7 @@ Det er vigtigt at tage backup af denne fil. (Sørg for at lukke Bitmessage før 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. - + @@ -508,7 +507,7 @@ Det er vigtigt at tage backup af denne fil. (Sørg for at lukke Bitmessage før 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. - + @@ -528,22 +527,22 @@ Det er vigtigt at tage backup af denne fil. (Sørg for at lukke Bitmessage før Error: The address version in %1 is too high. Either you need to upgrade your Bitmessage software or your acquaintance is being clever. - + Error: Some data encoded in the address %1 is too short. There might be something wrong with the software of your acquaintance. - + Error: Some data encoded in the address %1 is too long. There might be something wrong with the software of your acquaintance. - + Error: Some data encoded in the address %1 is malformed. There might be something wrong with the software of your acquaintance. - + @@ -596,22 +595,22 @@ Det er vigtigt at tage backup af denne fil. (Sørg for at lukke Bitmessage før Højreklik på en eller flere adresser i din adressebog og vælg 'Send besked til denne adresse'. - + Fetched address from namecoin identity. Adresse blev hentet fra namecoin-identitet. - + New Message Ny Besked - + From Fra - + Sending email gateway registration request Sender tilmeldelses-forespørgsel til email gateway @@ -626,142 +625,142 @@ Det er vigtigt at tage backup af denne fil. (Sørg for at lukke Bitmessage før Adressen som du har indtastet er ugyldig og vil derfor blive ignoreret. - + Error: You cannot add the same address to your address book twice. Try renaming the existing one if you want. Fejl: Du kan ikke tilføje den samme adresse til din adressebog flere gange. - + Error: You cannot add the same address to your subscriptions twice. Perhaps rename the existing one if you want. - + - + Restart Genstart - + You must restart Bitmessage for the port number change to take effect. Bitmessage skal genstartes før ændringen af portnummeret træder i kraft. - + Bitmessage will use your proxy from now on but you may want to manually restart Bitmessage now to close existing connections (if any). Bitmessage vil benytte en proxy fra nu af, men hvis du ønsker at afbryde eventuelle eksisterende forbindelser skal du genstarte Bitmessage manuelt. - + Number needed Et tal er nødvendigt - + Your maximum download and upload rate must be numbers. Ignoring what you typed. De maksimale download- og upload-hastigheder skal være tal. Det du har indtastet vil blive ignoreret. - + Will not resend ever Vil aldrig gensende - + 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. Bemærk at den tidsgrænse du har indtastet er kortere end den tid som Bitmessage venter inden første genafsendelsesforsøg, og dine beskeder vil derfor aldrig blive genafsendt. - + Sending email gateway unregistration request Sender afmeldelses-forespørgsel til email gateway - + Sending email gateway status request Sender status-forespørgsel til email gateway - + Passphrase mismatch Kodeordene stemmer ikke overens - + The passphrase you entered twice doesn't match. Try again. De to kodeord er ikke ens. Prøv igen. - + Choose a passphrase Vælg et kodeord - + You really do need a passphrase. Du kan ikke undlade at indtaste et kodeord. - + Address is gone Adressen er forsvundet - + Bitmessage cannot find your address %1. Perhaps you removed it? Bitmessage kan ikke finde din adresse %1. Måske har du fjernet den? - + Address disabled Addresse slået fra - + 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. Fejl: Adressen som du prøver at sende fra er deaktiveret. Du skal aktivere den fra fanen 'Dine Identiteter'. - + Entry added to the Address Book. Edit the label to your liking. Adresse tilføjet til adressebogen. Rediger navnet som du ønsker. - + Entry added to the blacklist. Edit the label to your liking. Adresse tilføjet til din blacklist. Rediger navnet som du ønsker. - + Error: You cannot add the same address to your blacklist twice. Try renaming the existing one if you want. Fejl: Den samme adresse kan ikke tilføjes til din sortliste flere gange. Prøv eventuelt at omdøbe den eksisterende. - + Moved items to trash. Beskeder blev flyttet til papirkurven. - + Undeleted item. Besked gendannet. - + Save As... Gem Som... - + Write error. Skrivefejl. - + No addresses selected. Ingen adresser valgt. - + 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? @@ -770,7 +769,7 @@ Are you sure you want to delete the subscription? Er du sikker på at du vil slette dette 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? @@ -779,92 +778,92 @@ Are you sure you want to delete the channel? Er du sikker på at du vil slette denne kanal? - + Do you really want to remove this avatar? Vil du virkelig fjerne dette ikon? - + You have already set an avatar for this address. Do you really want to overwrite it? Du har allerede valgt et ikon for denne adresse. Er du sikker på at du vil erstatte det? - + Start-on-login not yet supported on your OS. Automatisk start er endnu ikke understøttet på din platform. - + Minimize-to-tray not yet supported on your OS. Minimering til systembakken er endnu ikke understøttet på din platform. - + Tray notifications not yet supported on your OS. Systembakkenotifikationer er endnu ikke understøttet på din platform. - + Testing... Tester... - + This is a chan address. You cannot use it as a pseudo-mailing list. Dette er en kanaladresse. Den kan ikke benyttes som en pseudo-mailing-liste. - + The address should start with ''BM-'' Adressen bør starte med "BM-" - + The address is not typed or copied correctly (the checksum failed). DU har indtastet eller kopieret adressen forkert (checksummen passer ikke) - + The version number of this address is higher than this software can support. Please upgrade Bitmessage. Versionsnummeret for denne adresse er højere end hvad der understøttes af denne softwareversion. Opgrader venligst Bitmessage. - + The address contains invalid characters. Adressen indeholder ugyldige tegn. - + Some data encoded in the address is too short. Nogle af dataene som er indkodet i adressen, er for korte. - + Some data encoded in the address is too long. Nogle af dataene som er indkodet i adressen, er for lange. - + Some data encoded in the address is malformed. Nogle af dataene som er indkodet i adressen er ugyldige. - + Enter an address above. Vælg en adresse ovenfor. - + Address is an old type. We cannot display its past broadcasts. Adressen er af en gammel type. Dens broadcast-beskeder kan ikke vises. - + There are no recent broadcasts from this address to display. Der blev ikke fundet nogen broadcast-beskeder fra denne adresse - + You are using TCP port %1. (This can be changed in the settings). Du bruger TCP-port %1. (Dette kan ændres i indstillingerne). @@ -1066,225 +1065,209 @@ Er du sikker på at du vil slette denne kanal? Error: You cannot add the same address to your list twice. Perhaps rename the existing one if you want. - + Add new entry - Tilføj ny addresse + - + Display the %1 recent broadcast(s) from this address. - + New version of PyBitmessage is available: %1. Download it from https://github.com/Bitmessage/PyBitmessage/releases/latest - + - + Waiting for PoW to finish... %1% - + - + Shutting down Pybitmessage... %1% - + - + Waiting for objects to be sent... %1% - + - + Saving settings... %1% - + - + Shutting down core... %1% - + - + Stopping notifications... %1% - + - + Shutdown imminent... %1% - + %n hour(s) - - - - + - + %n day(s) - - - - + - + Shutting down PyBitmessage... %1% - + Sent - + Generating one new address - + Done generating address. Doing work necessary to broadcast it... - + Generating %1 new addresses. - + %1 is already in 'Your Identities'. Not adding it again. - + Done generating address - + - + SOCKS5 Authentication problem: %1 - + - + 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. - + Doing work necessary to send broadcast... - + Broadcast sent on %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 - + Doing work necessary to send message. There is no required difficulty for version 2 addresses like this. - + Doing work necessary to send message. Receiver's required difficulty: %1 and %2 - + Problem: The work demanded by the recipient (%1 and %2) is more difficult than you are willing to do. %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 - + Doing work necessary to send message. - + Message sent. Waiting for acknowledgement. Sent on %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 - + - + UPnP port mapping established on port %1 - + - + UPnP port mapping removed - - - - - Mark all messages as read - - - - - Are you sure you would like to mark all messages read? - + @@ -1304,7 +1287,7 @@ Som standard er tilfældige tal valgt, men der er både fordele og ulemper ved a <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> - + @@ -1407,7 +1390,7 @@ Som standard er tilfældige tal valgt, men der er både fordele og ulemper ved a Enter an address above. - Vælg en adresse ovenfor. + @@ -1438,79 +1421,75 @@ Som standard er tilfældige tal valgt, men der er både fordele og ulemper ved a Navn på pseudo-mailing-listen: - - Ui_aboutDialog - - - aboutDialog - <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 The Bitmessage Developers</p></body></html> - - - aboutDialog - + About Om - + 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>Distribueret under MIT/X11-licensen; se <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. Dette er en betaversion. + + + <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 The Bitmessage Developers</p></body></html> + + blacklist Use a Blacklist (Allow all incoming messages except those on the Blacklist) - + Use a Whitelist (Block all incoming messages except those on the Whitelist) - + Add new entry - Tilføj ny addresse + Name or Label - + Address - Adresse + Blacklist - + Whitelist - + @@ -1559,7 +1538,7 @@ Som standard er tilfældige tal valgt, men der er både fordele og ulemper ved a Icon Glossary - + @@ -1569,7 +1548,7 @@ Som standard er tilfældige tal valgt, men der er både fordele og ulemper ved a 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. - + @@ -1587,127 +1566,112 @@ Som standard er tilfældige tal valgt, men der er både fordele og ulemper ved a Total connections: - + Since startup: - + Processed 0 person-to-person messages. - + Processed 0 public keys. - + Processed 0 broadcasts. - + Inventory lookups per second: 0 - + Objects to be synced: - + Stream # - + Connections - + Since startup on %1 - + Down: %1/s Total: %2 - + Up: %1/s Total: %2 - + Total Connections: %1 - + Inventory lookups per second: %1 - + Up: 0 kB/s - + Down: 0 kB/s - + Network Status - + byte(s) - - - - + Object(s) to be synced: %n - - - - + Processed %n person-to-person message(s). - - - - + Processed %n broadcast message(s). - - - - + Processed %n public key(s). - - - - + @@ -1735,7 +1699,7 @@ Som standard er tilfældige tal valgt, men der er både fordele og ulemper ved a <html><head/><body><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. If you and someone else both create a chan with the same chan name then it is currently very likely that they will be the same chan.</p></body></html> - + @@ -1745,7 +1709,7 @@ Som standard er tilfældige tal valgt, men der er både fordele og ulemper ved a <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 normal person-to-person message to the chan address.</p><p>Chans are experimental and completely unmoderatable.</p></body></html> - + @@ -1826,7 +1790,7 @@ Som standard er tilfældige tal valgt, men der er både fordele og ulemper ved a Start Bitmessage in the tray (don't show main window) - + @@ -1982,7 +1946,7 @@ Som standard er tilfældige tal valgt, men der er både fordele og ulemper ved a The 'Total difficulty' affects the absolute amount of work the sender must complete. Doubling this value doubles the amount of work. - + @@ -1997,7 +1961,7 @@ Som standard er tilfældige tal valgt, men der er både fordele og ulemper ved a 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. - + @@ -2007,7 +1971,7 @@ Som standard er tilfældige tal valgt, men der er både fordele og ulemper ved a 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. - + @@ -2072,7 +2036,7 @@ Som standard er tilfældige tal valgt, men der er både fordele og ulemper ved a <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> - + @@ -2100,4 +2064,4 @@ Som standard er tilfældige tal valgt, men der er både fordele og ulemper ved a Forsøg på genafsendelse stopper efter - + \ No newline at end of file diff --git a/src/translations/bitmessage_de.qm b/src/translations/bitmessage_de.qm index ef443a61..1562fd38 100644 Binary files a/src/translations/bitmessage_de.qm and b/src/translations/bitmessage_de.qm differ diff --git a/src/translations/bitmessage_de.ts b/src/translations/bitmessage_de.ts index 69cdd2a8..e7c8fdfe 100644 --- a/src/translations/bitmessage_de.ts +++ b/src/translations/bitmessage_de.ts @@ -2,17 +2,17 @@ AddAddressDialog - + Add new entry Neuen Eintrag erstellen - + Label Name oder Bezeichnung - + Address Adresse @@ -20,99 +20,70 @@ EmailGatewayDialog - + Email gateway E-Mail Schnittstelle - + Register on email gateway - An E-Mail Schnittstelle registrieren + An E-Mailschnittstelle registrieren - + Account status at email gateway Statusanfrage der E-Mail Schnittstelle - + Change account settings at email gateway Einstellungen der E-Mail Schnittstelle ändern - + Unregister from email gateway Von der E-Mail Schnittstelle abmelden - + Email gateway allows you to communicate with email users. Currently, only the Mailchuck email gateway (@mailchuck.com) is available. Die E-Mail Schnittstelle ermöglicht es, mit anderen E-Mail Nutzern zu kommunizieren. Zur Zeit ist nur die Mailchuck-E-Mail Schnittstelle (@mailchuck.com) verfügbar. - + Desired email address (including @mailchuck.com): Gewünschte E-Mailaddresse (inkl. @mailchuck.com): - - - @mailchuck.com - @mailchuck.com - - - - Registration failed: - Registrierung fehlgeschlagen: - - - - The requested email address is not available, please try a new one. - Die gewünschte E-Mailaddresse ist nicht verfügbar, bitte probieren Sie eine neue. - - - - Sending email gateway registration request - Der Registrierungsantrag für die E-Mail Schnittstelle wird versandt. - - - - Sending email gateway unregistration request - E-Mail Schnittestellen-Abmeldeantrag wird versandt - - - - Sending email gateway status request - E-Mail Schnittestellen Statusantrag wird versandt - EmailGatewayRegistrationDialog - + Registration failed: - + Registrierung fehlgeschlagen: - + The requested email address is not available, please try a new one. Fill out the new desired email address (including @mailchuck.com) below: - + Die gewünschte E-Mailaddresse ist nicht verfügbar, bitte probieren Sie eine neue. Die gewünschte E-Mailaddresse (inkl. @mailchuck.com) unten ausfüllen: Email gateway registration - + E-Mail Schnittstellen Registrierung Email gateway allows you to communicate with email users. Currently, only the Mailchuck email gateway (@mailchuck.com) is available. Please type the desired email address (including @mailchuck.com) below: - + Die E-Mail Schnittstelle ermöglicht es, mit anderen E-Mail-Nutzern zu kommunizieren. Zur Zeit ist nur die Mailchuck-E-Mail-Schnittstelle verfügbar (@mailchuck.com). +Bitte geben Sie die gewünschte E-Mail-Adresse (inkl. @mailchuck.com) unten ein: Mailchuck - + # You can use this to configure your email gateway account # Uncomment the setting you want to use # Here are the options: @@ -203,366 +174,366 @@ Please type the desired email address (including @mailchuck.com) below: MainWindow - + Reply to sender Dem Absender antworten - + Reply to channel Antworten in den Chan - + Add sender to your Address Book Absender zum Adressbuch hinzufügen - + Add sender to your Blacklist Absender in die Blacklist eintragen - + Move to Trash In den Papierkorb verschieben - + Undelete Wiederherstellen - + View HTML code as formatted text HTML als formatierten Text anzeigen - + Save message as... Nachricht speichern unter... - + Mark Unread Als ungelesen markieren - + New Neu - + Enable Aktivieren - + Disable Deaktivieren - + Set avatar... Avatar wählen... - + Copy address to clipboard Adresse in die Zwischenablage kopieren - + Special address behavior... Spezielles Verhalten der Adresse... - + Email gateway E-Mail Schnittstelle - + Delete Löschen - + Send message to this address Nachricht an diese Adresse senden - + Subscribe to this address Diese Adresse abonnieren - + Add New Address Neue Adresse hinzufügen - + Copy destination address to clipboard Zieladresse in die Zwischenablage kopieren - + Force send Senden erzwingen - + One of your addresses, %1, is an old version 1 address. Version 1 addresses are no longer supported. May we delete it now? Eine Ihrer Adressen, %1, ist eine alte Adresse der Version 1 und wird nicht mehr unterstützt. Soll sie jetzt gelöscht werden? - + Waiting for their encryption key. Will request it again soon. Warte auf den Verschlüsselungscode. Wird bald erneut angefordert. Encryption key request queued. - + Verschlüsselungscode-Anforderung steht aus. - + Queued. In Warteschlange. - + Message sent. Waiting for acknowledgement. Sent at %1 - Nachricht gesendet. Warte auf Bestätigung. Zeitpunkt der Sendung: %1 + Nachricht gesendet. Warte auf Bestätigung. Gesendet %1 - + Message sent. Sent at %1 - Nachricht gesendet. Zeitpunkt der Sendung: %1 + Nachricht gesendet. Gesendet am %1 Need to do work to send message. Work is queued. - + Es muss Arbeit verrichtet werden um die Nachricht zu versenden. Arbeit ist in Warteschlange. - + Acknowledgement of the message received %1 Bestätigung der Nachricht erhalten %1 - + Broadcast queued. Rundruf in Warteschlange. - + Broadcast on %1 Rundruf um %1 - + Problem: The work demanded by the recipient is more difficult than you are willing to do. %1 Problem: Die vom Empfänger geforderte Arbeit ist schwerer als Sie bereit sind, zu berechnen. %1 - + Problem: The recipient's encryption key is no good. Could not encrypt message. %1 Problem: Der Verschlüsselungscode des Empfängers ist nicht in Ordnung. Nachricht konnte nicht verschlüsselt werden. %1 - + Forced difficulty override. Send should start soon. Schwierigkeitslimit überschrieben. Senden sollte bald beginnen. - + Unknown status: %1 %2 Unbekannter Status: %1 %2 - + Not Connected Nicht verbunden - + Show Bitmessage Bitmessage anzeigen - + Send Senden - + Subscribe Abonnieren - + Channel Chan - + Quit Beenden - + You may manage your keys by editing the keys.dat file stored in the same directory as this program. It is important that you back up this file. - Sie können Ihre Schlüssel verwalten, indem Sie die keys.dat bearbeiten, die im gleichen Ordner wie das Programm liegt. Es ist empfehlenswert, vorher ein Backup dieser Datei anzulegen. + Sie können Ihre Schlüssel verwalten, indem Sie die keys.dat bearbeiten, die im gleichen Ordner wie das Programm liegt. Es ist empfehlenswert, vorher ein Backup dieser Datei anlegen. - + You may manage your keys by editing the keys.dat file stored in %1 It is important that you back up this file. Sie können Ihre Schlüssel verwalten, indem Sie die keys.dat bearbeiten, die im Ordner %1 liegt. -Es ist empfehlenswert, vorher ein Backup dieser Datei anzulegen. +Es ist empfehlenswert, vorher ein Backup dieser Datei anlegen. - + Open keys.dat? Die keys.dat öffnen? - + You may manage your keys by editing the keys.dat file stored in the same directory as this program. It is important that you back up this file. Would you like to open the file now? (Be sure to close Bitmessage before making any changes.) - Sie können Ihre Schlüssel verwalten, indem Sie die keys.dat bearbeiten, die im gleichen Ordner wie das Programm liegt. Es ist empfehlenswert, vorher ein Backup dieser Datei anzulegen. Möchten Sie die Datei jetzt öffnen? (Stellen Sie sicher, dass Sie Bitmessage beendet haben, bevor Sie etwas ändern.) + Sie können Ihre Schlüssel verwalten, indem Sie die keys.dat bearbeiten, die im gleichen Ordner wie das Programm liegt. Es ist empfehlenswert, vorher ein Backup dieser Datei anlegen. Möchten Sie die Datei jetzt öffnen? (Stellen Sie sicher, dass Sie Bitmessage beendet haben, bevor Sie etwas ändern.) - + You may manage your keys by editing the keys.dat file stored in %1 It is important that you back up this file. Would you like to open the file now? (Be sure to close Bitmessage before making any changes.) Sie können Ihre Schlüssel verwalten, indem Sie die keys.dat bearbeiten, die im Ordner %1 liegt. -Es ist empfehlenswert, vorher ein Backup dieser Datei anzulegen. Möchten Sie die Datei jetzt öffnen? +Es ist empfehlenswert, vorher ein Backup dieser Datei anlegen. Möchten Sie die Datei jetzt öffnen? (Stellen Sie sicher, dass Sie Bitmessage beendet haben, bevor Sie etwas ändern.) - + Delete trash? Papierkorb leeren? - + Are you sure you want to delete all trashed messages? Sind Sie sicher, dass Sie alle Nachrichten im Papierkorb löschen möchten? - + bad passphrase Falsches Passwort - + You must type your passphrase. If you don't have one then this is not the form for you. Sie müssen Ihr Passwort eingeben. Wenn Sie keins haben, ist dies das falsche Formular für Sie. - + Bad address version number Falsche Addressenversionsnummer - + Your address version number must be a number: either 3 or 4. Die Addressenversionsnummer muss eine Zahl sein, entweder 3 oder 4. - + Your address version number must be either 3 or 4. Die Addressenversionnsnummer muss entweder 3 oder 4 sein. - + Chan name needed - + Name für den Chan benötigt + + + + You didn't enter a chan name. + Sie haben keinen Chan-Namen eingegeben. + + + + Address already present + Adresse bereits vorhanden + + + + Could not add chan because it appears to already be one of your identities. + Chan konnte nicht erstellt werden, da es sich bereits um eine Ihrer Identitäten handelt. + + + + Success + Erfolgreich + + + + Successfully created chan. To let others join your chan, give them the chan name and this Bitmessage address: %1. This address also appears in 'Your Identities'. + Chan erfolgreich erstellt. Um andere diesem Chan beitreten zu lassen, geben Sie ihnen den Chan-Namen und die Bitmessage-Adresse: %1. Diese Adresse befindet sich auch unter "Ihre Identitäten". + + + + Address too new + Adresse zu neu + + + + Although that Bitmessage address might be valid, its version number is too new for us to handle. Perhaps you need to upgrade Bitmessage. + Obwohl diese Bitmessage-Adresse gültig ist, ist ihre Versionsnummer zu hoch um verarbeitet zu werden. Vermutlich müssen Sie eine neuere Version von Bitmessage installieren. - You didn't enter a chan name. - - - - - Address already present - - - - - Could not add chan because it appears to already be one of your identities. - - - - - Success - - - - - Successfully created chan. To let others join your chan, give them the chan name and this Bitmessage address: %1. This address also appears in 'Your Identities'. - - - - - 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. - - - - Address invalid - + Adresse ungültig + + + + That Bitmessage address is not valid. + Diese Bitmessage-Adresse ist nicht gültig. - That Bitmessage address is not valid. - - - - Address does not match chan name - + Adresse stimmt nicht mit dem Chan-Namen überein + + + + Although the Bitmessage address you entered was valid, it doesn't match the chan name. + Obwohl die Bitmessage-Adresse die Sie eingegeben haben gültig ist, stimmt diese nicht mit dem Chan-Namen überein. - Although the Bitmessage address you entered was valid, it doesn't match the chan name. - - - - Successfully joined chan. - + Chan erfolgreich beigetreten. - + Connection lost Verbindung verloren - + Connected Verbunden - + Message trashed Nachricht in den Papierkorb verschoben - + The TTL, or Time-To-Live is the length of time that the network will hold the message. The recipient must get it during this time. If your Bitmessage client does not hear an acknowledgement, it will resend the message automatically. The longer the Time-To-Live, the @@ -570,272 +541,272 @@ Es ist empfehlenswert, vorher ein Backup dieser Datei anzulegen. Möchten Sie di Die Haltbarkeit, oder Time-To-Live, ist die Dauer, für die das Netzwerk die Nachricht speichern wird. Der Empfänger muss sie während dieser Zeit empfangen. Wenn Ihr Bitmessage-Client keine Empfangsbestätigung erhält, wird die Nachricht automatisch erneut verschickt. Je länger die Time-To-Live, desto mehr Arbeit muss Ihr Rechner verrichten, um die Nachricht zu senden. Eine Time-To-Live von vier oder fünf Tagen ist meist ausreichend. - + Message too long Narchricht zu lang - + The message that you are trying to send is too long by %1 bytes. (The maximum is 261644 bytes). Please cut it down before sending. Die Nachricht, die Sie zu senden versuchen, ist %1 Byte zu lang. (Maximum 261.644 Bytes). Bitte verringern Sie ihre Größe vor dem Senden. - + Error: Your account wasn't registered at an email gateway. Sending registration now as %1, please wait for the registration to be processed before retrying sending. - Fehler: Ihr Konto war an keiner E-Mail Schnittstelle registriert. Registrierung als %1 wird versandt, bitte vor einem erneutem Sendeversuch auf die Registrierungsverarbeitung warten. + Fehler: Ihr Konto war an keiner E-Mailschnittstelle registriert. Registrierung als %1 wird versandt, bitte vor einem erneutem Sendeversuch auf die Registrierungsverarbeitung warten. - + Error: Bitmessage addresses start with BM- Please check %1 - + Fehler: Bitmessage Adressen starten mit BM- Bitte überprüfen Sie %1 - + Error: The address %1 is not typed or copied correctly. Please check it. - + Fehler: Die Adresse %1 wurde nicht korrekt getippt oder kopiert. Bitte überprüfen. - + Error: The address %1 contains invalid characters. Please check it. - + Fehler: Die Adresse %1 beinhaltet ungültig Zeichen. Bitte überprüfen. - + Error: The address version in %1 is too high. Either you need to upgrade your Bitmessage software or your acquaintance is being clever. - + Fehler: Die Adressversion von %1 ist zu hoch. Entweder Sie müssen Ihre Bitmessage Software aktualisieren oder Ihr Bekannter ist sehr clever. - + Error: Some data encoded in the address %1 is too short. There might be something wrong with the software of your acquaintance. - + Fehler: Einige Daten die in der Adresse %1 codiert sind, sind zu kurz. Es könnte sein, dass etwas mit der Software Ihres Bekannten nicht stimmt. - + Error: Some data encoded in the address %1 is too long. There might be something wrong with the software of your acquaintance. - + Fehler: Einige Daten die in der Adresse %1 codiert sind, sind zu lang. Es könnte sein, dass etwas mit der Software Ihres Bekannten nicht stimmt. - + Error: Some data encoded in the address %1 is malformed. There might be something wrong with the software of your acquaintance. - + Fehler: Einige codierte Daten in der Adresse %1 sind ungültig. Es könnte etwas mit der Software Ihres Bekannten sein. - + Error: Something is wrong with the address %1. - + Fehler: Mit der Adresse %1 stimmt etwas nicht. - + Error: You must specify a From address. If you don't have one, go to the 'Your Identities' tab. Fehler: Sie müssen eine Absenderadresse auswählen. Sollten Sie keine haben, wechseln Sie zum Reiter "Ihre Identitäten". - + Address version number Adressversion - + Concerning the address %1, Bitmessage cannot understand address version numbers of %2. Perhaps upgrade Bitmessage to the latest version. Aufgrund der Adresse %1 kann Bitmessage Adressen mit der Version %2 nicht verarbeiten. Möglicherweise müssen Sie Bitmessage auf die aktuelle Version aktualisieren. - + Stream number Datenstrom Nummer - + Concerning the address %1, Bitmessage cannot handle stream numbers of %2. Perhaps upgrade Bitmessage to the latest version. Aufgrund der Adresse %1 kann Bitmessage den Datenstrom mit der Version %2 nicht verarbeiten. Möglicherweise müssen Sie Bitmessage auf die aktuelle Version aktualisieren. - + Warning: You are currently not connected. Bitmessage will do the work necessary to send the message but it won't send until you connect. Warnung: Sie sind aktuell nicht verbunden. Bitmessage wird die nötige Arbeit zum versenden verrichten, aber erst senden, wenn Sie verbunden sind. - + Message queued. Nachricht befindet sich in der Warteschleife. - + Your 'To' field is empty. Ihr "Empfänger"-Feld ist leer. - + Right click one or more entries in your address book and select 'Send message to this address'. Klicken Sie mit rechts auf einen oder mehrere Einträge aus Ihrem Adressbuch und wählen Sie "Nachricht an diese Adresse senden". - + Fetched address from namecoin identity. Adresse aus Namecoin Identität geholt. - + New Message Neue Nachricht - + From - + Von - + Sending email gateway registration request - + Der Registrierungsantrag für die E-Mail Schnittstelle wird versandt. - + Address is valid. Adresse ist gültig. - + The address you entered was invalid. Ignoring it. Die von Ihnen eingegebene Adresse ist ungültig, sie wird ignoriert. - + Error: You cannot add the same address to your address book twice. Try renaming the existing one if you want. Fehler: Sie können eine Adresse nicht doppelt im Adressbuch speichern. Sie können jedoch die bereits eingetragene umbenennen. - + Error: You cannot add the same address to your subscriptions twice. Perhaps rename the existing one if you want. Fehler: Dieselbe Adresse kann nicht doppelt in die Abonnements eingetragen werden. Sie können jedoch die bereits eingetragene umbenennen. - + Restart Neustart - + You must restart Bitmessage for the port number change to take effect. Sie müssen Bitmessage neu starten, um den geänderten Port zu verwenden. - + Bitmessage will use your proxy from now on but you may want to manually restart Bitmessage now to close existing connections (if any). Bitmessage wird ab sofort den Proxy-Server verwenden, aber eventuell möchten Sie Bitmessage neu starten um bereits bestehende Verbindungen zu schließen. - + Number needed Zahl erforderlich - + Your maximum download and upload rate must be numbers. Ignoring what you typed. Ihre maximale Herungerlade- und Hochladegeschwindigkeit müssen Zahlen sein. Die eingetragenen Werte werden ignoriert. - + Will not resend ever Wird nie wiederversendet - + Note that the time limit you entered is less than the amount of time Bitmessage waits for the first resend attempt therefore your messages will never be resent. Bitte beachten Sie, dass der eingetratene Dauer kürzer ist als die, die Bitmessage auf das erste Wiederversenden wartet. Deswegen werden Ihre Nachrichten nie wiederversendet. - + Sending email gateway unregistration request - + E-Mail Schnittestellen-Abmeldeantrag wird versandt - + Sending email gateway status request - + E-Mail Schnittestellen Statusantrag wird versandt - + Passphrase mismatch Kennwort stimmt nicht überein - + The passphrase you entered twice doesn't match. Try again. Die von Ihnen eingegebenen Kennwörter sind nicht identisch. Bitte neu versuchen. - + Choose a passphrase Wählen Sie ein Kennwort - + You really do need a passphrase. - Sie benötigen unbedingt ein Kennwort. + Sie benötigen wirklich ein Kennwort. - + Address is gone Adresse ist verloren - + Bitmessage cannot find your address %1. Perhaps you removed it? Bitmessage kann Ihre Adresse %1 nicht finden. Haben Sie sie gelöscht? - + Address disabled Adresse deaktiviert - + Error: The address from which you are trying to send is disabled. You'll have to enable it on the 'Your Identities' tab before using it. Fehler: Die Adresse von der Sie versuchen zu senden ist deaktiviert. Sie müssen sie unter dem Reiter "Ihre Identitäten" aktivieren bevor Sie fortfahren. - + Entry added to the Address Book. Edit the label to your liking. - + Eintrag dem Adressbuch hinzugefügt. Editieren Sie den Eintrag nach Belieben. - + Entry added to the blacklist. Edit the label to your liking. Eintrag in die Blacklist hinzugefügt. Die Beschriftung können Sie ändern. - + Error: You cannot add the same address to your blacklist twice. Try renaming the existing one if you want. Fehler: Dieselbe Addresse kann nicht doppelt in die Blacklist eingetragen werden. Sie können jedoch die bereits eingetragene umbenennen. - + Moved items to trash. Objekt(e) in den Papierkorb verschoben. - + Undeleted item. Nachricht wiederhergestellt. - + Save As... Speichern unter... - + Write error. Fehler beim Speichern. - + No addresses selected. Keine Adresse ausgewählt. - + If you delete the subscription, messages that you already received will become inaccessible. Maybe you can consider disabling the subscription instead. Disabled subscriptions will not receive new messages, but you can still view messages you already received. Are you sure you want to delete the subscription? @@ -844,7 +815,7 @@ Are you sure you want to delete the subscription? Sind Sie sicher, dass Sie das Abonnement löschen möchten? - + If you delete the channel, messages that you already received will become inaccessible. Maybe you can consider disabling the channel instead. Disabled channels will not receive new messages, but you can still view messages you already received. Are you sure you want to delete the channel? @@ -853,897 +824,595 @@ Are you sure you want to delete the channel? Sind Sie sicher, dass Sie das Chan löschen möchten? - + Do you really want to remove this avatar? Wollen Sie diesen Avatar wirklich entfernen? - + You have already set an avatar for this address. Do you really want to overwrite it? Sie haben bereits einen Avatar für diese Adresse gewählt. Wollen Sie ihn wirklich überschreiben? - + Start-on-login not yet supported on your OS. Mit Betriebssystem starten, noch nicht von Ihrem Betriebssystem unterstützt - + Minimize-to-tray not yet supported on your OS. Ins System Tray minimieren von Ihrem Betriebssytem noch nicht unterstützt. - + Tray notifications not yet supported on your OS. Trach-Benachrichtigungen von Ihrem Betriebssystem noch nicht unterstützt. - + Testing... teste... - + This is a chan address. You cannot use it as a pseudo-mailing list. - + Dies ist eine Chan-Adresse. Sie können sie nicht als Pseudo-Mailingliste verwenden. - + The address should start with ''BM-'' Die Adresse sollte mit "BM-" beginnen - + The address is not typed or copied correctly (the checksum failed). Die Adresse wurde nicht korrekt getippt oder kopiert (Prüfsumme falsch). - + The version number of this address is higher than this software can support. Please upgrade Bitmessage. - Die Versionsnummer dieser Adresse ist höher als diese Software unterstützt. Bitte installieren Sie die neueste Bitmessage Version. + Die Versionsnummer dieser Adresse ist höher als diese Software unterstützt. Bitte installieren Sie die neuste Bitmessage Version. - + The address contains invalid characters. Diese Adresse beinhaltet ungültige Zeichen. - + Some data encoded in the address is too short. - Die in der Adresse kodierten Daten sind zu kurz. + Die in der Adresse codierten Daten sind zu kurz. - + Some data encoded in the address is too long. - Die in der Adresse kodierten Daten sind zu lang. + Die in der Adresse codierten Daten sind zu lang. - + Some data encoded in the address is malformed. Einige in der Adresse kodierten Daten sind ungültig. - + Enter an address above. - + Eine Addresse oben ausfüllen. - + Address is an old type. We cannot display its past broadcasts. Alter Addressentyp. Wir können deren vorige Rundrufe nicht anzeigen. - + There are no recent broadcasts from this address to display. Es gibt keine neuen Rundrufe von dieser Adresse die angezeigt werden können. - + You are using TCP port %1. (This can be changed in the settings). - + Sie benutzen TCP-Port %1 (Dieser kann in den Einstellungen verändert werden). - + Bitmessage Bitmessage - + Identities Identitäten - + New Identity Neue Identität - + Search Suchen - + All Alle - + To An - + From Von - + Subject Betreff - + Message Nachricht - + Received Erhalten - + Messages Nachrichten - + Address book Addressbuch - + Address Adresse - + Add Contact Kontakt hinzufügen - + Fetch Namecoin ID Hole Namecoin ID - + Subject: Betreff: - + From: Von: - + To: An: - + Send ordinary Message Ordentliche Nachricht senden - + Send Message to your Subscribers Rundruf an Ihre Abonnenten senden - + TTL: - Lebenszeit: + Ablauf: - + Subscriptions Abonnements - + Add new Subscription Neues Abonnement anlegen - + Chans Chans - + Add Chan Chan hinzufügen - + File Datei - + Settings Einstellungen - + Help Hilfe - + Import keys Schlüssel importieren - + Manage keys Schlüssel verwalten - + Ctrl+Q Strg+Q - + F1 F1 - + Contact support - Unterstützung anfordern + Unterstütung anfordern - + About Über - + Regenerate deterministic addresses Deterministische Adressen neu generieren - + Delete all trashed messages Alle Nachrichten im Papierkorb löschen - + Join / Create chan Chan beitreten / erstellen - + All accounts Alle Identitäten - + Zoom level %1% Zoom-Stufe %1% - + Error: You cannot add the same address to your list twice. Perhaps rename the existing one if you want. Fehler: Sie können eine Adresse nicht doppelt zur Liste hinzufügen. Wenn Sie möchten, benennen Sie den existierenden Eintrag um. - + Add new entry Neuen Eintrag erstellen - + Display the %1 recent broadcast(s) from this address. - + Die letzten %1 Rundruf(e) von dieser Addresse anzeigen. - + New version of PyBitmessage is available: %1. Download it from https://github.com/Bitmessage/PyBitmessage/releases/latest Neue Version von PyBitmessage steht zur Verfügung: %1. Sie können sie von https://github.com/Bitmessage/PyBitmessage/releases/latest herunterladen. - + Waiting for PoW to finish... %1% - Warte auf Abschluss von Berechnungen (PoW)... %1% + Warte auf Achluss von Berechnungen (PoW)... %1% - + Shutting down Pybitmessage... %1% PyBitmessage wird beendet... %1% - + Waiting for objects to be sent... %1% Warte auf Versand von Objekten... %1% - + Saving settings... %1% Einstellungen werden gespeichert... %1% - + Shutting down core... %1% Kern wird beendet... %1% - + Stopping notifications... %1% Beende Benachrichtigungen... %1% - + Shutdown imminent... %1% Unmittelbar vor Beendung... %1% - + %n hour(s) %n Stunde%n Stunden - + %n day(s) %n Tag%n Tage - + Shutting down PyBitmessage... %1% PyBitmessage wird beendet... %1% - + Sent Gesendet - + Generating one new address Neue Addresse wird erstellt - + Done generating address. Doing work necessary to broadcast it... Die Addresse wurde erstellt. Arbeit wird verrichtet, um sie zu versenden... - + Generating %1 new addresses. Erzeuge %1 neue Addressen. - + %1 is already in 'Your Identities'. Not adding it again. %1 befindet sich bereits unter Ihren Identitäten, wird nicht doppelt hinzugefügt. - + Done generating address Addresse fertiggestellt. - + SOCKS5 Authentication problem: %1 - + SOCKS5-Authentizierung fehlgeschlagen: %1 - + Disk full Datenträger voll - + Alert: Your disk or data storage volume is full. Bitmessage will now exit. - Warnung: Datenträger ist voll. Bitmessage wird jetzt beendet. + Warnung: Ihr Datenträger oder Datenspeichervolumen ist voll. Bitmessage wird jetzt beendet. - + Error! Could not find sender address (your address) in the keys.dat file. Fehler! Konnte die Absenderadresse (Ihre Adresse) in der keys.dat-Datei nicht finden. - + Doing work necessary to send broadcast... - Arbeit wird verrichtet, um Rundruf zu verschicken... + Arbeit wird verrichtet, um einen Rundruf zu verschicken... - + Broadcast sent on %1 Rundruf verschickt um %1 - + Encryption key was requested earlier. - Verschlüsselungscode wurde früher angefordert. + Verschlüsselungsschlüssel wurde früher angefordert. - + Sending a request for the recipient's encryption key. - Anfrage nach dem Verschlüsselungscode des Empfängers wird versendet. + Anfrage nach dem Verschlüsselungsschlüssel des Empfängers wird versendet. - + Looking up the receiver's public key Suche nach dem öffentlichen Schlüssel des Empfängers - + Problem: Destination is a mobile device who requests that the destination be included in the message but this is disallowed in your settings. %1 Problem: Der Empfänger benutzt ein mobiles Gerät und erfordert eine unverschlüsselte Empfängeraddresse. Dies ist in Ihren Einstellungen jedoch nicht zulässig. 1% - + Doing work necessary to send message. There is no required difficulty for version 2 addresses like this. Arbeit für Nachrichtenversand wird verrichtet. Version-2-Addressen wie die des Empfängers haben keine Schweirigkeitserforderungen. - + Doing work necessary to send message. Receiver's required difficulty: %1 and %2 - Arbeit für Nachrichtenversand wird errichtet. Vom Empfänger geforderte Schwierigkeit: %1 und %2 + Arbeit für Nachrichtenversand wird errichtet. Vom Empfänger erforderliche Schwierigkeit: %1 und %2 - + Problem: The work demanded by the recipient (%1 and %2) is more difficult than you are willing to do. %3 Problem: Die vom Empfänger verlangte Arbeit (%1 und %2) ist schwieriger, als Sie in den Einstellungen erlaubt haben. %3 - + Problem: You are trying to send a message to yourself or a chan but your encryption key could not be found in the keys.dat file. Could not encrypt message. %1 Problem: Sie versuchen, eine Nachricht an sich zu versenden, aber Ihr Schlüssel befindet sich nicht in der keys.dat-Datei. Die Nachricht kann nicht verschlüsselt werden. 1% - + Doing work necessary to send message. - Arbeit wird verrichtet, um die Nachricht zu verschicken. + Arbeit wird verrichtet, um die Nachricht zu verschicken... - + Message sent. Waiting for acknowledgement. Sent on %1 - Nachricht gesendet. Auf Bestätigung wird gewartet. Zeitpunkt der Sendung: %1 + Nachricht versendet. Auf Bestätigung wird gewartet. Versendet am 1% - + Doing work necessary to request encryption key. Arbeit wird verrichtet, um den Schlüssel nachzufragen... - + Broadcasting the public key request. This program will auto-retry if they are offline. Anfrage nach dem öffentlichen Schlüssel läuft. Wenn der Besitzer nicht mit dem Netzwerk verbunden ist, wird ein Wiederholungsversuch unternommen. - + Sending public key request. Waiting for reply. Requested at %1 Nachfrage nach dem öffentlichen Schlüssel läuft, auf Antwort wird gewartet. Nachgefragt am %1 - + UPnP port mapping established on port %1 UPnP Port-Mapping eingerichtet auf Port %1 - + UPnP port mapping removed UPnP Port-Mapping entfernt - - - Mark all messages as read - Alle Nachrichten als gelesen markieren - - - - Are you sure you would like to mark all messages read? - Sind Sie sicher, dass Sie alle Nachrichten als gelesen markieren möchten? - - - - Doing work necessary to send broadcast. - Führe Arbeit aus, die notwendig ist zum Senden des Rundspruches. - - - - Proof of work pending - Arbeitsbeweis wird berechnet - - - - %n object(s) pending proof of work - %n Objekt wartet auf Berechnungen%n Objekte warten auf Berechnungen - - - - %n object(s) waiting to be distributed - %n Objekt wartet darauf, verteilt zu werden%n Objekte warten darauf, verteilt zu werden - - - - Wait until these tasks finish? - Warten bis diese Aufgaben erledigt sind? - - - - Problem communicating with proxy: %1. Please check your network settings. - Kommunikationsfehler mit dem Proxy: %1. Bitte überprüfen Sie Ihre Netzwerkeinstellungen. - - - - SOCKS5 Authentication problem: %1. Please check your SOCKS5 settings. - SOCKS5-Authentizierung fehlgeschlagen: %1. Bitte überprüfen Sie Ihre SOCKS5-Einstellungen. - - - - The time on your computer, %1, may be wrong. Please verify your settings. - Die Uhrzeit ihres Computers, %1, ist möglicherweise falsch. Bitte überprüfen Sie Ihre einstellungen. - - - - The name %1 was not found. - Der Name %1 wurde nicht gefunden. - - - - The namecoin query failed (%1) - Namecoin-abfrage fehlgeschlagen (%1) - - - - The namecoin query failed. - Namecoin-abfrage fehlgeschlagen. - - - - The name %1 has no valid JSON data. - Der Name %1 beinhaltet keine gültige JSON-Daten. - - - - The name %1 has no associated Bitmessage address. - Der Name %1 hat keine zugewiesene Bitmessageaddresse. - - - - Success! Namecoind version %1 running. - Erfolg! Namecoind Version %1 läuft. - - - - Success! NMControll is up and running. - Erfolg! NMControl läuft. - - - - Couldn't understand NMControl. - Kann NMControl nicht verstehen. - - - - Your GPU(s) did not calculate correctly, disabling OpenCL. Please report to the developers. - Ihre Grafikkarte hat inkorrekt berechnet, OpenCL wird deaktiviert. Bitte benachrichtigen Sie die Entwickler. - - - - Set notification sound... - Benachrichtigungsklang einstellen ... - - - - - Welcome to easy and secure Bitmessage - * send messages to other people - * send broadcast messages like twitter or - * discuss in chan(nel)s with other people - - -Willkommen zu einfachem und sicherem Bitmessage -* senden Sie Nachrichten an andere Leute -* senden Sie Rundrufe wie bei Twitter oder -* diskutieren Sie mit anderen Leuten in Chans - - - - not recommended for chans - für Chans nicht empfohlen - - - - Quiet Mode - stiller Modus - - - - Problems connecting? Try enabling UPnP in the Network Settings - Verbindungsprobleme? Versuchen Sie UPnP in den Netzwerkeinstellungen einzuschalten - - - - You are trying to send an email instead of a bitmessage. This requires registering with a gateway. Attempt to register? - Sie versuchen, eine E-Mail anstelle einer Bitmessage zu senden. Dies erfordert eine Registrierung bei einer Schnittstelle. Registrierung versuchen? - - - - Error: Bitmessage addresses start with BM- Please check the recipient address %1 - Fehler: Bitmessage Adressen starten mit BM- Bitte überprüfen Sie die Empfängeradresse %1 - - - - Error: The recipient address %1 is not typed or copied correctly. Please check it. - Fehler: Die Empfängeradresse %1 wurde nicht korrekt getippt oder kopiert. Bitte überprüfen. - - - - Error: The recipient address %1 contains invalid characters. Please check it. - Fehler: Die Empfängeradresse %1 beinhaltet ungültig Zeichen. Bitte überprüfen. - - - - Error: The version of the recipient address %1 is too high. Either you need to upgrade your Bitmessage software or your acquaintance is being clever. - Fehler: Die Empfängerdresseversion von %1 ist zu hoch. Entweder Sie müssen Ihre Bitmessage Software aktualisieren oder Ihr Bekannter ist sehr clever. - - - - Error: Some data encoded in the recipient address %1 is too short. There might be something wrong with the software of your acquaintance. - Fehler: Einige Daten die in der Empfängerdresse %1 codiert sind, sind zu kurz. Es könnte sein, dass etwas mit der Software Ihres Bekannten nicht stimmt. - - - - Error: Some data encoded in the recipient address %1 is too long. There might be something wrong with the software of your acquaintance. - Fehler: Einige Daten die in der Empfängeradresse %1 codiert sind, sind zu lang. Es könnte sein, dass etwas mit der Software Ihres Bekannten nicht stimmt. - - - - Error: Some data encoded in the recipient address %1 is malformed. There might be something wrong with the software of your acquaintance. - Fehler: Einige codierte Daten in der Empfängeradresse %1 sind ungültig. Es könnte etwas mit der Software Ihres Bekannten sein. - - - - Error: Something is wrong with the recipient address %1. - Fehler: Mit der Empfängeradresse %1 stimmt etwas nicht. - - - - Error: %1 - Fehler: %1 - - - - From %1 - Von %1 - - - - Synchronisation pending - Synchronisierung läuft - - - - Bitmessage hasn't synchronised with the network, %n object(s) to be downloaded. If you quit now, it may cause delivery delays. Wait until the synchronisation finishes? - Bitmessage ist nicht synchronisiert, %n Objekt wurde noch nicht heruntergeladen. Wenn Sie das Programm jetzt beenden, kann es zu Zustellverzögerungen kommen. Warten bis die Synchronisierung abgeschlossen ist?Bitmessage ist nicht synchronisiert, %n Objekte wurden noch nicht heruntergeladen. Wenn Sie das Programm jetzt beenden, kann es zu Zustellverzögerungen kommen. Warten bis die Synchronisierung abgeschlossen ist? - - - - Not connected - Nicht verbunden - - - - Bitmessage isn't connected to the network. If you quit now, it may cause delivery delays. Wait until connected and the synchronisation finishes? - Bitmessage ist nicht mit dem Netzwerk verbunden. Wenn Sie das Programm jetzt beenden, kann es zu Zustellverzögerungen kommen. Warten bis eine Verbindung besteht und die Synchronisierung abgeschlossen ist? - - - - Waiting for network connection... - Warte auf Netzwerkverbindung... - - - - Waiting for finishing synchronisation... - Warte auf Synchronisationsabschluss... - - - - You have already set a notification sound for this address book entry. Do you really want to overwrite it? - Sie haben bereits einen Benachrichtigungsklang für diesen Adressbucheintrag gesetzt. Möchten Sie ihn wirklich überschreiben? - - - - Error occurred: could not load message from disk. - Fehler: Nachricht konnte nicht geladen werden. - - - - Display the %n recent broadcast(s) from this address. - Den letzten %1 Rundruf von dieser Addresse anzeigen.Die letzten %1 Rundrufe von dieser Addresse anzeigen. - - - - Go online - Mit dem Netzwerk verbinden - - - - Go offline - Trennen vom Netzwerk - - - - Clear - Löschen - - - - inbox - Eingang - - - - new - Neu - - - - sent - - - - - trash - - - - - MessageView - - - Follow external link - Externen Link folgen - - - - The link "%1" will open in a browser. It may be a security risk, it could de-anonymise you or download malicious data. Are you sure? - Der Link "%1" wird in Browser geöffnet. Es kann ein Sicherheitsrisiko darstellen, es könnte Sie de-anonymisieren oder schädliche Aktivitäten durchführen. Sind Sie sicher? - - - - HTML detected, click here to display - HTML gefunden, klicken Sie hier um anzuzeigen - - - - Click here to disable HTML - Klicken Sie hier um HTML-Anzeige zu deaktivieren - - - - MsgDecode - - - The message has an unknown encoding. -Perhaps you should upgrade Bitmessage. - Diese Bitmessage verwendtet eine unbekannte codierung. -Womöglich sollten Sie Bitmessage upgraden. - - - - Unknown encoding - Codierung unbekannt - NewAddressDialog - + Create new Address Neue Adresse erstellen - + Here you may generate as many addresses as you like. Indeed, creating and abandoning addresses is encouraged. You may generate addresses by using either random numbers or by using a passphrase. If you use a passphrase, the address is called a "deterministic" address. The 'Random Number' option is selected by default but deterministic addresses have several pros and cons: Sie können so viele Adressen generieren wie Sie möchten. Es ist sogar empfohlen neue Adressen zu verwenden und alte fallen zu lassen. Sie können Adressen durch Zufallszahlen erstellen lassen, oder unter Verwendung eines Kennwortsatzes. Wenn Sie einen Kennwortsatz verwenden, nennt man dies eine "deterministische" Adresse. -Die Zufallszahlen-Option ist standardmässig gewählt, jedoch haben deterministische Adressen einige Vor- und Nachteile: +Die Zufallszahlen-Option ist standard, jedoch haben deterministische Adressen einige Vor- und Nachteile: - + <html><head/><body><p><span style=" font-weight:600;">Pros:<br/></span>You can recreate your addresses on any computer from memory. <br/>You need-not worry about backing up your keys.dat file as long as you can remember your passphrase. <br/><span style=" font-weight:600;">Cons:<br/></span>You must remember (or write down) your passphrase if you expect to be able to recreate your keys if they are lost. <br/>You must remember the address version number and the stream number along with your passphrase. <br/>If you choose a weak passphrase and someone on the Internet can brute-force it, they can read your messages and send messages as you.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Vorteile:<br/></span>Sie können ihre Adresse an jedem Computer aus dem Gedächtnis regenerieren. <br/>Sie brauchen sich keine Sorgen um das Sichern ihrer Schlüssel machen solange Sie sich den Kennwortsatz merken. <br/><span style=" font-weight:600;">Nachteile:<br/></span>Sie müssen sich den Kennwortsatz merken (oder aufschreiben) wenn Sie in der Lage sein wollen ihre Schlüssel wiederherzustellen. <br/>Sie müssen sich die Adressversion und die Datenstrom Nummer zusammen mit dem Kennwortsatz merken. <br/>Wenn Sie einen schwachen Kennwortsatz wählen und jemand kann ihn erraten oder durch ausprobieren herausbekommen, kann dieser Ihre Nachrichten lesen, oder in ihrem Namen Nachrichten senden..</p></body></html> - + Use a random number generator to make an address Lassen Sie eine Adresse mittels Zufallsgenerator erstellen - + Use a passphrase to make addresses Benutzen Sie einen Kennwortsatz um eine Adresse erstellen zu lassen - + Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter Verwenden Sie einige Minuten extra Rechenleistung um die Adresse(n) ein bis zwei Zeichen kürzer zu machen - + Make deterministic addresses Deterministische Adresse erzeugen - + Address version number: 4 Adress-Versionsnummer: 4 - + In addition to your passphrase, you must remember these numbers: Zusätzlich zu Ihrem Kennwortsatz müssen Sie sich diese Zahlen merken: - + Passphrase Kennwortsatz - + Number of addresses to make based on your passphrase: Anzahl Adressen die basierend auf diesem Kennwortsatz erzeugt werden sollen: - + Stream number: 1 Datenstrom Nummer: 1 - + Retype passphrase Kennwortsatz erneut eingeben - + Randomly generate address Zufällig generierte Adresse - + Label (not shown to anyone except you) Bezeichnung (Wird niemandem außer Ihnen gezeigt) - + Use the most available stream Verwendung des am besten verfügbaren Datenstroms - + (best if this is the first of many addresses you will create) (Zum Generieren der ersten Adresse empfohlen) - + Use the same stream as an existing address Verwendung des gleichen Datenstroms wie eine bestehende Adresse - + (saves you some bandwidth and processing power) (Dies erspart Ihnen etwas an Bandbreite und Rechenleistung) @@ -1751,22 +1420,22 @@ Die Zufallszahlen-Option ist standardmässig gewählt, jedoch haben deterministi NewSubscriptionDialog - + Add new entry Neuen Eintrag erstellen - + Label Name oder Bezeichnung - + Address Adresse - + Enter an address above. Bitte geben Sie oben eine Adresse ein. @@ -1774,72 +1443,62 @@ Die Zufallszahlen-Option ist standardmässig gewählt, jedoch haben deterministi SpecialAddressBehaviorDialog - + Special Address Behavior Spezielles Adressverhalten - + Behave as a normal address Wie eine normale Adresse verhalten - + Behave as a pseudo-mailing-list address Wie eine Pseudo-Mailinglistenadresse verhalten - + Mail received to a pseudo-mailing-list address will be automatically broadcast to subscribers (and thus will be public). Nachrichten an eine Pseudo-Mailinglistenadresse werden automatisch an alle Abonnenten weitergeleitet (Der Inhalt ist dann öffentlich). - + Name of the pseudo-mailing-list: Name der Pseudo-Mailingliste: - - - This is a chan address. You cannot use it as a pseudo-mailing list. - Dies ist eine Chan-Adresse. Sie können sie nicht als Pseudo-Mailingliste verwenden. - aboutDialog - + About Über - + PyBitmessage - + PyBitmessage - + version ? - + Version ? - + <html><head/><body><p>Distributed under the MIT/X11 software license; see <a href="http://www.opensource.org/licenses/mit-license.php"><span style=" text-decoration: underline; color:#0000ff;">http://www.opensource.org/licenses/mit-license.php</span></a></p></body></html> - <html><head/><body><p>Veröffentlicht unter der MIT/X11 Software-Lizenz, siehe unter <a href="http://www.opensource.org/licenses/mit-license.php"><span style=" text-decoration: underline; color:#0000ff;">http://www.opensource.org/licenses/mit-license.php</span></a></p></body></html> + <html><head/><body><p>Veröffentlicht unter der MIT/X11 Software-Lizenz; 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> - + This is Beta software. Dies ist Beta-Software. - + <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 © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 Die Bitmessage-Entwickler</p></body></html> @@ -1870,58 +1529,53 @@ Die Zufallszahlen-Option ist standardmässig gewählt, jedoch haben deterministi Adresse - + Blacklist - Blacklist (Liste gesperrter Adressen) + Blacklist - + Whitelist - Whitelist (Liste zugelassener Adressen) + Whitelist connectDialog - + Bitmessage - Bitmessage + Internetverbindung - + Bitmessage won't connect to anyone until you let it. Bitmessage wird sich nicht verbinden, wenn Sie es nicht möchten. - + Connect now Jetzt verbinden - + Let me configure special network settings first - Zunächst spezielle Netzwerkeinstellungen vornehmen - - - - Work offline - Nicht verbinden + Zunächst spezielle Nertzwerkeinstellungen vornehmen helpDialog - + Help Hilfe - + <a href="https://bitmessage.org/wiki/PyBitmessage_Help">https://bitmessage.org/wiki/PyBitmessage_Help</a> <a href="https://bitmessage.org/wiki/PyBitmessage_Help">https://bitmessage.org/wiki/PyBitmessage_Help</a> - + As Bitmessage is a collaborative project, help can be found online in the Bitmessage Wiki: Bitmessage ist ein kollaboratives Projekt. Hilfe finden Sie online im Bitmessage-Wiki: @@ -1929,35 +1583,30 @@ Die Zufallszahlen-Option ist standardmässig gewählt, jedoch haben deterministi iconGlossaryDialog - + Icon Glossary Icon Glossar - + You have no connections with other peers. Sie haben keine Verbindung mit anderen Netzwerkteilnehmern. - + You have made at least one connection to a peer using an outgoing connection but you have not yet received any incoming connections. Your firewall or home router probably isn't configured to forward incoming TCP connections to your computer. Bitmessage will work just fine but it would help the Bitmessage network if you allowed for incoming connections and will help you be a better-connected node. - Sie haben mindestes eine Verbindung mit einem Netzwerkteilnehmer über eine ausgehende Verbindung, aber Sie haben noch keine eingehende Verbindung. Ihre Firewall oder Ihr Router ist vermutlich nicht richtig konfiguriert, um eingehende TCP-Verbindungen an Ihren Computer weiterzuleiten. Bitmessage wird gut funktionieren, jedoch helfen Sie dem Netzwerk, wenn Sie eingehende Verbindungen erlauben. Es hilft auch Ihnen, schneller und mehr Verbindungen ins Netzwerk aufzubauen. + Sie haben mindestes eine Verbindung mit einem Netzwerkteilnehmer über eine ausgehende Verbindung, aber Sie haben noch keine eingehende Verbindung. Ihre Firewall oder Ihr Router ist vermutlich nicht richtig konfiguriert, um eingehende TCP-Verbindungen an Ihren Computer weiterzuleiten. Bitmessage wird gut funktionieren, jedoch helfen Sie dem Netzwerk, wenn Sie eingehende Verbindungen erlauben. Es hilft auch Ihnen schneller und mehr Verbindungen ins Netzwerk aufzubauen. You are using TCP port ?. (This can be changed in the settings). - + Sie benutzen TCP-Port ?. (Dies kann in den Einstellungen verändert werden). - + You do have connections with other peers and your firewall is correctly configured. Sie haben Verbindungen mit anderen Netzwerkteilnehmern und Ihre Firewall ist richtig konfiguriert. - - - You are using TCP port %1. (This can be changed in the settings). - Sie benutzen TCP-Port %1 (Dieser kann in den Einstellungen verändert werden). - networkstatus @@ -1967,322 +1616,203 @@ Die Zufallszahlen-Option ist standardmässig gewählt, jedoch haben deterministi Verbindungen insgesamt: - + Since startup: Seit Start: - + Processed 0 person-to-person messages. 0 Person-zu-Person-Nachrichten verarbeitet. - + Processed 0 public keys. 0 öffentliche Schlüssel verarbeitet. - + Processed 0 broadcasts. 0 Rundrufe verarbeitet. - + Inventory lookups per second: 0 Inventory lookups pro Sekunde: 0 - + Objects to be synced: Zu synchronisierende Objektanzahl: - + Stream # Datenstrom # Connections - + Verbindungen - + Since startup on %1 Seit Start der Anwendung am %1 - + Down: %1/s Total: %2 Herunter: %1/s Insg.: %2 - + Up: %1/s Total: %2 Hoch: %1/s Insg.: %2 - + Total Connections: %1 Verbindungen insgesamt: %1 - + Inventory lookups per second: %1 Inventory lookups pro Sekunde: %1 - + Up: 0 kB/s Hoch: 0 kB/s - + Down: 0 kB/s Herunter: 0 kB/s - + Network Status Netzwerkstatus - + byte(s) ByteBytes - + Object(s) to be synced: %n %n Objekt zu synchronisieren.%n Objekte zu synchronisieren. - + Processed %n person-to-person message(s). %n Person-zu-Person-Nachricht bearbeitet.%n Person-zu-Person-Nachrichten bearbeitet. - + Processed %n broadcast message(s). %n Rundruf-Nachricht bearbeitet.%n Rundruf-Nachrichten bearbeitet. - + Processed %n public key(s). %n öffentlicher Schlüssel verarbeitet.%n öffentliche Schlüssel verarbeitet. - - - Peer - Peer - - - - IP address or hostname - IP-Adresse oder Hostname - - - - Rating - Bewertung - - - - PyBitmessage tracks the success rate of connection attempts to individual nodes. The rating ranges from -1 to 1 and affects the likelihood of selecting the node in the future - PyBitmessage verfolgt die Erfolgsrate von Verbindungsversuchen zu einzelnen Knoten. Die Bewertung reicht von -1 bis 1 und beeinflusst die Wahrscheinlichkeit, den Knoten zukünftig auszuwählen - - - - User agent - Benutzer-Agent - - - - Peer's self-reported software - Peer's selbstberichtete Software - - - - TLS - TLS - - - - Connection encryption - Verbindungsverschlüsselung - - - - List of streams negotiated between you and the peer - Liste der zwischen Ihnen und dem Peer ausgehandelter Datenströme - newChanDialog Dialog - + Chan beitreten / erstellen Create a new chan - + Neuen Chan erstellen Join a chan - + Einem Chan beitreten Create a chan - + Chan erstellen <html><head/><body><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. If you and someone else both create a chan with the same chan name then it is currently very likely that they will be the same chan.</p></body></html> - + <html><head/><body><p>Geben Sie einen Namen für Ihren Chan ein. Wenn Sie einen ausreichend komplexen Chan-Namen wählen (wie einen starken, einzigartigen Kennwortsatz) und keiner Ihrer Freunde ihn öffentlich weitergibt, wird der Chan sicher und privat bleiben. Wenn eine andere Person einen Chan mit dem gleichen Namen erzeugt, werden diese zu einem Chan.</p><br></body></html> Chan name: - + Chan-Name: <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 normal person-to-person message to the chan address.</p><p>Chans are experimental and completely unmoderatable.</p></body></html> - + <html><head/><body><p>Ein Chan existiert, wenn eine Gruppe von Leuten sich den gleichen Entschlüsselungscode teilen. Die Schlüssel und Bitmessage-Adressen werden basierend auf einem lesbaren Wort oder Satz generiert (dem Chan-Namen). Um eine Nachricht an den Chan zu senden, senden Sie eine normale Person-zu-Person-Nachricht an die Chan-Adresse.</p><p>Chans sind experimentell und völlig unmoderierbar.</p><br></body></html> Chan bitmessage address: - - - - - Create or join a chan - Chan erstellen oder beitreten - - - - <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>Wenn eine Gruppe von Leuten sich den gleichen Entschlüsselungscode teilen, bilden sie einen "chan". Die Schlüssel und Bitmessage-Adressen werden basierend auf einem lesbaren Wort oder Satz generiert (dem Chan-Namen). Um eine Nachricht an den Chan zu senden, senden Sie eine Nachricht an die Chan-Adresse.</p><p>Chans sind experimentell und völlig zentral unmoderierbar.</p><p>Geben Sie einen Namen für Ihren Chan ein. Wenn Sie einen ausreichend komplexen Chan-Namen wählen (wie einen starken, einzigartigen Kennwortsatz) und keiner Ihrer Freunde ihn öffentlich weitergibt, wird der Chan sicher und privat bleiben. Wenn eine andere Person einen Chan mit dem gleichen Namen erzeugt, repräsentieren diese denselben Chan, bzw. der Chan für beide Personen verwendbar.</p></body></html> - - - - Chan passphrase/name: - Chan-Name - - - - Optional, for advanced usage - Optional, für Fortgeschrittene - - - - Chan address - Chan-Adresse - - - - Please input chan name/passphrase: - Bitte Chan-Namen eingeben: - - - - newchandialog - - - Successfully created / joined chan %1 - Chan %1 erfolgreich erstellt/beigetreten - - - - Chan creation / joining failed - Chan-erstellung/-beitritt fehlgeschlagen - - - - Chan creation / joining cancelled - Chan-erstellung/-beitritt abgebrochen - - - - proofofwork - - - C PoW module built successfully. - C-PoW-Modul erfolgreich erstellt. - - - - Failed to build C PoW module. Please build it manually. - Erstellung vom C-PoW-Modul fehlgeschlagen. Bitte erstellen Sie es manuell. - - - - C PoW module unavailable. Please build it. - C-PoW-Modul nicht verfügbar. Bitte erstellen Sie es. - - - - qrcodeDialog - - - QR-code - QR-Code + Chan-Bitmessage-Adresse: regenerateAddressesDialog - + Regenerate Existing Addresses Bestehende Adresse regenerieren - + Regenerate existing addresses Bestehende Adresse regenerieren - + Passphrase Kennwortsatz - + Number of addresses to make based on your passphrase: - Anzahl Adressen die basierend auf diesem Kennwortsatz erzeugt werden sollen: + Anzahl der Adressen, die basierend auf diesem Kennwortsatz erzeugt werden sollen: - + Address version number: Adress-Versionsnummer: - + Stream number: Stream-Nummer: - + 1 1 - + Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter - Verwenden Sie einige Minuten extra Rechenleistung um die Adresse(n) ein bis zwei Zeichen kürzer zu machen + Verwenden Sie einige Minuten extra Rechenleistung, um die Adresse(n) ein bis zwei Zeichen kürzer zu machen - + You must check (or not check) this box just like you did (or didn't) when you made your addresses the first time. - Sie müssen diese Option auswählen (oder nicht auswählen) wie Sie es gemacht haben, als Sie Ihre Adresse das erste Mal erstellt haben. + Sie müssen diese Option auswählen (oder nicht auswählen) wie Sie es gemacht haben, als Sie Ihre Adresse das erste mal erstellt haben. - + If you have previously made deterministic addresses but lost them due to an accident (like hard drive failure), you can regenerate them here. If you used the random number generator to make your addresses then this form will be of no use to you. Wenn Sie bereits deterministische Adressen erstellt haben, aber diese durch einen Unfall (zum Beispiel durch eine defekte Festplatte) verloren haben, können Sie sie hier regenerieren. Dies funktioniert nur dann, wenn Sie bei der erstmaligen Erstellung Ihrer Adressen nicht den Zufallsgenerator verwendet haben. @@ -2290,310 +1820,295 @@ Die Zufallszahlen-Option ist standardmässig gewählt, jedoch haben deterministi settingsDialog - + Settings Einstellungen - + Start Bitmessage on user login Bitmessage nach dem Hochfahren automatisch starten - + Tray Infobereich (Taskleiste) - + Start Bitmessage in the tray (don't show main window) Bitmessage minimiert starten (zeigt das Hauptfenster nicht an) - + Minimize to tray - In den Infobereich (Tray) minimieren + In den Systemtray minimieren - + Close to tray - Schliessen in den Infobereich (Tray) + Schliessen ins Infobereich - + Show notification when message received Benachrichtigung anzeigen, wenn eine Nachricht eintrifft - + Run in Portable Mode Im portablen Modus arbeiten - + 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. Im portablen Modus werden Nachrichten und Konfigurationen im gleichen Ordner abgelegt, in dem sich das Programm selbst befindet (anstatt im normalen Anwendungsdaten-Ordner). Das macht es möglich, Bitmessage auf einem USB-Stick zu betreiben. - + Willingly include unencrypted destination address when sending to a mobile device Willentlich die unverschlüsselte Adresse des Empfängers übertragen, wenn an ein mobiles Gerät gesendet wird - + Use Identicons Benutze Identicons (Automatisch generierte Icons zu einer Bitmessageadresse) - + Reply below Quote Antworte unter zitierter Nachricht - + Interface Language Sprachauswahl - + System Settings system - Systemeinstellungen + Vom System übernehmen - + User Interface Benutzerinterface - + Listening port - Empfangender TCP-Port + TCP-Port - + Listen for connections on port: Wartet auf Verbindungen auf Port: - + UPnP: UPnP: - + Bandwidth limit Bandbreite begrenzen - + Maximum download rate (kB/s): [0: unlimited] Maximale Downloadrate in kB/s, 0 bedeutet kein Limit - + Maximum upload rate (kB/s): [0: unlimited] Maximale Uploadrate in kB/s, 0 bedeutet kein Limit - + Proxy server / Tor Proxy-Server / Tor - + Type: Typ: - + Server hostname: Servername: - + Port: Port: - + Authentication Authentifizierung - + Username: Benutzername: - + Pass: Kennwort: - + Listen for incoming connections when using proxy Auf eingehende Verbindungen warten, auch wenn ein Proxy-Server verwendet wird - + none keiner - + SOCKS4a SOCKS4a - + SOCKS5 SOCKS5 - + Network Settings Netzwerkeinstellungen - + Total difficulty: Gesamtschwierigkeit: - + The 'Total difficulty' affects the absolute amount of work the sender must complete. Doubling this value doubles the amount of work. Die "Gesamtschwierigkeit" beeinflusst die absolute Menge Arbeit, die ein Sender verrichten muss. Verdoppelung dieses Wertes verdoppelt die Menge der Arbeit. - + Small message difficulty: Schwierigkeit für kurze Nachrichten: - + 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. Wenn jemand Ihnen eine Nachricht schickt, muss der absendende Computer erst einige Arbeit verrichten. Die Schwierigkeit dieser Arbeit ist standardmäßig 1. Sie können diesen Wert für alle neuen Adressen, die Sie generieren, hier ändern. Es gibt eine Ausnahme: Wenn Sie einen Freund oder Bekannten in Ihr Adressbuch übernehmen, wird Bitmessage ihn mit der nächsten Nachricht automatisch informieren, dass er nur noch die minimale Arbeit verrichten muss: Schwierigkeit 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. Die "Schwierigkeit für kurze Nachrichten" trifft nur auf das Senden kurzer Nachrichten zu. Verdoppelung dieses Wertes macht es fast doppelt so schwer, kurze Nachrichten zu senden, aber hat keinen Effekt bei langen Nachrichten. - + Demanded difficulty Geforderte Schwierigkeit - + 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. Hier setzen Sie die maximale Arbeit, die Sie bereit sind zu verrichten, um eine Nachricht an eine andere Person zu versenden. Ein Wert von 0 bedeutet, dass Sie jede Arbeit akzeptieren. - + Maximum acceptable total difficulty: Maximale akzeptierte Gesamtschwierigkeit: - + Maximum acceptable small message difficulty: Maximale akzeptierte Schwierigkeit für kurze Nachrichten: - + Max acceptable difficulty Maximale akzeptierte Schwierigkeit Hardware GPU acceleration (OpenCL) - + Hardwaregrafikkartenbeschleunigung (OpenCL) - + <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 kann ein anderes Bitcoin basiertes Programm namens Namecoin nutzen, um Adressen leserlicher zu machen. Zum Beispiel: Anstelle Ihrem Bekannten Ihre lange Bitmessage-Adresse vorzulesen, können Sie ihm einfach sagen, er soll eine Nachricht an <span style=" font-style:italic;">test </span>senden.</p><p> (Ihre Bitmessage-Adresse in Namecoin zu speichern ist noch sehr umständlich)</p><p>Bitmessage kann direkt namecoind verwenden, oder eine nmcontrol Instanz.</p></body></html> - + Host: Server: - + Password: Kennwort: - + Test Verbindung testen - + Connect to: Verbinde mit: - + Namecoind Namecoind - + NMControl NMControl - + Namecoin integration Namecoin Integration - + <html><head/><body><p>By default, if you send a message to someone and he is offline for more than two days, Bitmessage will send the message again after an additional two days. This will be continued with exponential backoff forever; messages will be resent after 5, 10, 20 days ect. until the receiver acknowledges them. Here you may change that behavior by having Bitmessage give up after a certain number of days or months.</p><p>Leave these input fields blank for the default behavior. </p></body></html> - <html><head/><body><p>Wenn der Empfänger eine Nachricht nicht bis zum Verfallsdatum herunterlädt, zum Beispiel weil er für längere Zeit nicht mit dem Netz verbunden ist, wird die Nachricht erneut versendet. Dies passiert solange, bis eine Empfangsbestätigung erhalten wird. Hier können Sie dieses Verhalten ändern, indem Sie Bitmessage die Wiederversandversuche nach einer bestimmten Anzahl von Tagen oder Monaten aufgeben lassen.</p><p>Für die Standardeinstellung (ohne zeitliche Einschränkung) lassen Sie diese Eingabefelder leer.</p></body></html> + <html><head/><body><p>Wenn der Empfänger eine Nachricht nicht bis zum Ablauf herunterlädt, zum Beisplel weil er für längere Zeit nicht mit dem Netz verbunden ist, wird die Nachricht erneut versendet. Dies passiert solange, bis eine Empfangsbestätigung erhalten wird. Hier können Sie dieses Verhalten ändern, indem Sie Bitmessage die Viederersandversuche nach einer bestimmten Anzahl von Tagen oder Monaten aufgeben lassen.</p><p>Für die Standardeinstellung (ohne zeitliche Einschränkung) lassen Sie diese Eingabefelder leer.</p></body></html> - + Give up after Gib auf nach - + and und - + days Tagen - + months. Monaten. - + Resends Expire - Verfall der erneuten Sendungen - - - - Hide connection notifications - Verbindungsbenachrichtigungen nicht anzeigen - - - - Maximum outbound connections: [0: none] - Maximale Anzahl der ausgehenden Verbindungen, 0 bedeutet keine - - - - Hardware GPU acceleration (OpenCL): - Hardwaregrafikkartenbeschleunigung (OpenCL): + Neusendungsablauf \ No newline at end of file diff --git a/src/translations/bitmessage_en.qm b/src/translations/bitmessage_en.qm index 4751f4ca..fcbb67c7 100644 Binary files a/src/translations/bitmessage_en.qm and b/src/translations/bitmessage_en.qm differ diff --git a/src/translations/bitmessage_en.ts b/src/translations/bitmessage_en.ts index 05e9cc4b..9751ae33 100644 --- a/src/translations/bitmessage_en.ts +++ b/src/translations/bitmessage_en.ts @@ -1,5 +1,4 @@ - - + AddAddressDialog @@ -59,12 +58,12 @@ EmailGatewayRegistrationDialog - + Registration failed: 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: The requested email address is not available, please try a new one. Fill out the new desired email address (including @mailchuck.com) below: @@ -170,52 +169,52 @@ Please type the desired email address (including @mailchuck.com) below: MainWindow - + Reply to sender Reply to sender - + Reply to channel Reply to channel - + Add sender to your Address Book Add sender to your Address Book - + Add sender to your Blacklist Add sender to your Blacklist - + Move to Trash Move to Trash - + Undelete Undelete - + View HTML code as formatted text View HTML code as formatted text - + Save message as... Save message as... - + Mark Unread Mark Unread - + New New @@ -240,12 +239,12 @@ Please type the desired email address (including @mailchuck.com) below:Copy address to clipboard - + Special address behavior... Special address behavior... - + Email gateway Email gateway @@ -255,107 +254,107 @@ Please type the desired email address (including @mailchuck.com) below:Delete - + Send message to this address Send message to this address - + Subscribe to this address Subscribe to this address - + Add New Address Add New Address - + Copy destination address to clipboard Copy destination address to clipboard - + Force send 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? One of your addresses, %1, is an old version 1 address. Version 1 addresses are no longer supported. May we delete it now? - + Waiting for their encryption key. Will request it again soon. Waiting for their encryption key. Will request it again soon. Encryption key request queued. - Encryption key request queued. + Encryption key request queued. - + Queued. Queued. - + Message sent. Waiting for acknowledgement. Sent at %1 Message sent. Waiting for acknowledgement. Sent at %1 - + Message sent. Sent at %1 Message sent. Sent at %1 Need to do work to send message. Work is queued. - Need to do work to send message. Work is queued. + Need to do work to send message. Work is queued. - + Acknowledgement of the message received %1 Acknowledgement of the message received %1 - + Broadcast queued. Broadcast queued. - + Broadcast on %1 Broadcast on %1 - + Problem: The work demanded by the recipient is more difficult than you are willing to do. %1 Problem: The work demanded by the recipient is more difficult than you are willing to do. %1 - + Problem: The recipient's encryption key is no good. Could not encrypt message. %1 Problem: The recipient's encryption key is no good. Could not encrypt message. %1 - + Forced difficulty override. Send should start soon. Forced difficulty override. Send should start soon. - + Unknown status: %1 %2 Unknown status: %1 %2 - + Not Connected Not Connected - + Show Bitmessage Show Bitmessage @@ -365,12 +364,12 @@ Please type the desired email address (including @mailchuck.com) below:Send - + Subscribe Subscribe - + Channel Channel @@ -380,12 +379,12 @@ Please type the desired email address (including @mailchuck.com) below: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. 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. - + You may manage your keys by editing the keys.dat file stored in %1 It is important that you back up this file. @@ -394,17 +393,17 @@ It is important that you back up this file. It is important that you back up this file. - + Open keys.dat? Open 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.) 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.) - + You may manage your keys by editing the keys.dat file stored in %1 It is important that you back up this file. Would you like to open the file now? (Be sure to close Bitmessage before making any changes.) @@ -413,122 +412,122 @@ It is important that you back up this file. Would you like to open the file now? 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.) - + Delete trash? Delete trash? - + Are you sure you want to delete all trashed messages? Are you sure you want to delete all trashed messages? - + bad passphrase bad passphrase - + You must type your passphrase. If you don't have one then this is not the form for you. You must type your passphrase. If you don't have one then this is not the form for you. - + Bad address version number Bad address version number - + Your address version number must be a number: either 3 or 4. Your address version number must be a number: either 3 or 4. - + Your address version number must be either 3 or 4. Your address version number must be either 3 or 4. - + Chan name needed Chan name needed - + You didn't enter a chan name. You didn't enter a chan name. - + Address already present Address already present - + Could not add chan because it appears to already be one of your identities. Could not add chan because it appears to already be one of your identities. - + Success Success - + Successfully created chan. To let others join your chan, give them the chan name and this Bitmessage address: %1. This address also appears in 'Your Identities'. Successfully created chan. To let others join your chan, give them the chan name and this Bitmessage address: %1. This address also appears in 'Your Identities'. - + Address too new 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. Although that Bitmessage address might be valid, its version number is too new for us to handle. Perhaps you need to upgrade Bitmessage. - + Address invalid Address invalid - + That Bitmessage address is not valid. That Bitmessage address is not valid. - + Address does not match chan name Address does not match chan name - + Although the Bitmessage address you entered was valid, it doesn't match the chan name. Although the Bitmessage address you entered was valid, it doesn't match the chan name. - + Successfully joined chan. Successfully joined chan. - + Connection lost Connection lost - + Connected Connected - + Message trashed 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 @@ -539,122 +538,122 @@ It is important that you back up this file. Would you like to open the file now? more work your computer must do to send the message. A Time-To-Live of four or five days is often appropriate. - + Message too long 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. 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. - + 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. 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. - + Error: Bitmessage addresses start with BM- Please check %1 Error: Bitmessage addresses start with BM- Please check %1 - + Error: The address %1 is not typed or copied correctly. Please check it. Error: The address %1 is not typed or copied correctly. Please check it. - + Error: The address %1 contains invalid characters. Please check it. Error: The address %1 contains invalid characters. Please check it. - + Error: The address version in %1 is too high. Either you need to upgrade your Bitmessage software or your acquaintance is being clever. Error: The address version in %1 is too high. Either you need to upgrade your Bitmessage software or your acquaintance is being clever. - + Error: Some data encoded in the address %1 is too short. There might be something wrong with the software of your acquaintance. Error: Some data encoded in the address %1 is too short. There might be something wrong with the software of your acquaintance. - + Error: Some data encoded in the address %1 is too long. There might be something wrong with the software of your acquaintance. Error: Some data encoded in the address %1 is too long. There might be something wrong with the software of your acquaintance. - + Error: Some data encoded in the address %1 is malformed. There might be something wrong with the software of your acquaintance. Error: Some data encoded in the address %1 is malformed. There might be something wrong with the software of your acquaintance. - + Error: Something is wrong with the address %1. Error: Something is wrong with the address %1. - + Error: You must specify a From address. If you don't have one, go to the 'Your Identities' tab. Error: You must specify a From address. If you don't have one, go to the 'Your Identities' tab. - + Address version number Address version number - + Concerning the address %1, Bitmessage cannot understand address version numbers of %2. Perhaps upgrade Bitmessage to the latest version. Concerning the address %1, Bitmessage cannot understand address version numbers of %2. Perhaps upgrade Bitmessage to the latest version. - + Stream number Stream number - + Concerning the address %1, Bitmessage cannot handle stream numbers of %2. Perhaps upgrade Bitmessage to the latest version. Concerning the address %1, Bitmessage cannot handle stream numbers of %2. Perhaps upgrade Bitmessage to the latest 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. 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. Message queued. - + Your 'To' field is empty. Your 'To' field is empty. - + Right click one or more entries in your address book and select 'Send message to this address'. Right click one or more entries in your address book and select 'Send message to this address'. - + Fetched address from namecoin identity. Fetched address from namecoin identity. - + New Message New Message - + From From - + Sending email gateway registration request Sending email gateway registration request @@ -669,142 +668,142 @@ It is important that you back up this file. Would you like to open the file now? 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 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. Error: You cannot add the same address to your subscriptions twice. Perhaps rename the existing one if you want. - + Restart Restart - + You must restart Bitmessage for the port number change to take effect. 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). 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 Number needed - + Your maximum download and upload rate must be numbers. Ignoring what you typed. Your maximum download and upload rate must be numbers. Ignoring what you typed. - + Will not resend ever 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. 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 unregistration request - + Sending email gateway status request Sending email gateway status request - + Passphrase mismatch Passphrase mismatch - + The passphrase you entered twice doesn't match. Try again. The passphrase you entered twice doesn't match. Try again. - + Choose a passphrase Choose a passphrase - + You really do need a passphrase. You really do need a passphrase. - + Address is gone Address is gone - + Bitmessage cannot find your address %1. Perhaps you removed it? Bitmessage cannot find your address %1. Perhaps you removed it? - + Address disabled 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. 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 Address Book. Edit the label to your liking. - + Entry added to the blacklist. 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. Error: You cannot add the same address to your blacklist twice. Try renaming the existing one if you want. - + Moved items to trash. Moved items to trash. - + Undeleted item. Undeleted item. - + Save As... Save As... - + Write error. Write error. - + No addresses selected. 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? @@ -813,7 +812,7 @@ Are you sure you want to delete the subscription? 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? @@ -822,92 +821,92 @@ Are you sure you want to delete the channel? Are you sure you want to delete the channel? - + Do you really want to remove this avatar? Do you really want to remove this avatar? - + You have already set an avatar for this address. Do you really want to overwrite it? 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. Start-on-login not yet supported on your OS. - + Minimize-to-tray not yet supported on your OS. Minimize-to-tray not yet supported on your OS. - + Tray notifications not yet supported on your OS. Tray notifications not yet supported on your OS. - + Testing... Testing... - + This is a chan address. You cannot use it as a pseudo-mailing list. This is a chan address. You cannot use it as a pseudo-mailing list. - + The address should start with ''BM-'' The address should start with ''BM-'' - + The address is not typed or copied correctly (the checksum failed). 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 version number of this address is higher than this software can support. Please upgrade Bitmessage. - + The address contains invalid characters. The address contains invalid characters. - + Some data encoded in the address is too short. 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 too long. - + Some data encoded in the address is malformed. Some data encoded in the address is malformed. - + Enter an address above. Enter an address above. - + Address is an old type. We cannot display its past broadcasts. Address is an old type. We cannot display its past broadcasts. - + There are no recent broadcasts from this address to display. There are no recent broadcasts from this address to display. - + You are using TCP port %1. (This can be changed in the settings). You are using TCP port %1. (This can be changed in the settings). @@ -1117,73 +1116,67 @@ Are you sure you want to delete the channel? Add new entry - + Display the %1 recent broadcast(s) from this address. Display the %1 recent broadcast(s) from this address. - + New version of PyBitmessage is available: %1. Download it from https://github.com/Bitmessage/PyBitmessage/releases/latest New version of PyBitmessage is available: %1. Download it from https://github.com/Bitmessage/PyBitmessage/releases/latest - + Waiting for PoW to finish... %1% Waiting for PoW to finish... %1% - + Shutting down Pybitmessage... %1% Shutting down Pybitmessage... %1% - + Waiting for objects to be sent... %1% Waiting for objects to be sent... %1% - + Saving settings... %1% Saving settings... %1% - + Shutting down core... %1% Shutting down core... %1% - + Stopping notifications... %1% Stopping notifications... %1% - + Shutdown imminent... %1% Shutdown imminent... %1% %n hour(s) - - %n hour - %n hours - + %n hour%n hours - + %n day(s) - - %n day - %n days - + %n day%n days - + Shutting down PyBitmessage... %1% Shutting down PyBitmessage... %1% - + Sent Sent @@ -1213,17 +1206,17 @@ Are you sure you want to delete the channel? Done generating address - + SOCKS5 Authentication problem: %1 SOCKS5 Authentication problem: %1 - + Disk full Disk full - + Alert: Your disk or data storage volume is full. Bitmessage will now exit. Alert: Your disk or data storage volume is full. Bitmessage will now exit. @@ -1233,12 +1226,12 @@ Are you sure you want to delete the channel? Error! Could not find sender address (your address) in the keys.dat file. - + Doing work necessary to send broadcast... Doing work necessary to send broadcast... - + Broadcast sent on %1 Broadcast sent on %1 @@ -1287,7 +1280,7 @@ Receiver's required difficulty: %1 and %2 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 - + Doing work necessary to send message. Doing work necessary to send message. @@ -1297,7 +1290,7 @@ Receiver's required difficulty: %1 and %2 Message sent. Waiting for acknowledgement. Sent on %1 - + Doing work necessary to request encryption key. Doing work necessary to request encryption key. @@ -1312,56 +1305,15 @@ Receiver's required difficulty: %1 and %2 Sending public key request. Waiting for reply. Requested at %1 - + UPnP port mapping established on port %1 UPnP port mapping established on port %1 - + UPnP port mapping removed UPnP port mapping removed - - - 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 object(s) waiting to be distributed - - - - - - - - Wait until these tasks finish? - - NewAddressDialog @@ -1517,34 +1469,34 @@ The 'Random Number' option is selected by default but deterministic ad aboutDialog - + About About - + 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>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> - + This is Beta software. This is Beta software. - - <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 The Bitmessage Developers</p></body></html> - <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 The Bitmessage Developers</p></body></html> + + <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 The Bitmessage Developers</p></body></html> + <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 The Bitmessage Developers</p></body></html> @@ -1744,42 +1696,27 @@ The 'Random Number' option is selected by default but deterministic ad byte(s) - - byte - bytes - + bytebytes Object(s) to be synced: %n - - Object to be synced: %n - Objects to be synced: %n - + Object to be synced: %nObjects to be synced: %n Processed %n person-to-person message(s). - - Processed %n person-to-person message. - Processed %n person-to-person messages. - + Processed %n person-to-person message.Processed %n person-to-person messages. Processed %n broadcast message(s). - - Processed %n broadcast message. - Processed %n broadcast messages. - + Processed %n broadcast message.Processed %n broadcast messages. Processed %n public key(s). - - Processed %n public key. - Processed %n public keys. - + Processed %n public key.Processed %n public keys. @@ -2172,4 +2109,4 @@ The 'Random Number' option is selected by default but deterministic ad Resends Expire - + \ No newline at end of file diff --git a/src/translations/bitmessage_en_pirate.qm b/src/translations/bitmessage_en_pirate.qm index 69e6bde8..9115c42f 100644 Binary files a/src/translations/bitmessage_en_pirate.qm and b/src/translations/bitmessage_en_pirate.qm differ diff --git a/src/translations/bitmessage_en_pirate.ts b/src/translations/bitmessage_en_pirate.ts index 69642a96..d788ffc3 100644 --- a/src/translations/bitmessage_en_pirate.ts +++ b/src/translations/bitmessage_en_pirate.ts @@ -1,5 +1,4 @@ - - + AddAddressDialog @@ -23,61 +22,61 @@ 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. - + Desired email address (including @mailchuck.com): - + EmailGatewayRegistrationDialog - + Registration failed: - + - + The requested email address is not available, please try a new one. Fill out the new desired email address (including @mailchuck.com) below: - + Email 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: - + @@ -124,364 +123,364 @@ Please type the desired email address (including @mailchuck.com) below: # the money directly. To turn it off again, set "feeamount" to 0. Requires # subscription. - + MainWindow - + Reply to sender - + - + Reply to channel - + + + + + Add sender to your Address Book + - Add sender to your Address Book - - - - Add sender to your Blacklist - + - + Move to Trash - + - + Undelete - + + + + + View HTML code as formatted text + - View HTML code as formatted text - + Save message as... + - 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 + - Send message to this address - + Subscribe 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? - + Waiting for their encryption key. Will request it again soon. - + Encryption key request queued. - + Queued. - + Message sent. Waiting for acknowledgement. Sent at %1 - + Message sent. Sent at %1 - + Need to do work to send message. Work is queued. - + Acknowledgement of the message received %1 - + Broadcast queued. - + Broadcast on %1 - + Problem: The work demanded by the recipient is more difficult than you are willing to do. %1 - + Problem: The recipient's encryption key is no good. Could not encrypt message. %1 - + Forced difficulty override. Send should start soon. - + Unknown status: %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. - + You may manage your keys by editing the keys.dat file stored in %1 It is important that you back up this file. - + Open 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.) - + 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.) - + 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. - + Your address version number must be either 3 or 4. - + Chan name needed - + You didn't enter a chan name. - + Address already present - + Could not add chan because it appears to already be one of your identities. - + Success - + Successfully created chan. To let others join your chan, give them the chan name and this Bitmessage address: %1. This address also appears in 'Your Identities'. - + 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. - + Address invalid - + That Bitmessage address is not valid. - + Address does not match chan name - + Although the Bitmessage address you entered was valid, it doesn't match the chan name. - + Successfully joined chan. - + Connection lost - + Connected - + Message trashed - + @@ -489,436 +488,436 @@ It is important that you back up this file. Would you like to open the file now? 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. - + 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. - + 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. - + Error: Bitmessage addresses start with BM- Please check %1 - + Error: The address %1 is not typed or copied correctly. Please check it. - + Error: The address %1 contains invalid characters. Please check it. - + Error: The address version in %1 is too high. Either you need to upgrade your Bitmessage software or your acquaintance is being clever. - + Error: Some data encoded in the address %1 is too short. There might be something wrong with the software of your acquaintance. - + Error: Some data encoded in the address %1 is too long. There might be something wrong with the software of your acquaintance. - + Error: Some data encoded in the address %1 is malformed. There might be something wrong with the software of your acquaintance. - + Error: Something is wrong with the address %1. - + 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. - + Stream number - + Concerning the address %1, Bitmessage cannot handle stream numbers of %2. Perhaps upgrade Bitmessage to the latest 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. - + 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. - + - + New Message - + - + From - + - + Sending email gateway registration request - + Address is valid. - + The address you entered was invalid. Ignoring it. - + - + Error: You cannot add the same address to your address book twice. Try renaming the existing one if you want. - + - + Error: You cannot add the same address to your subscriptions twice. Perhaps rename the existing one if you want. - + - + Restart - + - + You must restart Bitmessage for the port number change to take effect. - + - + Bitmessage will use your proxy from now on but you may want to manually restart Bitmessage now to close existing connections (if any). - + - + Number needed - + - + Your maximum download and upload rate must be numbers. Ignoring what you typed. - + - + Will not resend ever - + - + Note that the time limit you entered is less than the amount of time Bitmessage waits for the first resend attempt therefore your messages will never be resent. - + - + Sending email gateway unregistration request - + - + Sending email gateway status request - + - + Passphrase mismatch - + - + The passphrase you entered twice doesn't match. Try again. - + - + Choose a passphrase - + - + You really do need a passphrase. - + - + Address is gone - + - + Bitmessage cannot find your address %1. Perhaps you removed it? - + - + 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... + - Testing... - - - - This is a chan address. You cannot use it as a pseudo-mailing list. - + - + The address should start with ''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). - + Bitmessage - + Identities - + New Identity - + Search - + All - + To - + From - + Subject - + Message - + Received - + Messages - + Address book - + @@ -928,67 +927,67 @@ Are you sure you want to delete the channel? Add Contact - + Fetch Namecoin ID - + Subject: - + From: - + To: - + Send ordinary Message - + Send Message to your Subscribers - + TTL: - + Subscriptions - + Add new Subscription - + Chans - + Add Chan - + File - + @@ -1003,280 +1002,264 @@ Are you sure you want to delete the channel? Import keys - + Manage keys - + Ctrl+Q - + F1 - + Contact support - + About - + Regenerate deterministic addresses - + Delete all trashed messages - + Join / Create chan - + All accounts - + Zoom level %1% - + Error: You cannot add the same address to your list twice. Perhaps rename the existing one if you want. - + Add new entry - Add yee new entry + - + Display the %1 recent broadcast(s) from this address. - + New version of PyBitmessage is available: %1. Download it from https://github.com/Bitmessage/PyBitmessage/releases/latest - + - + Waiting for PoW to finish... %1% - + - + Shutting down Pybitmessage... %1% - + - + Waiting for objects to be sent... %1% - + - + Saving settings... %1% - + - + Shutting down core... %1% - + - + Stopping notifications... %1% - + - + Shutdown imminent... %1% - + %n hour(s) - - - - + - + %n day(s) - - - - + - + Shutting down PyBitmessage... %1% - + Sent - + Generating one new address - + Done generating address. Doing work necessary to broadcast it... - + Generating %1 new addresses. - + %1 is already in 'Your Identities'. Not adding it again. - + Done generating address - + - + SOCKS5 Authentication problem: %1 - + - + 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. - + Doing work necessary to send broadcast... - + Broadcast sent on %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 - + Doing work necessary to send message. There is no required difficulty for version 2 addresses like this. - + Doing work necessary to send message. Receiver's required difficulty: %1 and %2 - + Problem: The work demanded by the recipient (%1 and %2) is more difficult than you are willing to do. %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 - + Doing work necessary to send message. - + Message sent. Waiting for acknowledgement. Sent on %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 - + - + UPnP port mapping established on port %1 - + - + UPnP port mapping removed - - - - - Mark all messages as read - - - - - Are you sure you would like to mark all messages read? - + @@ -1306,7 +1289,7 @@ T' 'Random Number' option be selected by default but deterministi Use a passphrase to make addresses - + @@ -1321,7 +1304,7 @@ T' 'Random Number' option be selected by default but deterministi Address version number: 4 - + @@ -1399,7 +1382,7 @@ T' 'Random Number' option be selected by default but deterministi Enter an address above. - + @@ -1430,41 +1413,37 @@ T' 'Random Number' option be selected by default but deterministi Name yee pseudo-mailing-list: - - Ui_aboutDialog - - - aboutDialog - <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 The Bitmessage Developers</p></body></html> - - - aboutDialog - + About - + - + 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> - + - + This is Beta software. - + + + + + <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 The Bitmessage Developers</p></body></html> + @@ -1472,37 +1451,37 @@ T' 'Random Number' option be selected by default but deterministi Use a Blacklist (Allow all incoming messages except those on the Blacklist) - + Use a Whitelist (Block all incoming messages except those on the Whitelist) - + Add new entry - Add yee new entry + Name or Label - + Address - Address + Blacklist - + Whitelist - + @@ -1510,22 +1489,22 @@ T' 'Random Number' option be selected by default but deterministi Bitmessage - + Bitmessage won't connect to anyone until you let it. - + Connect now - + Let me configure special network settings first - + @@ -1538,7 +1517,7 @@ T' 'Random Number' option be selected by default but deterministi <a href="https://bitmessage.org/wiki/PyBitmessage_Help">https://bitmessage.org/wiki/PyBitmessage_Help</a> - + @@ -1561,7 +1540,7 @@ T' 'Random Number' option be selected by default but deterministi 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. - + @@ -1579,127 +1558,112 @@ T' 'Random Number' option be selected by default but deterministi Total connections: - + Since startup: - + Processed 0 person-to-person messages. - + Processed 0 public keys. - + Processed 0 broadcasts. - + Inventory lookups per second: 0 - + Objects to be synced: - + Stream # - + Connections - + Since startup on %1 - + Down: %1/s Total: %2 - + Up: %1/s Total: %2 - + Total Connections: %1 - + Inventory lookups per second: %1 - + Up: 0 kB/s - + Down: 0 kB/s - + Network Status - + byte(s) - - - - + Object(s) to be synced: %n - - - - + Processed %n person-to-person message(s). - - - - + Processed %n broadcast message(s). - - - - + Processed %n public key(s). - - - - + @@ -1707,42 +1671,42 @@ T' 'Random Number' option be selected by default but deterministi Dialog - + Create a new chan - + Join a chan - + Create a chan - + <html><head/><body><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. If you and someone else both create a chan with the same chan name then it is currently very likely that they will be the same chan.</p></body></html> - + Chan name: - + <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 normal person-to-person message to the chan address.</p><p>Chans are experimental and completely unmoderatable.</p></body></html> - + Chan bitmessage address: - + @@ -1770,7 +1734,7 @@ T' 'Random Number' option be selected by default but deterministi Address version number: - + @@ -1813,7 +1777,7 @@ T' 'Random Number' option be selected by default but deterministi Tray - + @@ -1828,7 +1792,7 @@ T' 'Random Number' option be selected by default but deterministi Close to tray - + @@ -1848,28 +1812,28 @@ T' 'Random Number' option be selected by default but deterministi Willingly include unencrypted destination address when sending to a mobile device - + Use Identicons - + Reply below Quote - + Interface Language - + System Settings system - + @@ -1889,22 +1853,22 @@ T' 'Random Number' option be selected by default but deterministi UPnP: - + Bandwidth limit - + Maximum download rate (kB/s): [0: unlimited] - + Maximum upload rate (kB/s): [0: unlimited] - + @@ -1944,7 +1908,7 @@ T' 'Random Number' option be selected by default but deterministi Listen for incoming connections when using proxy - + @@ -1999,97 +1963,97 @@ T' 'Random Number' option be selected by default but deterministi 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 - + Hardware GPU acceleration (OpenCL) - + <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> - + Host: - + Password: - + Test - + Connect to: - + Namecoind - + NMControl - + Namecoin integration - + <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> - + Give up after - + and - + days - + months. - + Resends Expire - + - + \ No newline at end of file diff --git a/src/translations/bitmessage_eo.qm b/src/translations/bitmessage_eo.qm index 77c20edf..606f12a2 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..486a4b4a 100644 --- a/src/translations/bitmessage_eo.ts +++ b/src/translations/bitmessage_eo.ts @@ -2,17 +2,17 @@ AddAddressDialog - + Add new entry Aldoni novan elementon - + Label Etikedo - + Address Adreso @@ -20,93 +20,64 @@ EmailGatewayDialog - + Email gateway Retpoŝta kluzo - + Register on email gateway - Registri ĉe retpoŝta kluzo + Registri je retpoŝta kluzo - + Account status at email gateway - Stato de retpoŝt-kluza konto + Stato de retpoŝta kluza konto - + Change account settings at email gateway Ŝanĝu agordojn de konto ĉe retpoŝta kluzo - + Unregister from email gateway Malregistri de retpoŝta kluzo - + Email gateway allows you to communicate with email users. Currently, only the Mailchuck email gateway (@mailchuck.com) is available. Retpoŝta kluzo ebligas al vi komunikadi kun retpoŝtaj uzantoj. Nuntempe, nur la retpoŝta kluzo de Mailchuck (@mailchuck.com) estas disponebla. - + Desired email address (including @mailchuck.com): Dezirata retpoŝta adreso (kune kun @mailchuck.com): - - - @mailchuck.com - @mailchuck.com - - - - Registration failed: - Registrado malsukcesis: - - - - The requested email address is not available, please try a new one. - La dezirata retpoŝtadreso ne estas disponebla, bonvolu provi alian. - - - - Sending email gateway registration request - Sendado de peto pri registrado ĉe retpoŝta kluzo - - - - Sending email gateway unregistration request - Sendado de peto pri malregistrado de retpoŝta kluzo - - - - Sending email gateway status request - Sendado de peto pri stato de retpoŝta kluzo - EmailGatewayRegistrationDialog - + Registration failed: - + Registrado malsukcesis: - + The requested email address is not available, please try a new one. Fill out the new desired email address (including @mailchuck.com) below: - + La dezirata retpoŝtadreso ne estas disponebla, bonvolu provi kun alia. Entajpu novan deziratan adreson (kune kun @mailchuck.com) sube: Email gateway registration - + Registrado je retpoŝta kluzo Email gateway allows you to communicate with email users. Currently, only the Mailchuck email gateway (@mailchuck.com) is available. Please type the desired email address (including @mailchuck.com) below: - + Retpoŝta kluzo ebligas al vi komunikadi kun retpoŝtaj uzantoj. Nuntempe, nur la retpoŝta kluzo de Mailchuck (@mailchuck.com) estas disponebla. +Bonvolu entajpi deziratan retpoŝtadreson (kune kun @mailchuck.com) sube: @@ -152,59 +123,15 @@ 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, # verigi, ĉifri kaj deĉifri kiel vi. Se vi volas uzi PGP-on, sed vi estas laca, -# uzu tion. Postulas abonon. +# uzu tion. Bezonas abonon. # # pgp: local # La retpoŝta kluzo ne faros PGP-operaciojn kiel vi. Vi povas aŭ ne uzi PGP-on @@ -212,16 +139,16 @@ Please type the desired email address (including @mailchuck.com) below: # # attachments: yes # Alvenaj kunsendaĵoj en retmesaĝoj estos alŝutitaj al MEGA.nz, kaj vi povos -# elŝuti ilin de tie per alklaki ligilon. Postulas abonon. +# elŝuti ilin de tie per alklaki ligilon. Bezonas abonon. # # attachments: no -# Kunsendaĵoj estos ignorataj. +# Oni ignoros kunsendaĵojn. # # archive: yes # Viaj alvenontaj retmesaĝoj estos arĥivitaj en la servilo. Uzu tion, se vi -# bezonas helpon kun senerarigado aŭ vi bezonas eksterliveritan pruvon de +# bezonas helpon kun senerarigado aŭ vi bezonas ekstere-liveritan pruvon de # retmesaĝoj. Tamen tio signifas, ke la manipulisto de servo eblos legi viajn -# retmesaĝojn eĉ kiam, tiam oni estos liverinta ilin al vi. +# retmesaĝojn eĉ kiam tiam oni estos liverinta ilin al vi. # # archive: no # Alvenaj mesaĝoj estos forigitaj de la servilo tuj post ili estos liveritaj al vi. @@ -232,59 +159,59 @@ Please type the desired email address (including @mailchuck.com) below: # feecurrency: BTC, XBT, USD, EUR aŭ GBP # Uzu tiujn se vi volas pagoŝarĝi homojn kiuj sendos al vi retmesaĝojn. Se tiu # agordo estas ŝaltita kaj iu ajn sendos al vi retmesaĝon, li devos pagi difinan -# sendokoston. Por remalaktivigi ĝin, agordu ‘feeamount’ al 0. Postulas abonon. +# sendokoston. Por re-malaktivigi ĝin, agordu "feeamount" al 0. Bezonas abonon. 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 + Malforviŝi - + View HTML code as formatted text - Montri HTML-n kiel aranĝitan tekston + Montri HTML-n kiel aranĝita teksto - + Save message as... - Konservi mesaĝon kiel… + Konservi mesaĝon kiel... - + Mark Unread - Marki kiel nelegitan + Marki kiel nelegita - + New Nova @@ -301,7 +228,7 @@ Please type the desired email address (including @mailchuck.com) below: Set avatar... - Agordi avataron… + Agordi avataron... @@ -309,12 +236,12 @@ Please type the desired email address (including @mailchuck.com) below: Kopii adreson al tondejo - + Special address behavior... - Speciala sinteno de adreso… + Speciala sinteno de adreso... - + Email gateway Retpoŝta kluzo @@ -324,405 +251,405 @@ Please type the desired email address (including @mailchuck.com) below: 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? + Iu de viaj adresoj, %1, estas malnova versio 1 adreso. Versioj 1 adresoj ne estas jam subtenataj. Ĉu ni povas forviŝi ĝin? - + Waiting for their encryption key. Will request it again soon. - Atendado je ilia ĉifroŝlosilo. Baldaŭ petos ĝin denove. + Atendante ilian ĉifroŝlosilon. Baldaŭ petos ĝin denove. Encryption key request queued. - + Peto por ĉifroŝlosilo envicigita. - + Queued. En atendovico. - + Message sent. Waiting for acknowledgement. Sent at %1 - Mesaĝo sendita. Atendado je konfirmo. Sendita je %1 + Mesaĝo sendita. Atendante konfirmon. Sendita je %1 - + Message sent. Sent at %1 Mesaĝo sendita. Sendita je %1 Need to do work to send message. Work is queued. - + Devas labori por sendi mesaĝon. Laboro en atendovico. - + 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 redaktante la dosieron keys.dat en la sama dosierujo kiel tiu programo. Estas grava ke vi faru savkopion 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 redaktante la dosieron keys.dat en la dosierujo %1. -Estas grava, ke vi faru sekurkopion de tiu dosiero. +Estas grava ke vi faru savkopion 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 redaktante la dosieron keys.dat en la sama dosierujo kiel tiu programo. Estas grava ke vi faru savkopion 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 redaktante 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.) +Estas grava ke vi faru savkopion 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. + Vi devas tajpi vian pasvorton. Se vi ne havas pasvorton tiu ne estas la prava formularo por vi. - + Bad address version number - Erara numero de adresversio + Malkorekta 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. - + Chan name needed - + Bezonas nomon de kanalo - + You didn't enter a chan name. - + Vi ne enmetis nomon de kanalo. - + Address already present - + Adreso jam ĉi tie - + Could not add chan because it appears to already be one of your identities. - + Ne povis aldoni kanalon ĉar ŝajne jam estas unu el viaj indentigoj. - + Success - - - - - Successfully created chan. To let others join your chan, give them the chan name and this Bitmessage address: %1. This address also appears in 'Your Identities'. - - - - - 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. - - - - - Address invalid - - - - - That Bitmessage address is not valid. - - - - - Address does not match chan name - - - - - Although the Bitmessage address you entered was valid, it doesn't match the chan name. - - - - - Successfully joined chan. - + Sukceso + Successfully created chan. To let others join your chan, give them the chan name and this Bitmessage address: %1. This address also appears in 'Your Identities'. + Sukcese kreis kanalon. Por ebligi al aliaj aniĝi vian kanalon, sciigu al ili la nomon de la kanalo kaj ties Bitmesaĝa adreso: %1. Tiu adreso ankaŭ aperas en 'Viaj identigoj'. + + + + Address too new + Adreso tro nova + + + + Although that Bitmessage address might be valid, its version number is too new for us to handle. Perhaps you need to upgrade Bitmessage. + Kvankam tiu Bitmesaĝa adreso povus esti ĝusta, ĝia versionumero estas tro nova por pritrakti ĝin. Eble vi devas ĝisdatigi vian Bitmesaĝon. + + + + Address invalid + Adreso estas malĝusta + + + + That Bitmessage address is not valid. + Tiu Bitmesaĝa adreso ne estas ĝusta. + + + + Address does not match chan name + Adreso ne kongruas kun kanalonomo + + + + Although the Bitmessage address you entered was valid, it doesn't match the chan name. + Kvankam la Bitmesaĝa adreso kiun vi enigis estas ĝusta, ĝi ne kongruas kun la kanalonomo. + + + + Successfully joined chan. + Sukcese aniĝis al kanalo. + + + Connection lost Perdis konekton - + Connected Konektita - + Message trashed Movis mesaĝon al rubujo - + The TTL, or Time-To-Live is the length of time that the network will hold the message. The recipient must get it during this time. If your Bitmessage client does not hear an acknowledgement, it will resend the message automatically. The longer the Time-To-Live, the more work your computer must do to send the message. A Time-To-Live of four or five days is often appropriate. - La vivdaŭro signifas ĝis kiam la reto tenos la mesaĝon. La ricevonto devos elŝuti ĝin dum tiu tempo. Se via bitmesaĝa kliento ne ricevos konfirmon, ĝi resendos mesaĝon aŭtomate. Ju pli longa vivdaŭro, des pli laboron via komputilo bezonos fari por sendi mesaĝon. Vivdaŭro proksimume kvin aŭ kvar horoj estas ofte konvena. + La vivdaŭro signifas ĝis kiam la reto tenos la mesaĝon. La ricevonto devos elŝuti ĝin dum tiu tempo. Se via Bitmesaĝa kliento ne ricevos konfirmon, ĝi resendos mesaĝon aŭtomate. Ju pli longa vivdaŭro, des pli laboron via komputilo bezonos fari por sendi mesaĝon. Vivdaŭro proksimume kvin aŭ kvar horoj estas ofte konvena. - + Message too long Mesaĝo tro longa - + The message that you are trying to send is too long by %1 bytes. (The maximum is 261644 bytes). Please cut it down before sending. - La mesaĝon kiun vi provis sendi estas tro longa je %1 bitokoj. (La maksimumo estas 261644 bitokoj.) Bonvolu mallongigi ĝin antaŭ sendado. + La mesaĝon kiun vi provis sendi estas tro longa pro %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. - + Error: Bitmessage addresses start with BM- Please check %1 - + Eraro: Bitmesaĝaj adresoj komencas kun BM- Bonvolu kontroli %1 - + Error: The address %1 is not typed or copied correctly. Please check it. - + Eraro: La adreso %1 ne estis prave tajpita aŭ kopiita. Bonvolu kontroli ĝin. - + Error: The address %1 contains invalid characters. Please check it. - + Eraro: La adreso %1 enhavas malpermesitajn simbolojn. Bonvolu kontroli ĝin. - + Error: The address version in %1 is too high. Either you need to upgrade your Bitmessage software or your acquaintance is being clever. - + Eraro: La adresversio %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 address %1 is too short. There might be something wrong with the software of your acquaintance. - + Eraro: Kelkaj datumoj koditaj en la adreso %1 estas tro mallongaj. Povus esti ke io en la programo de via konato malfunkcias. - + Error: Some data encoded in the address %1 is too long. There might be something wrong with the software of your acquaintance. - + Eraro: Kelkaj datumoj koditaj en la adreso %1 estas tro longaj. Povus esti ke io en la programo de via konato malfunkcias. - + Error: Some data encoded in the address %1 is malformed. There might be something wrong with the software of your acquaintance. - + Eraro: Kelkaj datumoj koditaj en la adreso %1 estas misformitaj. Povus esti ke io en la programo de via konato malfunkcias. - + Error: Something is wrong with the address %1. - + Eraro: Io malĝustas kun la adreso %1. - + 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. + Priaboranta adreson %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. + Priaboranta adreson %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'. + Dekstre alklaku kelka(j)n ero(j)n en via adresaro kaj elektu 'Sendi mesaĝon al tiu adreso'. - + Fetched address from namecoin identity. - Venigis adreson de namecoin-a identigo. + Venigis adreson de Namecoin-a identigo. - + New Message Nova mesaĝo - + From - + De - + Sending email gateway registration request - + Sendanta peton pri registrado je retpoŝta kluzo @@ -735,440 +662,440 @@ Estas grava, ke vi faru sekurkopion de tiu dosiero. Ĉu vi volas malfermi la dos La adreso kiun vi enmetis estas malĝusta. Ignoras ĝin. - + Error: You cannot add the same address to your address book twice. Try renaming the existing one if you want. - Eraro: Vi ne povas duoble aldoni la saman adreson al via adresaro. Provu renomi la ekzistan se vi volas. + Eraro: Vi ne povas duoble aldoni la saman adreson al via adresaro. Provu renomi la jaman 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. + Eraro: Vi ne povas aldoni duoble la saman adreson al viaj abonoj. Eble renomi la jaman 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. + Bitmesaĝo uzos vian prokurilon (proxy) ekde nun sed eble vi volas permane restartigi Bitmesaĝon nun por ke ĝi fermu eblajn jamajn 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. - + Sending email gateway unregistration request - + Sendanta peton pri malregistrado de retpoŝta kluzo - + Sending email gateway status request - + Sendanta peton pri stato de retpoŝta kluzo - + Passphrase mismatch Pasfrazoj malsamas - + The passphrase you entered twice doesn't match. Try again. - Entajpitaj pasfrazoj malsamas. Provu denove. + La pasfrazo kiun vi duoble enmetis 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. - + Entry added to the Address Book. Edit the label to your liking. - + Aldonis elementon al adresaro. Redaktu la etikedon laŭvole. - + Entry added to the blacklist. Edit the label to your liking. - Aldonis elementon al la 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. + Malforviŝis elementon. - + Save As... - Konservi kiel… + Konservi kiel... - + Write error. Skriberaro. - + No addresses selected. Neniu adreso elektita. - + If you delete the subscription, messages that you already received will become inaccessible. Maybe you can consider disabling the subscription instead. Disabled subscriptions will not receive new messages, but you can still view messages you already received. Are you sure you want to delete the subscription? - Se vi forigos abonon, mesaĝojn kiujn vi jam ricevis, igos neatingeblajn. Eble vi devus anstataŭ malaktivigi abonon. Malaktivaj abonoj ne ricevos novajn mesaĝojn, tamen vi plu povos legi mesaĝojn kiujn ci jam ricevis. + Se vi forviŝos abonon, mesaĝojn kiujn vi jam ricevis igos neatingeblajn. Eble vi devus anstataŭ malaktivigi abonon. Malaktivaj abonoj ne ricevos novajn mesaĝojn, tamen vi plu povos legi mesaĝojn kiujn ci jam ricevis. -Ĉu vi certe volas forigi la abonon? +Ĉu vi certe volas forviŝi la abonon? - + If you delete the channel, messages that you already received will become inaccessible. Maybe you can consider disabling the channel instead. Disabled channels will not receive new messages, but you can still view messages you already received. Are you sure you want to delete the channel? - Se vi forigos kanalon, mesaĝojn kiujn vi jam ricevis, igos neatingeblajn. Eble vi devus anstataŭ malaktivigi kanalon. Malaktivaj kanaloj ne ricevos novajn mesaĝojn, tamen vi plu povos legi mesaĝojn kiujn ci jam ricevis. + Se vi forviŝos kanalon, mesaĝojn kiujn vi jam ricevis igos neatingeblajn. Eble vi devus anstataŭ malaktivigi kanalon. Malaktivaj kanaloj ne ricevos novajn mesaĝojn, tamen vi plu povos legi mesaĝojn kiujn ci jam ricevis. -Ĉu vi certe volas forigi la kanalon? +Ĉu vi certe volas forviŝi la kanalon? - + Do you really want to remove this avatar? - Ĉu vi certe volas forviŝi tiun ĉi avataron? + Ĉu vi certe volas forviŝi tiun 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? + Vi jam agordis avataron por tiu 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… + Testante... - + This is a chan address. You cannot use it as a pseudo-mailing list. - + Tio estas kanaladreso. Vi ne povas ĝin uzi kiel kvazaŭ-dissendolisto. - + The address should start with ''BM-'' - La adreso komencu kun “BM-” + La adreso komencu kun "BM-" - + The address is not typed or copied correctly (the checksum failed). - La adreso ne estis ĝuste tajpita aŭ kopiita (kontrolsumo malsukcesis). + La adreso ne estis prave 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. + La numero de adresversio estas pli alta ol tiun, 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. + Kelkaj datumoj kodita en la adreso estas tro mallongaj. - + Some data encoded in the address is too long. - Iuj datumoj koditaj en la adreso estas tro longaj. + Kelkaj datumoj kodita en la adreso estas tro longaj. - + Some data encoded in the address is malformed. - Iuj datumoj koditaj en la adreso estas misformitaj. + Kelkaj datumoj koditaj en la adreso estas misformitaj. - + Enter an address above. - + Enmetu adreson supre. - + Address is an old type. We cannot display its past broadcasts. - Malnova tipo de adreso. Ne povas montri ĝiajn antaŭajn elsendojn. + Malnova speco 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. + Neniaj lastatempaj elsendoj de tiu adreso por montri. - + You are using TCP port %1. (This can be changed in the settings). - + Vi estas uzanta TCP pordo %1 (Tio estas ŝanĝebla en la agordoj). - + Bitmessage Bitmesaĝo - + Identities Identigoj - + New Identity Nova identigo - + Search Serĉi - + All Ĉio - + To Al - + From De - + Subject Temo - + Message Mesaĝo - + Received Ricevita je - + Messages Mesaĝoj - + Address book - Etikedo + Adresaro - + Address Adreso - + Add Contact Aldoni kontakton - + Fetch Namecoin ID - Venigi Namecoin ID + Venigu 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 + Regeneri determinisman 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 @@ -1183,709 +1110,387 @@ Are you sure you want to delete the channel? Aldoni novan elementon - + Display the %1 recent broadcast(s) from this address. - + Montri la %1 lasta(j)n elsendo(j)n de tiu adreso. - + 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% + Atendanta ĝis laborpruvo finos... %1% - + Shutting down Pybitmessage... %1% - Fermado de PyBitmessage… %1% + Fermanta na PyBitmessage... %1% - + Waiting for objects to be sent... %1% - Atendado ĝis objektoj estos senditaj… %1% + Atendanta ĝis objektoj estos senditaj... %1% - + Saving settings... %1% - Konservado de agordoj… %1% + Konservanta agordojn... %1% - + Shutting down core... %1% - Fermado de kerno… %1% + Fermanta kernon... %1% - + Stopping notifications... %1% - Haltigado de sciigoj… %1% + Haltiganta sciigojn... %1% - + Shutdown imminent... %1% - Fermado tuj… %1% + Fermanta tuj... %1% - + %n hour(s) %n horo%n horoj - + %n day(s) %n tago%n tagoj - + Shutting down PyBitmessage... %1% - Fermado de PyBitmessage… %1% + Fermanta na PyBitmessage... %1% - + Sent Senditaj - + Generating one new address - Kreado de unu nova adreso + Kreanta unu novan adreson - + Done generating address. Doing work necessary to broadcast it... - Adreso kreita. Kalkulado de laborpruvo, kiu endas por elsendi ĝin… + Adreso kreita. Faranta laborpruvon endan por elsendi ĝin... - + Generating %1 new addresses. - Kreado de %1 novaj adresoj. + Kreanta %1 novajn adresojn. - + %1 is already in 'Your Identities'. Not adding it again. - %1 jam estas en ‘Viaj Identigoj’. Ĝi ne estos aldonita ree. + %1 jam estas en 'Viaj Identigoj'. Ĝi ne estos aldonita ree. - + Done generating address Ĉiuj adresoj estas kreitaj - + SOCKS5 Authentication problem: %1 - + SOCKS5 eraro kun aŭtentigado: %1 - + 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… + Faranta laborpruvon endan 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. + Sendanta peton pri ĉifroŝlosilo de ricevonto. - + Looking up the receiver's public key - Serĉado de publika ĉifroŝlosilo de ricevonto + Serĉanta publikan ĉifroŝlosilon 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 + 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. + Faranta laborpruvon endan 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. + Faranta laborpruvon endan 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. + Faranta laborpruvon endan por sendi mesaĝon. - + Message sent. Waiting for acknowledgement. Sent on %1 - Mesaĝo sendita. Atendado je konfirmo. Sendita je %1 + Mesaĝo sendita. Atendante konfirmon. Sendita je %1 - + Doing work necessary to request encryption key. - Kalkulado de laborpruvo, kiu endas por peti pri ĉifroŝlosilo. + Faranta laborpruvon endan 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. + Elsendanta peton 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 + Sendanta peton pri publika ĉifroŝlosilo. Atendanta respondon. Petis je %1 - + UPnP port mapping established on port %1 - UPnP pord-mapigo farita je pordo %1 + UPnP pordo-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? - - - - 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 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 - * send broadcast messages like twitter or - * discuss in chan(nel)s with other people - - -Bonvenon al facila kaj sekura Bitmesaĝo -* sendi mesaĝojn al aliaj homoj -* sendi elsendajn mesaĝojn (kiel per Tvitero) -* babili kun aliaj uloj en mesaĝ-kanaloj - - - - not recommended for chans - malkonsilinda por kanaloj - - - - Quiet Mode - Silenta reĝimo - - - - Problems connecting? Try enabling UPnP in the Network Settings - Ĉu problemo kun konektado? Provu aktivigi UPnP en retaj agordoj. - - - - You are trying to send an email instead of a bitmessage. This requires registering with a gateway. Attempt to register? - Vi provas sendi retmesaĝon anstataŭ bitmesaĝ-mesaĝon. Tio ĉi postulas registriĝi ĉe retpoŝta kluzo. Ĉu provi registriĝi? - - - - 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 - - - - 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 + UPnP pordo-mapigo forigita NewAddressDialog - + Create new Address Krei novan adreson - + Here you may generate as many addresses as you like. Indeed, creating and abandoning addresses is encouraged. You may generate addresses by using either random numbers or by using a passphrase. If you use a passphrase, the address is called a "deterministic" address. The 'Random Number' option is selected by default but deterministic addresses have several pros and cons: - Tie ĉi vi povas generi tiom da adresoj, kiom vi volas. Ververe krei kaj forlasi adresojn estas konsilinda. Vi povas krei adresojn uzante hazardajn nombrojn aŭ pasfrazon. Se vi uzos pasfrazon, la adreso estas nomita kiel “antaŭkalkulebla” (determinisma) adreso. -La “hazardnombra” adreso estas antaŭagordita, sed antaŭkalkuleblaj adresoj havas kelkajn bonaĵojn kaj malbonaĵojn: + Tie ĉi vi povas generi tiom adresojn, kiom vi volas. Ververe kreado kaj forlasado de adresoj estas konsilinda. Vi povas krei adresojn uzante hazardajn nombrojn aŭ pasfrazon. Se vi uzos pasfrazon, la adreso estas nomita kiel 'determinisma' adreso. +La 'hazardnombra' adreso estas antaŭagordita, sed determinismaj adresoj havas kelkajn bonaĵojn kaj malbonaĵojn: - + <html><head/><body><p><span style=" font-weight:600;">Pros:<br/></span>You can recreate your addresses on any computer from memory. <br/>You need-not worry about backing up your keys.dat file as long as you can remember your passphrase. <br/><span style=" font-weight:600;">Cons:<br/></span>You must remember (or write down) your passphrase if you expect to be able to recreate your keys if they are lost. <br/>You must remember the address version number and the stream number along with your passphrase. <br/>If you choose a weak passphrase and someone on the Internet can brute-force it, they can read your messages and send messages as you.</p></body></html> - <html><head/><body><p><span style=" font-weight:600;">Bonaĵoj:<br/></span>Vi poveblas rekrei viajn adresojn per iu ajn komputilo elkape.<br/>Vi ne devas klopodi fari sekurkopion de la dosiero keys.dat tiel longe, dum vi memoras vian pasfrazon.<br/><span style=" font-weight:600;">Malbonaĵoj:<br/></span>Vi devas memori (aŭ konservi) vian pasfrazon se vi volas rekrei viajn ŝlosilojn kiam vi perdos ilin.<br/>Vi devas memori nombron de adresversio kaj de fluo kune kun vian pasfrazon.<br/>Se vi elektos malfortan pasfrazon kaj iu ajn Interrete eblos brutforti ĝin, li povos legi ĉiujn viajn mesaĝojn kaj sendi mesaĝojn kiel vi.</p></body></html> + <html><head/><body><p><span style=" font-weight:600;">Bonaĵoj:<br/></span>Vi poveblas rekrei viajn adresojn per iu ajn komputilo elkape.<br/>Vi ne devas klopodi fari savkopion de keys.dat dosiero tiel longe dum vi memoras vian pasfrazon.<br/><span style=" font-weight:600;">Malbonaĵoj:<br/></span>Vi devas memori (aŭ konservi) vian pasfrazon se vi volas rekrei viajn ŝlosilojn kiam vi perdos ilin.<br/>Vi devas memori nombro de adresversio kaj de fluo kune kun vian pasfrazon.<br/>Se vi elektos malfortan pasfrazon kaj iu ajn Interrete eblos brutforti ĝin, li povos legi ĉiujn viajn mesaĝojn kaj sendi mesaĝojn kiel vi.</p></body></html> - + Use a random number generator to make an address Uzi hazardnombran generilon por krei adreson - + Use a passphrase to make addresses Uzi pasfrazon por krei adreson - + Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter - Pasigi kelkajn minutojn aldone kompute por krei adreso(j)n mallongaj je 1 aŭ 2 signoj + Pasigi kelkajn minutojn aldone kompute por fari la adreso(j)n 1 aŭ 2 signoj pli mallongaj - + Make deterministic addresses - Fari antaŭkalkuleblan adreson + Fari determinisman adreson - + Address version number: 4 Numero de adresversio: 4 - + In addition to your passphrase, you must remember these numbers: Kune kun vian pasfrazon vi devas memori jenajn numerojn: - + Passphrase Pasfrazo - + Number of addresses to make based on your passphrase: - Nombro da farotaj adresoj bazante sur via pasfrazo: + Kvanto de farotaj adresoj bazante sur via pasfrazo: - + Stream number: 1 - Numero de fluo: 1 + Fluo numero: 1 - + Retype passphrase - Pasfrazo ree + Reenmeti pasfrazon - + Randomly generate address Hazardnombra adreso - + Label (not shown to anyone except you) Etikedo (ne montrata al iu ajn escepte de vi) - + Use the most available stream Uzi la plej disponeblan fluon - + (best if this is the first of many addresses you will create) - (plej bone se ĝi estas la unua de ĉiuj adresojn vi kreos) + (plej bone se tiun estas la unuan de ĉiuj adresojn vi kreos) - + Use the same stream as an existing address - Uzi la saman fluon kiel ekzistan adreson + Uzi saman fluon kiel jama adreso - + (saves you some bandwidth and processing power) - (konservas iomete da ret-trafiko kaj komput-povo) + (konservas iomete rettrafikon kaj komputopovon) NewSubscriptionDialog - + Add new entry Aldoni novan elementon - + Label Etikedo - + Address Adreso - + Enter an address above. - Entajpu adreson supre. + Enmetu adreson supre. SpecialAddressBehaviorDialog - + Special Address Behavior Speciala sinteno de adreso - + Behave as a normal address Sintenadi kiel normala adreso - + Behave as a pseudo-mailing-list address Sintenadi kiel kvazaŭ-elsendlista adreso - + Mail received to a pseudo-mailing-list address will be automatically broadcast to subscribers (and thus will be public). Mesaĝoj alvenintaj al kvazaŭ-dissendlisto estos aŭtomate dissenditaj al abonantoj (do ili estos publikaj). - + Name of the pseudo-mailing-list: - Nomo de 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. + Nomo de la kvazaŭ-dissendlisto: aboutDialog - + About Pri - + 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>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. + versio ? - <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 The Bitmessage Developers</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> + <html><head/><body><p>Distribuita sub la permesilo "MIT/X11 software license"; vidu <a href="http://www.opensource.org/licenses/mit-license.php"><span style=" text-decoration: underline; color:#0000ff;">http://www.opensource.org/licenses/mit-license.php</span></a></p></body></html> - - - <html><head/><body><p>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> + + + This is Beta software. + Tio estas beta-eldono. + + + + <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 The Bitmessage Developers</p></body></html> + <html><head/><body><p>Aŭtorrajto © 2012-2016 Jonathan Warren<br/>Aŭtorrajto © 2013-2016 La Programistoj de Bitmesaĝo</p></body></html> @@ -1893,12 +1498,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) @@ -1918,56 +1523,51 @@ La “hazardnombra” adreso estas antaŭagordita, sed antaŭkalkuleblaj adresoj Blacklist - Blokataj kontaktoj + Nigra Listo Whitelist - Permesataj kontaktoj + Blanka Listo connectDialog - + Bitmessage Bitmesaĝo - + Bitmessage won't connect to anyone until you let it. Bitmesaĝo ne konektos antaŭ vi permesos al ĝi. - + Connect now Konekti nun - + Let me configure special network settings first - Ebligi al mi unue alĝustigi specialajn agordojn de reto - - - - Work offline - Funkcii eksterrete + Lasu min unue fari specialajn retajn agordojn helpDialog - + Help Helpo - + <a href="https://bitmessage.org/wiki/PyBitmessage_Help">https://bitmessage.org/wiki/PyBitmessage_Help</a> <a href="https://bitmessage.org/wiki/PyBitmessage_Help">https://bitmessage.org/wiki/PyBitmessage_Help</a> - + As Bitmessage is a collaborative project, help can be found online in the Bitmessage Wiki: Ĉar Bitmesaĝo estas kunlabora projekto, vi povas trovi helpon enrete ĉe la vikio de Bitmesaĝo: @@ -1975,35 +1575,30 @@ La “hazardnombra” adreso estas antaŭagordita, sed antaŭkalkuleblaj adresoj iconGlossaryDialog - + Icon Glossary Piktograma Glosaro - + You have no connections with other peers. - Vi havas neniujn konektojn al aliaj samtavolanoj. + Vi havas neniajn konektojn al aliaj samtavolanoj. - + You have made at least one connection to a peer using an outgoing connection but you have not yet received any incoming connections. Your firewall or home router probably isn't configured to forward incoming TCP connections to your computer. Bitmessage will work just fine but it would help the Bitmessage network if you allowed for incoming connections and will help you be a better-connected node. - Vi konektis almenaŭ al unu samtavolano uzante elirantan konekton, sed vi ankoraŭ ne ricevis enirantajn konektojn. Via fajroŝirmilo (firewall) aŭ hejma enkursigilo (router) verŝajne estas agordita por ne plusendi enirantajn TCP-konektojn al via komputilo. Bitmesaĝo funkcios sufiĉe bone, sed helpus al la bitmesaĝa reto se vi permesus enirantajn konektojn kaj tiel estus pli bone konektita nodo. + Vi konektis almenaŭ al unu samtavolano uzante eliranta konekto, sed vi ankoraŭ ne ricevis enirantajn konetkojn. Via fajroŝirmilo (firewall) aŭ hejma enkursigilo (router) verŝajne estas agordita ne plusendi enirantajn TCP konektojn al via komputilo. Bitmesaĝo funkcios sufiĉe bone sed helpus al la Bitmesaĝa reto se vi permesus enirantajn konektojn kaj tiel estus pli bone konektita nodo. You are using TCP port ?. (This can be changed in the settings). - + Ĉu vi estas uzanta TCP pordon ?. (Tio estas ŝanĝebla en la agordoj). - + You do have connections with other peers and your firewall is correctly configured. Vi havas konektojn al aliaj samtavolanoj kaj via fajroŝirmilo estas ĝuste agordita. - - - You are using TCP port %1. (This can be changed in the settings). - Vi uzas TCP-pordon %1 (tio ĉi estas ŝanĝebla en la agordoj). - networkstatus @@ -2013,625 +1608,499 @@ La “hazardnombra” adreso estas antaŭagordita, sed antaŭkalkuleblaj adresoj Ĉiuj konektoj: - + Since startup: Ekde starto: - + Processed 0 person-to-person messages. Pritraktis 0 inter-personajn mesaĝojn. - + Processed 0 public keys. Pritraktis 0 publikajn ŝlosilojn. - + Processed 0 broadcasts. Pritraktis 0 elsendojn. - + Inventory lookups per second: 0 Petoj pri inventaro en sekundo: 0 - + Objects to be synced: Samtempigotaj eroj: - + Stream # Fluo # Connections - + Konektoj - + Since startup on %1 Ekde lanĉo de la programo je %1 - + Down: %1/s Total: %2 - Elŝuto: %1/s Sume: %2 + Elŝuto: %1/s Entute: %2 - + Up: %1/s Total: %2 - Alŝuto: %1/s Sume: %2 + Alŝuto: %1/s Entute: %2 - + Total Connections: %1 Ĉiuj konektoj: %1 - + Inventory lookups per second: %1 Petoj pri inventaro en sekundo: %1 - + Up: 0 kB/s Alŝuto: 0 kB/s - + Down: 0 kB/s Elŝuto: 0 kB/s - + Network Status - 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. - - - Peer - Samtavolano - - - - IP address or hostname - IP-adreso aŭ gastiga nomo - - - - Rating - Takso - - - - PyBitmessage tracks the success rate of connection attempts to individual nodes. The rating ranges from -1 to 1 and affects the likelihood of selecting the node in the future - PyBitmessage registras frekvencon de sukcesaj konekt-provoj al aliaj nodoj. Takso varias de -1 ĝis 1 kaj influas probablon por elekti nodon estontece. - - - - User agent - Klienta aplikaĵo - - - - Peer's self-reported software - Anoncata klienta aplikaĵo - - - - TLS - TLS - - - - Connection encryption - Konekta ĉifrado - - - - List of streams negotiated between you and the peer - Listo de interŝanĝataj fluoj inter vi kaj la samtavolano - newChanDialog Dialog - + Dialogo Create a new chan - + Krei novan kanalon Join a chan - + Aniĝi al kanalo Create a chan - + Krei kanalon <html><head/><body><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. If you and someone else both create a chan with the same chan name then it is currently very likely that they will be the same chan.</p></body></html> - + <html><head/><body><p>Enmetu nomon por via kanalo. Se vi elektas sufiĉe ampleksan kanalnomon (kiel fortan kaj unikan pasfrazon) kaj neniu el viaj amikoj komunikas ĝin publike, la kanalo estos sekura kaj privata. Se vi kaj iu ajn kreas kanalon kun la sama nomo, tiam ili iĝos tre verŝajne la saman kanalon.</p></body></html> Chan name: - + Nomo de kanalo: <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 normal person-to-person message to the chan address.</p><p>Chans are experimental and completely unmoderatable.</p></body></html> - + <html><head/><body><p>Kanalo ekzistas kiam grupo da personoj havas komunajn malĉifrajn ŝlosilojn. La ŝlosiloj kaj Bitmesaĝa adreso uzitaj de kanalo estas generitaj el hom-legebla vorto aŭ frazo (la nomo de la kanalo). Por sendi mesaĝon al ĉiuj en la kanalo, sendu normalan inter-personan mesaĝon al la adreso de la kanalo.</p><p>Kanaloj estas eksperimentaj kaj tute malkontroleblaj.</p></body></html> Chan bitmessage address: - - - - - Create or join a chan - Krei aŭ anigi kanalon - - - - <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>Kanalo ekzistas kiam grupo da personoj kunhavas la komunajn malĉifrajn ŝlosilojn. La ŝlosiloj kaj bitmesaĝa adreso uzataj de kanalo estas generitaj el hom-legebla vorto aŭ frazo (la kanala nomo). Por sendi mesaĝon al ĉiuj en la kanalo, sendu mesaĝon al la kanala adreso.</p><p>Kanaloj estas eksperimentaj kaj tute neadministreblaj.</p><p>Entajpu nomon por via kanalo. Se vi elektos sufiĉe ampleksan kanalan nomon (kiel fortan kaj unikan pasfrazon) kaj neniu de viaj amikoj komunikos ĝin publike, la kanalo estos sekura kaj privata. Tamen se vi kaj iu ajn kreos kanalon kun la sama nomo, tiam ili iĝos tre verŝajne la saman kanalon.</p></body></html> - - - - Chan passphrase/name: - Kanala pasfrazo/nomo: - - - - Optional, for advanced usage - Malnepra, por sperta uzado - - - - Chan address - Kanala adreso - - - - Please input chan name/passphrase: - Bonvolu entajpu kanalan nomon/pasfrazon: - - - - newchandialog - - - Successfully created / joined chan %1 - Sukcese kreis / anigis al la kanalo %1 - - - - Chan creation / joining failed - Kreado / aniĝado al kanalo malsukcesis - - - - Chan creation / joining cancelled - Kreado / aniĝado al kanalo nuligita - - - - 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. + Bitmesaĝa adreso de kanalo: regenerateAddressesDialog - + Regenerate Existing Addresses Regeneri ekzistantajn adresojn - + Regenerate existing addresses Regeneri ekzistantajn adresojn - + Passphrase Pasfrazo - + Number of addresses to make based on your passphrase: - Nombro da farotaj adresoj bazante sur via pasfrazo: + Kvanto de farotaj adresoj bazante sur via pasfrazo: - + Address version number: Numero de adresversio: - + Stream number: - Numero de fluo: + Fluo numero: - + 1 1 - + Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter - Pasigi kelkajn minutojn aldone kompute por krei adreso(j)n mallongaj je 1 aŭ 2 signoj + Pasigi kelkajn minutojn aldone kompute por krei la adreso(j)n 1 aŭ 2 signoj pli mallongaj - + You must check (or not check) this box just like you did (or didn't) when you made your addresses the first time. - Vi devas marki (aŭ ne marki) tiun ĉi mark-butonon samkiel vi faris kiam vi generis vian adresojn unuafoje. + Vi devas marki (aŭ ne marki) tiun markobutono samkiel vi faris kiam vi generis vian adreson unuafoje. - + If you have previously made deterministic addresses but lost them due to an accident (like hard drive failure), you can regenerate them here. If you used the random number generator to make your addresses then this form will be of no use to you. - Se vi antaŭe kreis antaŭkalkuleblajn (determinismajn) adresojn sed perdis ilin akcidente (ekz. en diska paneo), vi povas regeneri ilin tie ĉi. Se vi uzis la hazardnombran generilon por krei viajn adresojn, tiu ĉi formularo ne taŭgos por vi. + Se vi antaŭe kreis determinismajn adresojn sed perdis ilin akcidente (ekz. en diska paneo), vi povas regeneri ilin ĉi tie. Se vi uzis la generilo de hazardnombroj por krei vian adreson tiu formularo ne taŭgos por vi. 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 + Prokurila (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 + Aŭskulti pri alvenaj konektoj kiam dum uzado de prokurilo - + 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. + La 'Tuta malfacilaĵo' efikas sur la tuta kvalito da laboro kiu la sendonto devos fari. Duobligo de tiu valoro, duobligas la kvanton de laboro. - + 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 kiuj vi generos per ŝanĝo de ĉi-tiaj valoroj. Ĉiuj novaj adresoj kreotaj de vi bezonos por ke sendontoj akceptu pli altan malfacilaĵon. Estas unu escepto: se vi aldonos kolegon al vi adresaro, Bitmesaĝo aŭtomate sciigos lin kiam vi sendos mesaĝon, ke li bezonos fari nur minimuman kvaliton da laboro: malfacilaĵo 1. - + 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 Hardware GPU acceleration (OpenCL) - + Aparatara GPU-a plirapidigo (OpenCL) - + <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> + <html><head/><body><p>Defaŭlte 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 eblas ŝ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] - - - - Hardware GPU acceleration (OpenCL): - Aparatara GPU-a plirapidigo (OpenCL): - \ No newline at end of file diff --git a/src/translations/bitmessage_fr.qm b/src/translations/bitmessage_fr.qm index 8cb08a3a..7ac6a5fb 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..43e25c2f 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,93 +20,64 @@ 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) : @@ -153,104 +124,103 @@ 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 + Nouveau @@ -273,12 +243,12 @@ Please type the desired email address (including @mailchuck.com) below: Copier l’adresse dans le presse-papier - + Special address behavior... Comportement spécial de l’adresse… - + Email gateway Passerelle de courriel @@ -288,137 +258,137 @@ Please type the desired email address (including @mailchuck.com) below: 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. Encryption key request queued. - + Demande de clé de chiffrement en attente. - + 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 Need to do work to send message. Work is queued. - + Travail nécessaire pour envoyer le message. Travail en attente. - + 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,139 +396,139 @@ 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. - + Chan name needed - + Nécessite le nom du canal + + + + You didn't enter a chan name. + Vous n’avez pas saisi de nom de canal + + + + Address already present + Adresse déjà présente + + + + Could not add chan because it appears to already be one of your identities. + Il n’a pas été possible d’ajouter le canal parce qu’il semble déjà être une de vos identités. + + + + Success + Succès + + + + Successfully created chan. To let others join your chan, give them the chan name and this Bitmessage address: %1. This address also appears in 'Your Identities'. + Canal créé avec succès. Pour laisser d’autres utilisateurs rejoindre votre canal, donnez leur le nom du canal et cette adresse Bitmessage : %1. Cette adresse apparaît aussi dans "Vos identités". + + + + Address too new + Adresse trop récente + + + + Although that Bitmessage address might be valid, its version number is too new for us to handle. Perhaps you need to upgrade Bitmessage. + Bien que l’adresse Bitmessage pourrait être valable, son numéro de version est trop récent pour pouvoir être traité. Peut-être que vous devez mettre à niveau Bitmessage. - You didn't enter a chan name. - - - - - Address already present - - - - - Could not add chan because it appears to already be one of your identities. - - - - - Success - - - - - Successfully created chan. To let others join your chan, give them the chan name and this Bitmessage address: %1. This address also appears in 'Your Identities'. - - - - - 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. - - - - Address invalid - + Adresse invalide + + + + That Bitmessage address is not valid. + Cette adresse Bitmessage n’est pas valide. - That Bitmessage address is not valid. - - - - Address does not match chan name - + L’adresse ne correspond pas au nom du canal + + + + Although the Bitmessage address you entered was valid, it doesn't match the chan name. + Bien que l’adresse Bitmessage que vous avez saisie soit valable, elle ne correspond pas au nom du canal - Although the Bitmessage address you entered was valid, it doesn't match the chan name. - - - - Successfully joined chan. - + Canal rejoint avec succès. - + 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,124 +537,124 @@ 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. - + Error: Bitmessage addresses start with BM- Please check %1 - + Erreur : Les adresses Bitmessage commencent avec BM- Merci de vérifier %1 - + Error: The address %1 is not typed or copied correctly. Please check it. - + Erreur : L’adresse %1 n’est pas correctement recopiée. Veuillez la vérifier. - + Error: The address %1 contains invalid characters. Please check it. - + Erreur : L’adresse %1 contient des caractères invalides. Veuillez la vérifier. - + Error: The address version in %1 is too high. Either you need to upgrade your Bitmessage software or your acquaintance is being clever. - + Erreur : La version de l’adresse %1 est trop grande. Pensez à mettre à jour Bitmessage. - + Error: Some data encoded in the address %1 is too short. There might be something wrong with the software of your acquaintance. - + Erreur : Certaines données encodées dans l’adresse %1 sont trop courtes. Il peut y avoir un problème avec le logiciel ou votre connaissance. - + Error: Some data encoded in the address %1 is too long. There might be something wrong with the software of your acquaintance. - + Erreur : Certaines données encodées dans l’adresse %1 sont trop longues. Il peut y avoir un problème avec le logiciel ou votre connaissance. - + Error: Some data encoded in the address %1 is malformed. There might be something wrong with the software of your acquaintance. - + Erreur : quelques données codées dans l’adresse %1 sont mal formées. Il pourrait y avoir un soucis avec le logiciel de votre connaissance. - + Error: Something is wrong with the address %1. - + Erreur : Problème avec l’adresse %1. - + 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 - + From - + De - + Sending email gateway registration request - + Envoi de la demande d’inscription de la passerelle de courriel @@ -697,142 +667,142 @@ Le destinataire doit l’obtenir avant ce temps. Si votre client Bitmessage ne r 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,287 +820,287 @@ 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 + Abonnement - + 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 - + Zoom level %1% Niveau de zoom %1% @@ -1145,614 +1115,300 @@ Are you sure you want to delete the channel? 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 - + SOCKS5 Authentication problem: %1 - + Problème d’authentification SOCKS5 : %1 - + Disk full Disque plein - + Alert: Your disk or data storage volume is full. Bitmessage will now exit. Alerte : votre disque ou le volume de stockage de données est plein. Bitmessage va maintenant se fermer. - + Error! Could not find sender address (your address) in the keys.dat file. Erreur ! Il n’a pas été possible de trouver l’adresse d’expéditeur (votre adresse) dans le fichier keys.dat. - + Doing work necessary to send broadcast... Travail en cours afin d’envoyer le message de diffusion… - + Broadcast sent on %1 Message de diffusion envoyé %1 - + Encryption key was requested earlier. La clé de chiffrement a été demandée plus tôt. - + Sending a request for the recipient's encryption key. Envoi d’une demande de la clé de chiffrement du destinataire. - + Looking up the receiver's public key Recherche de la clé publique du récepteur - + Problem: Destination is a mobile device who requests that the destination be included in the message but this is disallowed in your settings. %1 Problème : la destination est un dispositif mobile qui nécessite que la destination soit incluse dans le message mais ceci n’est pas autorisé dans vos paramètres. %1 - + Doing work necessary to send message. There is no required difficulty for version 2 addresses like this. Travail en cours afin d’envoyer le message. Il n’y a pas de difficulté requise pour les adresses version 2 comme celle-ci. - + Doing work necessary to send message. Receiver's required difficulty: %1 and %2 Travail en cours afin d’envoyer le message. Difficulté requise du destinataire : %1 et %2 - + Problem: The work demanded by the recipient (%1 and %2) is more difficult than you are willing to do. %3 Problème : Le travail demandé par le destinataire (%1 and %2) est plus difficile que ce que vous avez paramétré. %3 - + Problem: You are trying to send a message to yourself or a chan but your encryption key could not be found in the keys.dat file. Could not encrypt message. %1 Problème : Vous essayez d’envoyer un message à un canal ou à vous-même mais votre clef de chiffrement n’a pas été trouvée dans le fichier keys.dat. Le message ne peut pas être chiffré. %1 - + Doing work necessary to send message. Travail en cours afin d’envoyer le message. - + Message sent. Waiting for acknowledgement. Sent on %1 Message envoyé. En attente de l’accusé de réception. Envoyé %1 - + Doing work necessary to request encryption key. Travail en cours afin d’obtenir la clé de chiffrement. - + Broadcasting the public key request. This program will auto-retry if they are offline. Diffusion de la demande de clef publique. Ce programme réessaiera automatiquement si ils sont déconnectés. - + Sending public key request. Waiting for reply. Requested at %1 Envoi d’une demande de clef publique. En attente d’une réponse. Demandée à %1 - + UPnP port mapping established on port %1 Transfert de port UPnP établi sur le port %1 - + UPnP port mapping removed Transfert de port UPnP retiré - - - Mark all messages as read - Marquer tous les messages comme lus - - - - Are you sure you would like to mark all messages read? - Êtes-vous sûr(e) de vouloir marquer tous les messages comme lus ? - - - - Doing work necessary to send broadcast. - Travail en cours afin d’envoyer la diffusion. - - - - Proof of work pending - En attente de preuve de fonctionnement - - - - %n object(s) pending proof of work - %n objet en attente de preuve de fonctionnement%n objet(s) en attente de preuve de fonctionnement - - - - %n object(s) waiting to be distributed - %n objet en attente d'être distribué%n objet(s) en attente d'être distribués - - - - Wait until these tasks finish? - Attendre jusqu'à ce que ces tâches se terminent ? - - - - 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 - * send broadcast messages like twitter or - * discuss in chan(nel)s with other people - - -Bienvenue dans le facile et sécurisé Bitmessage -* envoyer des messages à d'autres personnes -* envoyer des messages par diffusion (broadcast) à la manière de Twitter ou -* discuter dans des canaux (channels) avec d'autres personnes - - - - - not recommended for chans - pas recommandé pour les canaux - - - - 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 - - - - 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 - 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 +1416,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,72 +1439,62 @@ 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 - + 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. - + <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 © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 The Bitmessage Developers</p></body></html> @@ -1892,45 +1538,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 +1579,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,314 +1612,203 @@ 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° + Flux # Connections - + Connexions - + Since startup on %1 Démarré depuis le %1 - + Down: %1/s Total: %2 Téléchargées : %1/s Total : %2 - + Up: %1/s Total: %2 Téléversées : %1/s Total : %2 - + Total Connections: %1 Total des connexions : %1 - + Inventory lookups per second: %1 Consultations d’inventaire par seconde : %1 - + Up: 0 kB/s Téléversement : 0 kO/s - + Down: 0 kB/s Téléchargement : 0 kO/s - + 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. + Traité %n message de diffusionTraité %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 - newChanDialog Dialog - + Dialogue Create a new chan - + Créer un nouveau canal Join a chan - + Rejoindre un canal Create a chan - + Créer un canal <html><head/><body><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. If you and someone else both create a chan with the same chan name then it is currently very likely that they will be the same chan.</p></body></html> - + <html><head/><body><p>Saisissez un nom pour votre canal. Si vous choisissez un nom de canal suffisamment complexe (comme l’est une phrase secrète forte et unique) et qu’aucun de vos amis ne le partage publiquement, alors le canal sera sécurisé et privé. Si vous-même et quelqu’un d’autre que vous créaient chacun un canal avec le même nom de canal, alors ce serait très probablement le même canal, actuellement.</p></body></html> Chan name: - + Nom du canal : <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 normal person-to-person message to the chan address.</p><p>Chans are experimental and completely unmoderatable.</p></body></html> - + <html><head/><body><p>Un canal existe lorsqu’un groupe de personnes partage les mêmes clés de déchiffrement. Les clés et l’adresse Bitmessage utilisées par un canal sont produites à partir d’un mot ou d’une phrase favorable à l’humain (le nom de canal). Pour envoyer un message à tout le monde dans le canal, envoyez un message de personne-à-personne normal vers cette adresse de canal.</p><p>Les canaux sont expérimentaux et complètement non modérées.</p></body></html> Chan bitmessage address: - - - - - Create or join a chan - Créer ou rejoindre un canal - - - - <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>Un canal existe lorsqu'un groupe de personnes partage les mêmes clés de décryptage. Les clés et l'adresse bitmessage utilisées par un canal sont produites à partir d'un mot ou d'une phrase faciles à mémoriser par des hommes. Pour envoyer un message à tout le monde dans le canal, envoyez un message à l'adresse du canal.</p><p>Les canaux sont expérimentaux et complètement sans modération.</p><p>Entrez un nom pour votre canal. Si vous le choisissez un nom de canal suffisamment complexe (comme par exemple une phrase mot de passe qui soit forte et unique) et qu'aucun de vos amis ne le partage publiquement, alors le canal sera sécurisé et privé. Cependant si vous et quelqu'un d'autre créez tous les deux un canal ayant le même nom, ces canaux identiques seront partagés.</p></body></html> - - - - Chan passphrase/name: - Phrase passe ou nom : - - - - Optional, for advanced usage - Facultatif, pour une utilisation avancée - - - - Chan address - Adresse de canal - - - - Please input chan name/passphrase: - Veuillez saisir le nom du canal ou la phrase mot de passe : - - - - newchandialog - - - Successfully created / joined chan %1 - Le canal %1 a été rejoint ou créé avec succès. - - - - Chan creation / joining failed - Échec lors de la création du canal ou de la tentative de le rejoindre - - - - Chan creation / joining cancelled - Annulation de la création du canal ou de la tentative de le rejoindre - - - - proofofwork - - - C PoW module built successfully. - Module PoW C construit avec succès. - - - - Failed to build C PoW module. Please build it manually. - Échec à construire le module PoW C. Veuillez le construire manuellement. - - - - C PoW module unavailable. Please build it. - Module PoW C non disponible. Veuillez le construire. + Adresse de canal Bitmessage : 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,310 +1816,295 @@ 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 Hardware GPU acceleration (OpenCL) - + Accélération GPU matérielle (OpenCL) - + <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) : - \ No newline at end of file diff --git a/src/translations/bitmessage_it.qm b/src/translations/bitmessage_it.qm index d38e68bc..f151a58d 100644 Binary files a/src/translations/bitmessage_it.qm and b/src/translations/bitmessage_it.qm differ diff --git a/src/translations/bitmessage_it.ts b/src/translations/bitmessage_it.ts index dbefce30..f6e39188 100644 --- a/src/translations/bitmessage_it.ts +++ b/src/translations/bitmessage_it.ts @@ -1,5 +1,4 @@ - - + AddAddressDialog @@ -59,12 +58,12 @@ EmailGatewayRegistrationDialog - + Registration failed: Registrazione fallita: - + The requested email address is not available, please try a new one. Fill out the new desired email address (including @mailchuck.com) below: L'email richiesto non è disponibile, si prega di provarne uno nuovo. Inserisci il nuovo indirizzo email desiderato (incluso @mailchuck.com) qui sotto: @@ -77,7 +76,7 @@ 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: - + @@ -168,52 +167,52 @@ Il gateway email non condurrà operazioni PGP a vostro nome. È possibile MainWindow - + Reply to sender Rispondi al mittente - + Reply to channel Rispondi sul canale - + Add sender to your Address Book Aggiungi mittente alla rubrica - + Add sender to your Blacklist Aggiungi mittente alla blacklist - + Move to Trash Sposta nel cestino - + Undelete Ripristina l'eliminato - + View HTML code as formatted text Vedi codice HTML come testo formattato - + Save message as... Salva messaggio con nome... - + Mark Unread Segna come non letto - + New Nuovo @@ -238,12 +237,12 @@ Il gateway email non condurrà operazioni PGP a vostro nome. È possibile Copia indirizzo negli appunti - + Special address behavior... Comportamento indirizzo speciale - + Email gateway Gateway di posta @@ -253,99 +252,99 @@ Il gateway email non condurrà operazioni PGP a vostro nome. È possibile Cancella - + Send message to this address Manda un messaggio a questo indirizzo - + Subscribe to this address Sottoscrivi questo indirizzo - + Add New Address Aggiungi Nuovo Indirizzo - + Copy destination address to clipboard Copia indirizzo di destinazione negli appunti - + Force send Forza invio - + One of your addresses, %1, is an old version 1 address. Version 1 addresses are no longer supported. May we delete it now? Uno dei tuoi indirizzi, %1, è un indirizzo vecchio versione 1. Gli indirizzi versione 1 non sono più supportati. Posso eliminarlo ora? Waiting for their encryption key. Will request it again soon. - In attesa della chiave di criptazione. Sarà presto richiesta successivamente. + Encryption key request queued. - Chiave di criptazione richiesta, in coda. + Queued. - In coda. + Message sent. Waiting for acknowledgement. Sent at %1 - + Message sent. Sent at %1 - Messaggio inviato. Inviato a %1 + Need to do work to send message. Work is queued. - + Acknowledgement of the message received %1 - + Broadcast queued. - + Broadcast on %1 - + Problem: The work demanded by the recipient is more difficult than you are willing to do. %1 - + Problem: The recipient's encryption key is no good. Could not encrypt message. %1 - + Forced difficulty override. Send should start soon. - + Unknown status: %1 %2 - + @@ -380,14 +379,14 @@ Il gateway email non condurrà operazioni PGP a vostro nome. È possibile 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. - + You may manage your keys by editing the keys.dat file stored in %1 It is important that you back up this file. - + @@ -397,14 +396,14 @@ It is important that you back up this file. 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.) - + 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.) - + @@ -419,67 +418,67 @@ It is important that you back up this file. Would you like to open the file now? 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. - + Your address version number must be either 3 or 4. - + Chan name needed - + You didn't enter a chan name. - + Address already present - + Could not add chan because it appears to already be one of your identities. - + Success - + Successfully created chan. To let others join your chan, give them the chan name and this Bitmessage address: %1. This address also appears in 'Your Identities'. - + Address too new - Indirizzo troppo nuovo + Although that Bitmessage address might be valid, its version number is too new for us to handle. Perhaps you need to upgrade Bitmessage. - + @@ -494,17 +493,17 @@ It is important that you back up this file. Would you like to open the file now? Address does not match chan name - + Although the Bitmessage address you entered was valid, it doesn't match the chan name. - + Successfully joined chan. - + @@ -527,7 +526,7 @@ It is important that you back up this file. Would you like to open the file now? 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. - + @@ -537,117 +536,117 @@ It is important that you back up this file. Would you like to open the file now? 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. - + 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. - + Error: Bitmessage addresses start with BM- Please check %1 - + Error: The address %1 is not typed or copied correctly. Please check it. - + Error: The address %1 contains invalid characters. Please check it. - + Error: The address version in %1 is too high. Either you need to upgrade your Bitmessage software or your acquaintance is being clever. - + Error: Some data encoded in the address %1 is too short. There might be something wrong with the software of your acquaintance. - + Error: Some data encoded in the address %1 is too long. There might be something wrong with the software of your acquaintance. - + Error: Some data encoded in the address %1 is malformed. There might be something wrong with the software of your acquaintance. - + Error: Something is wrong with the address %1. - + 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. - + Stream number - + Concerning the address %1, Bitmessage cannot handle stream numbers of %2. Perhaps upgrade Bitmessage to the latest 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. - + 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. - + - + New Message Nuovo Messaggio - + From Da - + Sending email gateway registration request - + @@ -657,246 +656,246 @@ It is important that you back up this file. Would you like to open the file now? 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 Riavvia - + 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 Numero richiesto - + 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? - + - + Address disabled Indirizzo disabilitato - + 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... Salva come... - + Write error. Errore di scrittura. - + No addresses selected. Nessun indirizzo selezionato. - + 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... + - Testing... - - - - This is a chan address. You cannot use it as a pseudo-mailing list. - + - + The address should start with ''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). - + @@ -921,7 +920,7 @@ Are you sure you want to delete the channel? All - + @@ -956,7 +955,7 @@ Are you sure you want to delete the channel? Address book - + @@ -971,7 +970,7 @@ Are you sure you want to delete the channel? Fetch Namecoin ID - + @@ -991,12 +990,12 @@ Are you sure you want to delete the channel? Send ordinary Message - + Send Message to your Subscribers - + @@ -1006,22 +1005,22 @@ Are you sure you want to delete the channel? Subscriptions - + Add new Subscription - + Chans - + Add Chan - + @@ -1036,7 +1035,7 @@ Are you sure you want to delete the channel? Help - Aiuto + @@ -1061,27 +1060,27 @@ Are you sure you want to delete the channel? Contact support - + About - + Regenerate deterministic addresses - + Delete all trashed messages - + Join / Create chan - + @@ -1091,12 +1090,12 @@ Are you sure you want to delete the channel? Zoom level %1% - + Error: You cannot add the same address to your list twice. Perhaps rename the existing one if you want. - + @@ -1104,217 +1103,201 @@ Are you sure you want to delete the channel? Aggiungi una nuova voce - + Display the %1 recent broadcast(s) from this address. - + New version of PyBitmessage is available: %1. Download it from https://github.com/Bitmessage/PyBitmessage/releases/latest - + - + Waiting for PoW to finish... %1% - + - + Shutting down Pybitmessage... %1% - + - + Waiting for objects to be sent... %1% - + - + Saving settings... %1% - + - + Shutting down core... %1% - + - + Stopping notifications... %1% - + - + Shutdown imminent... %1% - + %n hour(s) - - - - + - + %n day(s) - - - - + - + Shutting down PyBitmessage... %1% - + Sent - + Generating one new address - + Done generating address. Doing work necessary to broadcast it... - + Generating %1 new addresses. - + %1 is already in 'Your Identities'. Not adding it again. - + Done generating address - + - + SOCKS5 Authentication problem: %1 - + - + 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. - + Doing work necessary to send broadcast... - + Broadcast sent on %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 - + Doing work necessary to send message. There is no required difficulty for version 2 addresses like this. - + Doing work necessary to send message. Receiver's required difficulty: %1 and %2 - + Problem: The work demanded by the recipient (%1 and %2) is more difficult than you are willing to do. %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 - + Doing work necessary to send message. - + Message sent. Waiting for acknowledgement. Sent on %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 - + - + UPnP port mapping established on port %1 - + - + UPnP port mapping removed - - - - - Mark all messages as read - - - - - Are you sure you would like to mark all messages read? - + @@ -1328,92 +1311,92 @@ Receiver's required difficulty: %1 and %2 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> - + 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 - + Make deterministic addresses - + Address version number: 4 - + In addition to your passphrase, you must remember these numbers: - + Passphrase - + Number of addresses to make based on your passphrase: - + Stream number: 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) - + @@ -1436,7 +1419,7 @@ The 'Random Number' option is selected by default but deterministic ad Enter an address above. - + @@ -1444,64 +1427,60 @@ The 'Random Number' option is selected by default but deterministic ad 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: - - - - - Ui_aboutDialog - - - aboutDialog - <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 The Bitmessage Developers</p></body></html> - + aboutDialog - + About - + - + PyBitmessage PyBitmessage - + version ? versione ? - + <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> - + - + This is Beta software. - + + + + + <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 The Bitmessage Developers</p></body></html> + @@ -1509,12 +1488,12 @@ The 'Random Number' option is selected by default but deterministic ad Use a Blacklist (Allow all incoming messages except those on the Blacklist) - + Use a Whitelist (Block all incoming messages except those on the Whitelist) - + @@ -1524,7 +1503,7 @@ The 'Random Number' option is selected by default but deterministic ad Name or Label - Nome o Etichetta + @@ -1534,12 +1513,12 @@ The 'Random Number' option is selected by default but deterministic ad Blacklist - + Whitelist - + @@ -1552,17 +1531,17 @@ The 'Random Number' option is selected by default but deterministic ad Bitmessage won't connect to anyone until you let it. - + Connect now - Connetti ora + Let me configure special network settings first - + @@ -1570,17 +1549,17 @@ The 'Random Number' option is selected by default but deterministic ad Help - Aiuto + <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: - + @@ -1588,7 +1567,7 @@ The 'Random Number' option is selected by default but deterministic ad Icon Glossary - + @@ -1598,17 +1577,17 @@ The 'Random Number' option is selected by default but deterministic ad 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. - + 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. - + @@ -1641,7 +1620,7 @@ The 'Random Number' option is selected by default but deterministic ad Inventory lookups per second: 0 - + @@ -1651,7 +1630,7 @@ The 'Random Number' option is selected by default but deterministic ad Stream # - + @@ -1661,17 +1640,17 @@ The 'Random Number' option is selected by default but deterministic ad Since startup on %1 - + Down: %1/s Total: %2 - + Up: %1/s Total: %2 - + @@ -1681,62 +1660,47 @@ The 'Random Number' option is selected by default but deterministic ad Inventory lookups per second: %1 - + Up: 0 kB/s - + Down: 0 kB/s - + Network Status - Stato di Rete + byte(s) - - byte - byte - + Object(s) to be synced: %n - - - - + Processed %n person-to-person message(s). - - - - + Processed %n broadcast message(s). - - - - + Processed %n public key(s). - - - - + @@ -1749,37 +1713,37 @@ The 'Random Number' option is selected by default but deterministic ad Create a new chan - + Join a chan - + Create a chan - + <html><head/><body><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. If you and someone else both create a chan with the same chan name then it is currently very likely that they will be the same chan.</p></body></html> - + Chan name: - + <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 normal person-to-person message to the chan address.</p><p>Chans are experimental and completely unmoderatable.</p></body></html> - + Chan bitmessage address: - + @@ -1797,22 +1761,22 @@ The 'Random Number' option is selected by default but deterministic ad Passphrase - + Number of addresses to make based on your passphrase: - + Address version number: - + Stream number: - + @@ -1822,17 +1786,17 @@ The 'Random Number' option is selected by default but deterministic ad Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter - + 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. - + @@ -1845,83 +1809,83 @@ The 'Random Number' option is selected by default but deterministic ad 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 - Esegui in Modalità Portabile + 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. - + Willingly include unencrypted destination address when sending to a mobile device - + Use Identicons - + Reply below Quote - + Interface Language - + System Settings system - Impostazioni di sistema + User Interface - Interfaccia Utente + Listening port - Porta in ascolto + Listen for connections on port: - + @@ -1931,17 +1895,17 @@ The 'Random Number' option is selected by default but deterministic ad Bandwidth limit - Limite banda + Maximum download rate (kB/s): [0: unlimited] - + Maximum upload rate (kB/s): [0: unlimited] - + @@ -1951,12 +1915,12 @@ The 'Random Number' option is selected by default but deterministic ad Type: - + Server hostname: - + @@ -1966,7 +1930,7 @@ The 'Random Number' option is selected by default but deterministic ad Authentication - Autenticazione + @@ -1976,17 +1940,17 @@ The 'Random Number' option is selected by default but deterministic ad Pass: - + Listen for incoming connections when using proxy - + none - + @@ -2011,22 +1975,22 @@ The 'Random Number' option is selected by default but deterministic ad 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. - + 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. - + @@ -2036,7 +2000,7 @@ The 'Random Number' option is selected by default but deterministic ad 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. - + @@ -2046,7 +2010,7 @@ The 'Random Number' option is selected by default but deterministic ad Maximum acceptable small message difficulty: - + @@ -2056,12 +2020,12 @@ The 'Random Number' option is selected by default but deterministic ad Hardware GPU acceleration (OpenCL) - Accelerazione hardware GPU (OpenCL) + <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> - + @@ -2101,12 +2065,12 @@ The 'Random Number' option is selected by default but deterministic ad <html><head/><body><p>By default, if you send a message to someone and he is offline for more than two days, Bitmessage will send the message again after an additional two days. This will be continued with exponential backoff forever; messages will be resent after 5, 10, 20 days ect. until the receiver acknowledges them. Here you may change that behavior by having Bitmessage give up after a certain number of days or months.</p><p>Leave these input fields blank for the default behavior. </p></body></html> - + Give up after - + @@ -2126,7 +2090,7 @@ The 'Random Number' option is selected by default but deterministic ad Resends Expire - + - + \ No newline at end of file diff --git a/src/translations/bitmessage_ja.qm b/src/translations/bitmessage_ja.qm index 77fa63d1..10b1edeb 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..daaa0f85 100644 --- a/src/translations/bitmessage_ja.ts +++ b/src/translations/bitmessage_ja.ts @@ -2,17 +2,17 @@ AddAddressDialog - + Add new entry 新しい項目を追加 - + Label ラベル - + Address アドレス @@ -20,80 +20,50 @@ 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: @@ -155,138 +125,56 @@ Please type the desired email address (including @mailchuck.com) below: - - - # 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: local -# メールゲートウェイは、あなたに代わって PGP 操作を行いません。 -# PGP をまったく使用しないか、ローカルで使用することができます。 -# -# attachments: yes -# 受信メールの添付ファイルは MEGA.nz にアップロードされ、リンクをフォローして -# そこからダウンロードすることができます。 サブスクリプションが必要です。 -# -# attachments: no -# 添付ファイルは無視されます。 -# -# archive: yes -# 受信メールはサーバーにアーカイブされます。 問題のデバッグで支援が必要な場合や、 -# メールの第三者証明が必要な場合に、これを使用してください。 しかし、これは、 -# メールがあなたに配信された後でも、サービスの運営者があなたのメールを -# 読むことができるということを意味します。 -# -# archive: no -# 受信メールは、あなたに転送されるとすぐにサーバーから削除されます。 -# -# masterpubkey_btc: BIP44 xpub key または electrum v1 public seed -# offset_btc: 整数 (デフォルトは 0) -# feeamount: 小数点以下 8 桁までの数字 -# feecurrency: BTC, XBT, USD, EUR または GBP -# メールを送信した人に請求したい場合に、これらを使用します。 これがオンで、 -# 不明な人があなたにメールを送信した場合、指定された手数料を支払うように要求されます。 -# この方式は決定的な公開鍵を使用するため、直接お金を受け取ることになります。 -# もう一度オフにするには、「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 新規 @@ -303,7 +191,7 @@ Please type the desired email address (including @mailchuck.com) below: Set avatar... - アバターを設定... + @@ -311,14 +199,14 @@ Please type the desired email address (including @mailchuck.com) below: アドレスをコピー - + Special address behavior... アドレスの特別な動作 - + Email gateway - メールゲートウェイ + @@ -326,406 +214,399 @@ Please type the desired email address (including @mailchuck.com) below: 削除 - + Send message to this address このアドレスへ送信 - + Subscribe to this address このアドレスを購読 - + Add New Address アドレスを追加 - + Copy destination address to clipboard 宛先アドレスをコピー - + Force send 強制的に送信 - + One of your addresses, %1, is an old version 1 address. Version 1 addresses are no longer supported. May we delete it now? %1は古いバージョン1のアドレスです。バージョン1のアドレスはサポートが終了しています。すぐに削除しますか? - + Waiting for their encryption key. Will request it again soon. - 暗号鍵を待っています。 すぐにもう一度リクエストします。 + Encryption key request queued. - + 暗号鍵のリクエストはキューに入りました。 - + Queued. キューに入りました。 - + Message sent. Waiting for acknowledgement. Sent at %1 - メッセージを送信しました。 確認応答を待っています。 %1 で送信されました + - + Message sent. Sent at %1 メッセージは送信されました。送信先: %1 Need to do work to send message. Work is queued. - + 送信のために処理を行う必要があります。処理はキューに入りました。 - + Acknowledgement of the message received %1 メッセージの確認を受け取りました %1 - + Broadcast queued. - 配信がキューに入りました。 + Broadcastがキューに入りました。 - + Broadcast on %1 - 配信: %1 + Broadcast: %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 のどちらかにする必要があります。 + - + Chan name needed - + Chan名が必要です - + You didn't enter a chan name. - + chan名が入力されていません。 - + Address already present - + アドレスは既に表示されています - + Could not add chan because it appears to already be one of your identities. - + chanを追加できません。既にアドレス一覧に含まれています。 - + Success - - - - - Successfully created chan. To let others join your chan, give them the chan name and this Bitmessage address: %1. This address also appears in 'Your Identities'. - - - - - 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. - - - - - Address invalid - - - - - That Bitmessage address is not valid. - - - - - Address does not match chan name - - - - - Although the Bitmessage address you entered was valid, it doesn't match the chan name. - - - - - Successfully joined chan. - + 成功 + Successfully created chan. To let others join your chan, give them the chan name and this Bitmessage address: %1. This address also appears in 'Your Identities'. + chanの作成に成功しました。他の人がchanに参加できるようにするには、chanの名前とBitmessageアドレスを伝えてください: %1 アドレスは「アドレス一覧」に表示されます。 + + + + 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. + このBitmessageアドレスは正当ですが、バージョン番号が現在使用中の物より新しいです。Bitmessageをアップデートしてください。 + + + + Address invalid + アドレスが不正です + + + + That Bitmessage address is not valid. + このBitmessageアドレスは不正です。 + + + + Address does not match chan name + アドレスがchan名と一致しません + + + + Although the Bitmessage address you entered was valid, it doesn't match the chan name. + このBitmessageアドレスは正当ですが、chan名と一致していません。 + + + + Successfully joined chan. + chanに参加しました。 + + + 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-Live は、ネットワークがメッセージを保持する時間の長さです。 -受信者は、この時間の間に取得する必要があります。 Bitmessage クライアントが確認応答を受け取らないと、 -自動的にメッセージが再送信されます。 TTL(Time-To-Live)が長くなるほど、 -コンピュータがメッセージを送信するために必要な処理が増えます。 多くの場合 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 として登録を送信しています。送信を再試行する前に、登録が処理されるのをお待ちください。 + - + Error: Bitmessage addresses start with BM- Please check %1 - + エラー: Bitmessageアドレスは「BM-」で始まります。確認してください %1 - + Error: The address %1 is not typed or copied correctly. Please check it. - + エラー: アドレス %1 は正しく入力、またはコピーされていません。確認して下さい。 - + Error: The address %1 contains invalid characters. Please check it. - + エラー: アドレス %1 は不正な文字を含んでいます。確認して下さい。 - + Error: The address version in %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 address %1 is too short. There might be something wrong with the software of your acquaintance. - + エラー: アドレス %1 でエンコードされたデータが短すぎます。連絡先のソフトウェアが何かしら誤っている可能性があります。 - + Error: Some data encoded in the address %1 is too long. There might be something wrong with the software of your acquaintance. - + エラー: アドレス %1 でエンコードされたデータが長すぎます。連絡先のソフトウェアが何かしら誤っている可能性があります。 - + Error: Some data encoded in the address %1 is malformed. There might be something wrong with the software of your acquaintance. - + Error: Something is wrong with the address %1. - + エラー: アドレス %1 には何かしら誤りがあります。 - + 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 新規メッセージ - + From - + 送信元 - + Sending email gateway registration request @@ -740,1061 +621,742 @@ It is important that you back up this file. Would you like to open the file now? 入力されたアドレスは不正です。無視されました。 - + Error: You cannot add the same address to your address book twice. Try renaming the existing one if you want. エラー: 同じアドレスを複数アドレス帳に追加する事はできません。既存の項目をリネームしてください。 - + Error: You cannot add the same address to your subscriptions twice. Perhaps rename the existing one if you want. - エラー: 購読に、同じアドレスを2回追加することはできません。 必要に応じて、既存の名前を変更してください。 + - + Restart 再開 - + You must restart Bitmessage for the port number change to take effect. ポート番号の変更を有効にするにはBitmessageを再起動してください。 - + Bitmessage will use your proxy from now on but you may want to manually restart Bitmessage now to close existing connections (if any). プロキシの設定を有効にするには手動でBitmessageを再起動してください。既に接続がある場合は切断されます。 - + Number needed - 数字が必要です + - + Your maximum download and upload rate must be numbers. Ignoring what you typed. - 最大ダウンロード数とアップロード数は数字にする必要があります。 入力されたものを無視します。 + - + Will not resend ever - 今後再送信されません + - + Note that the time limit you entered is less than the amount of time Bitmessage waits for the first resend attempt therefore your messages will never be resent. - 入力した時間制限は、Bitmessageが最初の再送試行を待つ時間よりも短いため、メッセージは再送信されないことにご注意ください。 + - + Sending email gateway unregistration request - + Sending email gateway status request - + Passphrase mismatch パスフレーズが一致しません - + The passphrase you entered twice doesn't match. Try again. 再度入力されたパスフレーズが一致しません。再入力してください。 - + Choose a passphrase パスフレーズを選択してください - + You really do need a passphrase. パスフレーズが必要です。 - + Address is gone アドレスが無効になりました - + Bitmessage cannot find your address %1. Perhaps you removed it? アドレス %1 が見つかりません。既に削除していませんか? - + Address disabled アドレスが無効になりました - + Error: The address from which you are trying to send is disabled. You'll have to enable it on the 'Your Identities' tab before using it. エラー: 送信しようとしたアドレスは無効になっています。使用する前に「アドレス一覧」で有効にしてください。 - + Entry added to the Address Book. Edit the label to your liking. + アドレス帳に項目が追加されました。ラベルは自由に編集できます。 + + + + Entry added to the blacklist. Edit the label to your liking. - - 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? - 購読を削除すると、すでに受信したメッセージにアクセスできなくなります。 代わりに、購読を無効にすることもできます。 無効になった購読は新しいメッセージを受信しませんが、受信したメッセージは表示できます。 - -購読を削除してもよろしいですか? + - + 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. - ログイン時に開始は、まだお使いの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). - + + 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 - チャンネルに参加 / 作成 + 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. - + 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 - アドレスの生成を完了しました + - + SOCKS5 Authentication problem: %1 - + 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 + + + + + Doing work necessary to send message. +There is no required difficulty for version 2 addresses like this. + + + + + Doing work necessary to send message. +Receiver's required difficulty: %1 and %2 + + + + + Problem: The work demanded by the recipient (%1 and %2) is more difficult than you are willing to do. %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 + + + + + Doing work necessary to send message. + - 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? - これらのタスクが完了するまで待ちますか? - - - - 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 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 - * send broadcast messages like twitter or - * discuss in chan(nel)s with other people - - -簡単で安全な Bitmessage へようこそ -* 他の人にメッセージを送ります -* Twitter のようなブロードキャストメッセージを送信します -* 他の人と一緒にチャン(ネル)で議論します - - - - - not recommended for chans - チャンネルにはお勧めしません - - - - Quiet Mode - マナーモード - - - - Problems connecting? Try enabling UPnP in the Network Settings - 接続に問題がありますか? ネットワーク設定でUPnPを有効にしてみてください - - - - You are trying to send an email instead of a bitmessage. This requires registering with a gateway. Attempt to register? - Bitmessage の代わりにメールを送信しようとしています。 これは、ゲートウェイに登録する必要があります。 登録しますか? - - - - Error: Bitmessage addresses start with BM- Please check the recipient address %1 - エラー: BitmessageのアドレスはBM-で始まります。 受信者のアドレス %1 を確認してください - - - - Error: The recipient address %1 is not typed or copied correctly. Please check it. - エラー: 受信者のアドレス %1 は正しく入力、またはコピーされていません。確認して下さい。 - - - - Error: The recipient address %1 contains invalid characters. Please check it. - エラー: 受信者のアドレス %1 は不正な文字を含んでいます。確認して下さい。 - - - - Error: The version of the recipient address %1 is too high. Either you need to upgrade your Bitmessage software or your acquaintance is being clever. - エラー: 受信者アドレスのバージョン %1 は高すぎます。 Bitmessageソフトウェアをアップグレードする必要があるか、連絡先が賢明になっているかのいずれかです。 - - - - Error: Some data encoded in the recipient address %1 is too short. There might be something wrong with the software of your acquaintance. - エラー: アドレス %1 でエンコードされたデータが短すぎます。連絡先のソフトウェアが何かしら誤っている可能性があります。 - - - - Error: Some data encoded in the recipient address %1 is too long. There might be something wrong with the software of your acquaintance. - エラー: 受信者のアドレス %1 でエンコードされたデータが短すぎます。連絡先のソフトウェアが何かしら誤っている可能性があります。 - - - - Error: Some data encoded in the recipient address %1 is malformed. There might be something wrong with the software of your acquaintance. - エラー: 受信者のアドレス %1 でエンコードされたデータの一部が不正です。連絡先のソフトウェアが何かしら誤っている可能性があります。 - - - - Error: Something is wrong with the recipient address %1. - エラー: 受信者のアドレス %1 には何かしら誤りがあります。 - - - - Error: %1 - エラー: %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 のオブジェクトをダウンロードする必要があります。 今、終了すると、配信が遅れる可能性があります。 同期が完了するまで待ちますか? - - - - 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" はブラウザで開きます。 セキュリティリスクの可能性があります。匿名性がなくなったり、悪意のあるデータをダウンロードする可能性があります。 よろしいですか? - - - - HTML detected, click here to display - HTMLが検出されました。ここをクリックすると表示します - - - - Click here to disable HTML - ここをクリックするとHTMLを無効にします - - - - MsgDecode - - - The message has an unknown encoding. -Perhaps you should upgrade Bitmessage. - メッセージのエンコードが不明です。 -Bitmessageをアップグレードする必要があるかもしれません。 - - - - Unknown encoding - 不明なエンコード + 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,177 +1364,162 @@ The 'Random Number' option is selected by default but deterministic ad 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. - chanアドレスは仮想メーリングリストのアドレスには使用できません。 - aboutDialog - + About 概要 - + 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>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. このソフトウェアはベータ版です。 - + <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> - blacklist Use a Blacklist (Allow all incoming messages except those on the Blacklist) - ブラックリストを使用(全てのメッセージを受信してブラックリストと一致する物だけ除外) + Use a Whitelist (Block all incoming messages except those on the Whitelist) - ホワイトリストを使用(全てのメッセージを受信拒否してホワイトリストと一致する物だけ許可) + Add new entry - 新しい項目を追加 + Name or Label - 名前、ラベル + Address - アドレス + Blacklist - ブラックリスト + Whitelist - ホワイトリスト + 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,77 +1527,72 @@ 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 Total connections: - 接続数: + - + Since startup: - 起動日時: + + + + + Processed 0 person-to-person messages. + + + + + Processed 0 public keys. + + + + + Processed 0 broadcasts. + + + + + Inventory lookups per second: 0 + - 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,114 +1600,69 @@ 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 - あなたとピアの間でネゴシエーションしたストリームのリスト + @@ -2173,159 +1670,93 @@ The 'Random Number' option is selected by default but deterministic ad Dialog - + ダイアログ Create a new chan - + 新しいchanを作成 Join a chan - + chanに参加 Create a chan - + chanを作成 <html><head/><body><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. If you and someone else both create a chan with the same chan name then it is currently very likely that they will be the same chan.</p></body></html> - + <html><head/><body><p>chan名を入力してください。(強くて一意のパスフレーズのように)十分に複雑なchan名を選び、あなたの友人たちがそれを他人に教えない限りchanはセキュアでプライベートです。あなたと他の誰かが同じ名前のchanを作成した場合、現状では同じchanになる可能性が非常に高い。</p></body></html> Chan name: - + Chan名: <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 normal person-to-person message to the chan address.</p><p>Chans are experimental and completely unmoderatable.</p></body></html> - + <html><head/><body><p>chanは人々のグループに復号鍵を共有されることで存在します。chanで使われる鍵とBitmessageアドレスは読みやすい単語、またはフレーズ(chan名)から生成されます。chanに居る人たちへメッセージを送るには、通常の一対一のメッセージをchanアドレスへ送ります。</p><p>chansは実験的機能で、内容を管理するモデレーターを設けることはできません。</p></body></html> Chan bitmessage address: - - - - - Create or join a chan - チャンネルを作成または参加 - - - - <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> - - - - Chan passphrase/name: - チャンネルのパスフレーズ/名前: - - - - Optional, for advanced usage - オプション、高度な使い方 - - - - Chan address - チャンネル アドレス - - - - Please input chan name/passphrase: - チャンネル名/パスフレーズを入力してください: - - - - newchandialog - - - Successfully created / joined chan %1 - チャンネル %1 を正常に作成 / 参加しました - - - - Chan creation / joining failed - チャンネルの作成 / 参加に失敗しました - - - - Chan creation / joining cancelled - チャンネルの作成 / 参加をキャンセルしました - - - - proofofwork - - - C PoW module built successfully. - C PoW モジュールのビルドに成功しました。 - - - - Failed to build C PoW module. Please build it manually. - C PoW モジュールのビルドに失敗しました。手動でビルドしてください。 - - - - C PoW module unavailable. Please build it. - C PoW モジュールが利用できません。ビルドしてください。 + chanのbitmessageアドレス: 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 +1764,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,89 +1985,74 @@ 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): + \ No newline at end of file diff --git a/src/translations/bitmessage_nl.qm b/src/translations/bitmessage_nl.qm index 72a7ade2..28ee42c7 100644 Binary files a/src/translations/bitmessage_nl.qm and b/src/translations/bitmessage_nl.qm differ diff --git a/src/translations/bitmessage_nl.ts b/src/translations/bitmessage_nl.ts index 3e7c1640..5b870b0f 100644 --- a/src/translations/bitmessage_nl.ts +++ b/src/translations/bitmessage_nl.ts @@ -1,5 +1,4 @@ - - + AddAddressDialog @@ -23,27 +22,27 @@ Email gateway - E-mail gateway + Register on email gateway - + Account status at email gateway - + Change account settings at email gateway - + Unregister from email gateway - + @@ -53,25 +52,25 @@ Desired email address (including @mailchuck.com): - + EmailGatewayRegistrationDialog - + Registration failed: Registratie mislukt - + The requested email address is not available, please try a new one. Fill out the new desired email address (including @mailchuck.com) below: Het opgegeven e-mail adres is niet beschikbaar, probeer een ander adres. Vul hieronder het nieuwe adres (inclusief @mailchuck.com) in: Email gateway registration - + @@ -125,58 +124,58 @@ Voer het gewenste e-mail adres (inclusief @mailchuck.com) hieronder in: - + MainWindow - + Reply to sender Afzender beantwoordenjavascript:; - + Reply to channel - + - + Add sender to your Address Book Afzender aan uw adresboek toevoegen - + Add sender to your Blacklist Afzender blokkeren - + Move to Trash Verplaats naar Prullenbak - + Undelete - + - + View HTML code as formatted text Bekijk HTML als geformatteerde tekst - + Save message as... Bewaar bericht als - + Mark Unread Markeer Ongelezen - + New Nieuw @@ -201,12 +200,12 @@ Voer het gewenste e-mail adres (inclusief @mailchuck.com) hieronder in:Kopieer adres naar klembord - + Special address behavior... - + - + Email gateway E-mail gateway @@ -216,34 +215,34 @@ Voer het gewenste e-mail adres (inclusief @mailchuck.com) hieronder in:Verwijder - + Send message to this address Stuur bericht naar dit adres - + Subscribe to this address Abonneren op dit adres - + Add New Address Nieuw adres toevoegen - + Copy destination address to clipboard Kopieer bestemmingsadres naar klembord - + Force send Forceer zenden - + One of your addresses, %1, is an old version 1 address. Version 1 addresses are no longer supported. May we delete it now? - + @@ -273,7 +272,7 @@ Voer het gewenste e-mail adres (inclusief @mailchuck.com) hieronder in: Need to do work to send message. Work is queued. - + @@ -283,27 +282,27 @@ Voer het gewenste e-mail adres (inclusief @mailchuck.com) hieronder in: Broadcast queued. - + Broadcast on %1 - + Problem: The work demanded by the recipient is more difficult than you are willing to do. %1 - + Problem: The recipient's encryption key is no good. Could not encrypt message. %1 - + Forced difficulty override. Send should start soon. - + @@ -333,7 +332,7 @@ Voer het gewenste e-mail adres (inclusief @mailchuck.com) hieronder in: Channel - + @@ -343,14 +342,14 @@ Voer het gewenste e-mail adres (inclusief @mailchuck.com) hieronder in: 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. - + You may manage your keys by editing the keys.dat file stored in %1 It is important that you back up this file. - + @@ -360,14 +359,14 @@ It is important that you back up this file. 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.) - + 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.) - + @@ -377,7 +376,7 @@ It is important that you back up this file. Would you like to open the file now? Are you sure you want to delete all trashed messages? - + @@ -387,7 +386,7 @@ It is important that you back up this file. Would you like to open the file now? You must type your passphrase. If you don't have one then this is not the form for you. - + @@ -397,32 +396,32 @@ It is important that you back up this file. Would you like to open the file now? Your address version number must be a number: either 3 or 4. - + Your address version number must be either 3 or 4. - + Chan name needed - + You didn't enter a chan name. - + Address already present - + Could not add chan because it appears to already be one of your identities. - + @@ -432,7 +431,7 @@ It is important that you back up this file. Would you like to open the file now? Successfully created chan. To let others join your chan, give them the chan name and this Bitmessage address: %1. This address also appears in 'Your Identities'. - + @@ -442,7 +441,7 @@ It is important that you back up this file. Would you like to open the file now? Although that Bitmessage address might be valid, its version number is too new for us to handle. Perhaps you need to upgrade Bitmessage. - + @@ -457,17 +456,17 @@ It is important that you back up this file. Would you like to open the file now? Address does not match chan name - + Although the Bitmessage address you entered was valid, it doesn't match the chan name. - + Successfully joined chan. - + @@ -490,7 +489,7 @@ It is important that you back up this file. Would you like to open the file now? 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. - + @@ -500,57 +499,57 @@ It is important that you back up this file. Would you like to open the file now? 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. - + 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. - + Error: Bitmessage addresses start with BM- Please check %1 - + Error: The address %1 is not typed or copied correctly. Please check it. - + Error: The address %1 contains invalid characters. Please check it. - + Error: The address version in %1 is too high. Either you need to upgrade your Bitmessage software or your acquaintance is being clever. - + Error: Some data encoded in the address %1 is too short. There might be something wrong with the software of your acquaintance. - + Error: Some data encoded in the address %1 is too long. There might be something wrong with the software of your acquaintance. - + Error: Some data encoded in the address %1 is malformed. There might be something wrong with the software of your acquaintance. - + Error: Something is wrong with the address %1. - + Error: You must specify a From address. If you don't have one, go to the 'Your Identities' tab. - + @@ -560,57 +559,57 @@ It is important that you back up this file. Would you like to open the file now? Concerning the address %1, Bitmessage cannot understand address version numbers of %2. Perhaps upgrade Bitmessage to the latest version. - + Stream number - + Concerning the address %1, Bitmessage cannot handle stream numbers of %2. Perhaps upgrade Bitmessage to the latest 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. - + 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. - + - + New Message Nieuw Bericht - + From Van - + Sending email gateway registration request - + @@ -623,243 +622,243 @@ It is important that you back up this file. Would you like to open the file now? Het ingevoerd adres is ongeldig. Worden genegeerd. - + 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 Herstarten - + 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? - + - + 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... Opslaan als... - + Write error. Schrijffout. - + No addresses selected. Geen adressen geselecteerd. - + 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... Testen... - + This is a chan address. You cannot use it as a pseudo-mailing list. - + - + The address should start with ''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). - + @@ -869,12 +868,12 @@ Are you sure you want to delete the channel? Identities - + New Identity - + @@ -914,12 +913,12 @@ Are you sure you want to delete the channel? Messages - + Address book - + @@ -929,7 +928,7 @@ Are you sure you want to delete the channel? Add Contact - + @@ -954,37 +953,37 @@ Are you sure you want to delete the channel? Send ordinary Message - + Send Message to your Subscribers - + TTL: - + Subscriptions - + Add new Subscription - + Chans - + Add Chan - + @@ -1009,7 +1008,7 @@ Are you sure you want to delete the channel? Manage keys - + @@ -1024,7 +1023,7 @@ Are you sure you want to delete the channel? Contact support - + @@ -1034,250 +1033,234 @@ Are you sure you want to delete the channel? Regenerate deterministic addresses - + Delete all trashed messages - + Join / Create chan - + All accounts - + Zoom level %1% - + Error: You cannot add the same address to your list twice. Perhaps rename the existing one if you want. - + Add new entry - Nieuw adres toevoegen + - + Display the %1 recent broadcast(s) from this address. - + New version of PyBitmessage is available: %1. Download it from https://github.com/Bitmessage/PyBitmessage/releases/latest - + - + Waiting for PoW to finish... %1% - + - + Shutting down Pybitmessage... %1% - + - + Waiting for objects to be sent... %1% - + - + Saving settings... %1% - + - + Shutting down core... %1% - + - + Stopping notifications... %1% - + - + Shutdown imminent... %1% - + %n hour(s) - - - - + - + %n day(s) - - - - + - + Shutting down PyBitmessage... %1% - + Sent - + Generating one new address - + Done generating address. Doing work necessary to broadcast it... - + Generating %1 new addresses. - + %1 is already in 'Your Identities'. Not adding it again. - + Done generating address - + - + SOCKS5 Authentication problem: %1 - + - + 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. - + Doing work necessary to send broadcast... - + Broadcast sent on %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 - + Doing work necessary to send message. There is no required difficulty for version 2 addresses like this. - + Doing work necessary to send message. Receiver's required difficulty: %1 and %2 - + Problem: The work demanded by the recipient (%1 and %2) is more difficult than you are willing to do. %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 - + Doing work necessary to send message. - + Message sent. Waiting for acknowledgement. Sent on %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 - + - + UPnP port mapping established on port %1 - + - + UPnP port mapping removed - - - - - Mark all messages as read - - - - - Are you sure you would like to mark all messages read? - + @@ -1291,12 +1274,12 @@ Receiver's required difficulty: %1 and %2 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> - + @@ -1306,17 +1289,17 @@ The 'Random Number' option is selected by default but deterministic ad Use a passphrase to make addresses - + Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter - + Make deterministic addresses - + @@ -1326,7 +1309,7 @@ The 'Random Number' option is selected by default but deterministic ad In addition to your passphrase, you must remember these numbers: - + @@ -1336,17 +1319,17 @@ The 'Random Number' option is selected by default but deterministic ad Number of addresses to make based on your passphrase: - + Stream number: 1 - + Retype passphrase - + @@ -1356,27 +1339,27 @@ The 'Random Number' option is selected by default but deterministic ad 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) - + @@ -1384,7 +1367,7 @@ The 'Random Number' option is selected by default but deterministic ad Add new entry - Nieuw adres toevoegen + @@ -1399,7 +1382,7 @@ The 'Random Number' option is selected by default but deterministic ad Enter an address above. - + @@ -1407,64 +1390,60 @@ The 'Random Number' option is selected by default but deterministic ad 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: - - - - - Ui_aboutDialog - - - aboutDialog - <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 The Bitmessage Developers</p></body></html> - + aboutDialog - + About Over - + 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> - + - + This is Beta software. - + + + + + <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 The Bitmessage Developers</p></body></html> + @@ -1472,37 +1451,37 @@ The 'Random Number' option is selected by default but deterministic ad Use a Blacklist (Allow all incoming messages except those on the Blacklist) - + Use a Whitelist (Block all incoming messages except those on the Whitelist) - + Add new entry - Nieuw adres toevoegen + Name or Label - + Address - Adres + Blacklist - + Whitelist - + @@ -1515,17 +1494,17 @@ The 'Random Number' option is selected by default but deterministic ad Bitmessage won't connect to anyone until you let it. - + Connect now - + Let me configure special network settings first - + @@ -1538,12 +1517,12 @@ The 'Random Number' option is selected by default but deterministic ad <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: - + @@ -1551,27 +1530,27 @@ The 'Random Number' option is selected by default but deterministic ad 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. - + 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. - + @@ -1579,127 +1558,112 @@ The 'Random Number' option is selected by default but deterministic ad Total connections: - + Since startup: - + Processed 0 person-to-person messages. - + Processed 0 public keys. - + Processed 0 broadcasts. - + Inventory lookups per second: 0 - + Objects to be synced: - + Stream # - + Connections - + Since startup on %1 - + Down: %1/s Total: %2 - + Up: %1/s Total: %2 - + Total Connections: %1 - + Inventory lookups per second: %1 - + Up: 0 kB/s - + Down: 0 kB/s - + Network Status - + byte(s) - - - - + Object(s) to be synced: %n - - - - + Processed %n person-to-person message(s). - - - - + Processed %n broadcast message(s). - - - - + Processed %n public key(s). - - - - + @@ -1707,42 +1671,42 @@ The 'Random Number' option is selected by default but deterministic ad Dialog - + Create a new chan - + Join a chan - + Create a chan - + <html><head/><body><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. If you and someone else both create a chan with the same chan name then it is currently very likely that they will be the same chan.</p></body></html> - + Chan name: - + <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 normal person-to-person message to the chan address.</p><p>Chans are experimental and completely unmoderatable.</p></body></html> - + Chan bitmessage address: - + @@ -1750,12 +1714,12 @@ The 'Random Number' option is selected by default but deterministic ad Regenerate Existing Addresses - + Regenerate existing addresses - + @@ -1765,17 +1729,17 @@ The 'Random Number' option is selected by default but deterministic ad Number of addresses to make based on your passphrase: - + Address version number: - + Stream number: - + @@ -1785,17 +1749,17 @@ The 'Random Number' option is selected by default but deterministic ad Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter - + 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. - + @@ -1813,52 +1777,52 @@ The 'Random Number' option is selected by default but deterministic ad 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. - + Willingly include unencrypted destination address when sending to a mobile device - + Use Identicons - + Reply below Quote - + @@ -1869,7 +1833,7 @@ The 'Random Number' option is selected by default but deterministic ad System Settings system - + @@ -1889,22 +1853,22 @@ The 'Random Number' option is selected by default but deterministic ad UPnP: - + Bandwidth limit - + Maximum download rate (kB/s): [0: unlimited] - + Maximum upload rate (kB/s): [0: unlimited] - + @@ -1944,7 +1908,7 @@ The 'Random Number' option is selected by default but deterministic ad Listen for incoming connections when using proxy - + @@ -1969,67 +1933,67 @@ The 'Random Number' option is selected by default but deterministic ad 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. - + 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 - + Hardware GPU acceleration (OpenCL) - + <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> - + Host: - + @@ -2039,32 +2003,32 @@ The 'Random Number' option is selected by default but deterministic ad Test - + Connect to: - + Namecoind - + NMControl - + Namecoin integration - + <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> - + @@ -2089,7 +2053,7 @@ The 'Random Number' option is selected by default but deterministic ad Resends Expire - + - + \ No newline at end of file diff --git a/src/translations/bitmessage_no.qm b/src/translations/bitmessage_no.qm index 493d09ef..2edc917c 100644 Binary files a/src/translations/bitmessage_no.qm and b/src/translations/bitmessage_no.qm differ diff --git a/src/translations/bitmessage_no.ts b/src/translations/bitmessage_no.ts index bb1d5278..8ab1e73b 100644 --- a/src/translations/bitmessage_no.ts +++ b/src/translations/bitmessage_no.ts @@ -1,5 +1,4 @@ - - + AddAddressDialog @@ -48,7 +47,7 @@ Email gateway allows you to communicate with email users. Currently, only the Mailchuck email gateway (@mailchuck.com) is available. - + @@ -59,14 +58,14 @@ EmailGatewayRegistrationDialog - + Registration failed: - Registrering feilet: + - + The requested email address is not available, please try a new one. Fill out the new desired email address (including @mailchuck.com) below: - + @@ -77,7 +76,7 @@ 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: - + @@ -124,58 +123,58 @@ Please type the desired email address (including @mailchuck.com) below: # the money directly. To turn it off again, set "feeamount" to 0. Requires # subscription. - + MainWindow - + Reply to sender Svar til avsender - + Reply to channel Svar til kanal - + Add sender to your Address Book Legg til avsender i adresseboka - + Add sender to your Blacklist Legg til avsender i svartelisten - + Move to Trash Kast - + Undelete Angre sletting - + View HTML code as formatted text Vis HTML-koden som formatert tekst - + Save message as... Lagre beskjed som ... - + Mark Unread Merk som ulest - + New Ny @@ -200,12 +199,12 @@ Please type the desired email address (including @mailchuck.com) below: Kopier adressen til utklippstavlen - + Special address behavior... Spesieladressebehandling ... - + Email gateway Epost portal @@ -215,32 +214,32 @@ Please type the desired email address (including @mailchuck.com) below: Slett - + Send message to this address Send beskjed til denne adressen - + Subscribe to this address Abonner på denne adressen - + Add New Address Legg til ny adresse - + Copy destination address to clipboard Kopier destinasjonsadresse til utklippstavlen - + Force send Tving sending - + One of your addresses, %1, is an old version 1 address. Version 1 addresses are no longer supported. May we delete it now? En av dine gamle adresser er av den første typen og derfor ikke lenger støttet: %1. Derfor kan den vel slettes? @@ -493,7 +492,7 @@ Det er viktig at du tar sikkerhetskopi av denne filen. Vil du åpne denne filen 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. - + @@ -503,12 +502,12 @@ Det er viktig at du tar sikkerhetskopi av denne filen. Vil du åpne denne filen 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. - + 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. - + @@ -543,7 +542,7 @@ Det er viktig at du tar sikkerhetskopi av denne filen. Vil du åpne denne filen Error: Some data encoded in the address %1 is malformed. There might be something wrong with the software of your acquaintance. - + @@ -596,24 +595,24 @@ Det er viktig at du tar sikkerhetskopi av denne filen. Vil du åpne denne filen Høyreklikk på en eller flere oppføringer i adresseboka og velg 'Send beskjed til denne adressen'. - + Fetched address from namecoin identity. Hentet adresse fra Namecoin-identitet. - + New Message Ny beskjed - + From Fra - + Sending email gateway registration request - + @@ -626,241 +625,241 @@ Det er viktig at du tar sikkerhetskopi av denne filen. Vil du åpne denne filen Adressen du oppga var ugyldig og vil derfor bli ignorert. - + Error: You cannot add the same address to your address book twice. Try renaming the existing one if you want. Feil: Du kan ikke legge til samme adresse i adresseboka flere ganger. - + Error: You cannot add the same address to your subscriptions twice. Perhaps rename the existing one if you want. - + - + Restart Omstart - + You must restart Bitmessage for the port number change to take effect. Du må ta omstart av Bitmessage for at endringen av portnummer skal tre i kraft. - + Bitmessage will use your proxy from now on but you may want to manually restart Bitmessage now to close existing connections (if any). Bitmessage vil bruke proxy fra nå av. Hvis du vil kan du omstart av programmet for å lukke eksisterende tilkoblinger (hvis det finnes noen). - + Number needed Trenger nummer - + Your maximum download and upload rate must be numbers. Ignoring what you typed. - + - + Will not resend ever Vil ikke igjensende noensinne - + 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. Legg merke til at utløpstiden du oppga er kortere enn det Bitmessage venter for første igjensendingsforsøk, dine beskjeder vil derfor aldri bli igjensendt. - + Sending email gateway unregistration request - + - + Sending email gateway status request - + - + Passphrase mismatch Passordfrase stemmer ikke - + The passphrase you entered twice doesn't match. Try again. Passordfrasene er ikke like. Vennligst prøv igjen. - + Choose a passphrase Velg en passordfrase - + You really do need a passphrase. Du trenger sårt en passordfrase. - + Address is gone Adressen er borte - + Bitmessage cannot find your address %1. Perhaps you removed it? Bitmessage kan ikke finne adressen %1. Kanskje du fjernet den? - + Address disabled Adressen er deaktivert - + 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. Feil: Adressen du prøver å sende med er deaktivert. Du må aktivere den fra 'Dine identiteter' før du kan bruke den. - + Entry added to the Address Book. Edit the label to your liking. Ny oppføring lagt til i adresseboka. Du kan forandre etiketten til det du måtte ønske. - + 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. Kastet innholdet. - + Undeleted item. - Gjenopprettet innhold. + - + Save As... Lagre som ... - + Write error. Skrivefeil. - + No addresses selected. Ingen adresse valgt. - + 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? Vil du virkelig fjerne dette avataret? - + You have already set an avatar for this address. Do you really want to overwrite it? Du har allerede satt ett avatar for denne adressen. Vil du virkelig overskrive det? - + Start-on-login not yet supported on your OS. Start ved innlogging er ikke støttet enda for ditt OS. - + Minimize-to-tray not yet supported on your OS. - Minimering til systemkurv er ikke støttet enda for ditt OS. + Minimering til systemstatusfeltet er ikke støttet enda for ditt OS. - + Tray notifications not yet supported on your OS. - Varslinger via systemkurv er ikke støttet enda for ditt OS. + Varslinger via systemstatusfeltet er ikke støttet enda for ditt OS. - + Testing... Tester ... - + This is a chan address. You cannot use it as a pseudo-mailing list. Dette er en kanaladresse. Du kan ikke bruke den som en pseudo-epostliste. - + The address should start with ''BM-'' Adressen bør starte med ''BM-'' - + The address is not typed or copied correctly (the checksum failed). Adressen er ikke skrevet eller kopiert inn riktig (sjekksummen feilet). - + The version number of this address is higher than this software can support. Please upgrade Bitmessage. Typenummeret for denne adressen er høyere enn det programvaren støtter. Vennligst oppgrader Bitmessage. - + The address contains invalid characters. Adressen inneholder ugyldige tegn. - + Some data encoded in the address is too short. Noen av de kodede dataene i adressen er for korte. - + Some data encoded in the address is too long. Noen av de kodede dataene i adressen er for lange. - + Some data encoded in the address is malformed. - + - + Enter an address above. Oppgi inn en adresse over. - + Address is an old type. We cannot display its past broadcasts. Adressen er av gammel type. Vi kan ikke vise dens tidligere kringkastninger. - + There are no recent broadcasts from this address to display. Det er ingen nylige kringkastninger fra denne adressen å vise frem. - + You are using TCP port %1. (This can be changed in the settings). Du benytter TCP-port %1. (Dette kan endres på i innstillingene). @@ -1062,225 +1061,209 @@ Are you sure you want to delete the channel? Error: You cannot add the same address to your list twice. Perhaps rename the existing one if you want. - + Add new entry - Legg til ny oppføring + - + Display the %1 recent broadcast(s) from this address. - + New version of PyBitmessage is available: %1. Download it from https://github.com/Bitmessage/PyBitmessage/releases/latest - + - + Waiting for PoW to finish... %1% - + - + Shutting down Pybitmessage... %1% - + - + Waiting for objects to be sent... %1% - + - + Saving settings... %1% - + - + Shutting down core... %1% - + - + Stopping notifications... %1% - + - + Shutdown imminent... %1% - + %n hour(s) - - %n time - %n timer - + - + %n day(s) - - %n dag - %n dager - + - + Shutting down PyBitmessage... %1% - + Sent - Sendt + Generating one new address - + Done generating address. Doing work necessary to broadcast it... - + Generating %1 new addresses. - + %1 is already in 'Your Identities'. Not adding it again. - + Done generating address - + - + SOCKS5 Authentication problem: %1 - + - + 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. - + Doing work necessary to send broadcast... - + Broadcast sent on %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 - + Doing work necessary to send message. There is no required difficulty for version 2 addresses like this. - + Doing work necessary to send message. Receiver's required difficulty: %1 and %2 - + Problem: The work demanded by the recipient (%1 and %2) is more difficult than you are willing to do. %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 - + Doing work necessary to send message. - + Message sent. Waiting for acknowledgement. Sent on %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 - + - + UPnP port mapping established on port %1 - + - + UPnP port mapping removed - - - - - Mark all messages as read - - - - - Are you sure you would like to mark all messages read? - + @@ -1434,59 +1417,55 @@ The 'Random Number' option is selected by default but deterministic ad Navnet på pseudo-epostlista: - - Ui_aboutDialog - - - aboutDialog - <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 The Bitmessage Developers</p></body></html> - - - aboutDialog - + About Om - + PyBitmessage PyBitmessage - + version ? versjon ? - + <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>Distribuert under MIT/X11-programvarelisens, se <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. Dette er betaprogramvare. + + + <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 The Bitmessage Developers</p></body></html> + + blacklist Use a Blacklist (Allow all incoming messages except those on the Blacklist) - + Use a Whitelist (Block all incoming messages except those on the Whitelist) - + Add new entry - Legg til ny oppføring + @@ -1542,7 +1521,7 @@ The 'Random Number' option is selected by default but deterministic ad <a href="https://bitmessage.org/wiki/PyBitmessage_Help">https://bitmessage.org/wiki/PyBitmessage_Help</a> - + @@ -1608,7 +1587,7 @@ The 'Random Number' option is selected by default but deterministic ad Inventory lookups per second: 0 - + @@ -1618,7 +1597,7 @@ The 'Random Number' option is selected by default but deterministic ad Stream # - + @@ -1648,62 +1627,47 @@ The 'Random Number' option is selected by default but deterministic ad Inventory lookups per second: %1 - + Up: 0 kB/s - Opp: 0 kB/s + Down: 0 kB/s - Ned: 0 kB/s + Network Status - Nettverksstatus + byte(s) - - byte - byte - + Object(s) to be synced: %n - - - - + Processed %n person-to-person message(s). - - - - + Processed %n broadcast message(s). - - - - + Processed %n public key(s). - - - - + @@ -1817,22 +1781,22 @@ The 'Random Number' option is selected by default but deterministic ad Tray - Systemkurv + Start Bitmessage in the tray (don't show main window) - Start Bitmessage i systemkurv (ikke vis hovedvinduet) + Start Bitmessage i systemstatusfeltet (ikke vis hovedvinduet) Minimize to tray - Minimiser til systemkurv + Minimiser til systemstatusfeltet Close to tray - Lukk til systemkurv + @@ -1903,12 +1867,12 @@ The 'Random Number' option is selected by default but deterministic ad Maximum download rate (kB/s): [0: unlimited] - + Maximum upload rate (kB/s): [0: unlimited] - + @@ -2023,7 +1987,7 @@ The 'Random Number' option is selected by default but deterministic ad Hardware GPU acceleration (OpenCL) - + @@ -2096,4 +2060,4 @@ The 'Random Number' option is selected by default but deterministic ad Igjensending - + \ No newline at end of file diff --git a/src/translations/bitmessage_pl.qm b/src/translations/bitmessage_pl.qm deleted file mode 100644 index fb31e8d5..00000000 Binary files a/src/translations/bitmessage_pl.qm and /dev/null differ diff --git a/src/translations/bitmessage_pl.ts b/src/translations/bitmessage_pl.ts index 89c6162e..a72e9db7 100644 --- a/src/translations/bitmessage_pl.ts +++ b/src/translations/bitmessage_pl.ts @@ -2,98 +2,68 @@ AddAddressDialog - + Add new entry - Dodaj nowy wpis + - + Label - Etykieta + - + Address - Adres + EmailGatewayDialog - + Email gateway - Przekaźnik e-mail + - + Register on email gateway - Zarejestruj u przekaźnika e-mail + - + Account status at email gateway - Status konta u przekaźnika e-mail + - + Change account settings at email gateway - Zmień ustawienia konta u przekaźnika e-mail + - + Unregister from email gateway - Wyrejestruj od przekaźnika e-mail + - + Email gateway allows you to communicate with email users. Currently, only the Mailchuck email gateway (@mailchuck.com) is available. - Przekaźnik e-mail umożliwia komunikację z użytkownikami poczty elektronicznej. Obecnie usługa ta oferowana jest tylko przez Mailchuck (@mailchuck.com). + - + Desired email address (including @mailchuck.com): - Wybrany adres e-mail (razem a końcówką @mailchuck.com): - - - - @mailchuck.com - @mailchuck.com - - - - Registration failed: - Rejestracja nie powiodła się: - - - - The requested email address is not available, please try a new one. - Wybrany adres e-mail nie jest dostępny, proszę spróbować inny. - - - - Sending email gateway registration request - Wysyłanie zapytania o rejestrację na bramce poczty - - - - Sending email gateway unregistration request - Wysyłanie zapytania o wyrejestrowanie z bramki poczty - - - - Sending email gateway status request - Wysyłanie zapytania o stan bramki poczty + EmailGatewayRegistrationDialog - + Registration failed: - + The requested email address is not available, please try a new one. Fill out the new desired email address (including @mailchuck.com) below: @@ -155,212 +125,128 @@ Please type the desired email address (including @mailchuck.com) below: - - - # 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ć -# Ustawienia: -# -# pgp: server -# Bramka poczty utworzy i będzie zarządzać kluczami PGP za Ciebie - -# podpisywać, weryfikować, szyfrować i deszyfrować. Jeżeli chcesz -# używać PGP, ale jesteś leniwy, użyj tej opcji. Wymaga subskrypcji. -# -# pgp: local -# Bramka poczty nie będzie wykonywała operacji PGP za Ciebie. Możesz -# albo wcale nie korzystać z PGP, albo używać go lokalnie. -# -# attachments: yes -# Przychodzące załączniki w wiadomościach będą wrzucane na MEGA.nz i -# będziesz mógł je pobrać z podanego łącza. Wymaga subskrypcji. -# -# attachments: no -# Załączniki zostaną zignorowane. -# -# 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 -# spowoduje, że operator usługi będzie mógł czytać Twoje listy nawet po -# przesłaniu ich do Ciebie. -# -# archive: no -# Przychodzące listy zostaną niezwłocznie usunięte z serwera po -# przekierowaniu ich do Ciebie. -# -# masterpubkey_btc: publiczny klucz BIP44 xpub lub electrum v1 publiczny seed -# offset_btc: integer (domyślnie 0) -# feeamount: liczba, maksymalnie 8 cyfr po przecinku -# feecurrency: BTC, XBT, USD, EUR lub GBP -# Użyj tych opcji, jeżeli chcesz pobierać opłaty od osób, które wyślą -# 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. -# 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 - + Reply to sender + + + + + Reply to channel + + + + + Add sender to your Address Book + + + + + Add sender to your Blacklist + + + + + Move to Trash + + + + 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,19 +254,19 @@ 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,1511 +274,1175 @@ 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. - Możesz zarządzać swoimi kluczami edytując plik keys.dat znajdujący się -%1 -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.) - Możesz zarządzać swoimi kluczami edytując plik keys.dat znajdujący się -%1 -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. + - + Chan name needed - + You didn't enter a chan name. - + Address already present - + Could not add chan because it appears to already be one of your identities. - + Success - + Successfully created chan. To let others join your chan, give them the chan name and this Bitmessage address: %1. This address also appears in 'Your Identities'. - + 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. - + Address invalid - + That Bitmessage address is not valid. - + Address does not match chan name - + Although the Bitmessage address you entered was valid, it doesn't match the chan name. - + Successfully joined chan. - + 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 more work your computer must do to send the message. A Time-To-Live of four or five days is often appropriate. - TTL (ang. Time-To-Live -- czas życia) to czas przez jaki wiadomość będzie przechowywana przez węzły sieci, po jego upływie odbiorca nie będzie juz mógł jej odebrać. -Jeżeli adresat nie otrzyma w tym czasie potwierdzenia odbioru wiadomości, zostanie ona wysłana ponownie. -Im dłuższy TTL, tym więcej pracy będzie musiał wykonac komputer wysyłający wiadomość. -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. + - + Error: Bitmessage addresses start with BM- Please check %1 - + Error: The address %1 is not typed or copied correctly. Please check it. - + Error: The address %1 contains invalid characters. Please check it. - + Error: The address version in %1 is too high. Either you need to upgrade your Bitmessage software or your acquaintance is being clever. - + Error: Some data encoded in the address %1 is too short. There might be something wrong with the software of your acquaintance. - + Error: Some data encoded in the address %1 is too long. There might be something wrong with the software of your acquaintance. - + Error: Some data encoded in the address %1 is malformed. There might be something wrong with the software of your acquaintance. - + Error: Something is wrong with the address %1. - + Error: You must specify a From address. If you don't have one, go to the 'Your Identities' tab. - Błąd: musisz wybrać adres wysyłania. Jeżeli go nie posiadasz, przejdź do zakładki 'Twoje tożsamości'. + - + Address version number - Numer wersji adresu + - + Concerning the address %1, Bitmessage cannot understand address version numbers of %2. Perhaps upgrade Bitmessage to the latest version. - Odnośnie adresu %1, Bitmessage nie potrafi odczytać wersji adresu %2. Może uaktualnij Bitmessage do najnowszej wersji. + - + Stream number - Numer strumienia + - + Concerning the address %1, Bitmessage cannot handle stream numbers of %2. Perhaps upgrade Bitmessage to the latest version. - Odnośnie adresu %1, Bitmessage nie potrafi operować na strumieniu adresu %2. Może uaktualnij Bitmessage do najnowszej wersji. + - + Warning: You are currently not connected. Bitmessage will do the work necessary to send the message but it won't send until you connect. - Uwaga: nie jesteś obecnie połączony. Bitmessage wykona niezbędną pracę do wysłania wiadomości, ale nie wyśle jej póki się nie połączysz. + - + Message queued. - W kolejce do wysłania + - + Your 'To' field is empty. - Pole 'Do' jest puste + - + Right click one or more entries in your address book and select 'Send message to this address'. - Użyj prawego przycisku myszy na adresie z książki adresowej i wybierz opcję "Wyślij wiadomość do tego adresu". + - + Fetched address from namecoin identity. - Pobrano adres z identyfikatora Namecoin. + - + New Message - Nowa wiadomość + - + From - + Sending email gateway registration request 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. + - + Sending email gateway unregistration request - + Sending email gateway status request - + Passphrase mismatch - Hasła różnią się + - + The passphrase you entered twice doesn't match. Try again. - Hasła, które wpisałeś nie pasują. Spróbuj ponownie. + - + Choose a passphrase - Wpisz hasło + - + You really do need a passphrase. - Naprawdę musisz wpisać hasło. + - + Address is gone - Adres zniknął + - + Bitmessage cannot find your address %1. Perhaps you removed it? - Bitmessage nie może odnaleźć Twojego adresu %1. Może go usunąłeś? + - + Address disabled - Adres nieaktywny + - + Error: The address from which you are trying to send is disabled. You'll have to enable it on the 'Your Identities' tab before using it. - Błąd: adres, z którego próbowałeś wysłać wiadomość jest nieaktywny. Włącz go w zakładce „Twoje tożsamości”, zanim go użyjesz. + - + Entry added to the Address Book. Edit the label to your liking. - + Entry added to the blacklist. Edit the label to your liking. - Dodano wpis do listy blokowanych. Można teraz zmienić jego nazwę. + - + Error: You cannot add the same address to your blacklist twice. Try renaming the existing one if you want. - Błąd: adres znajduje się już na liście blokowanych. + - + Moved items to trash. - Przeniesiono wiadomości do kosza. + - + Undeleted item. - Przywró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? - Jeżeli usuniesz subskrypcję, wiadomości które już dostałeś staną się niedostępne. Może powinieneś rozważyć wyłączenie subskrypcji. Dezaktywowane subskrypcje nie będą odbierać nowych wiadomości, ale ciągle będziesz mógł odczytać obecnie pobrane. - -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? - Jeżeli usuniesz kanał, wiadomości które już dostałeś staną się niedostępne. Może powinieneś rozważyć wyłączenie kanału. Dezaktywowane kanały nie będą odbierać nowych wiadomości, ale ciągle będziesz mógł odczytać obecnie pobrane. - -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… + - + This is a chan address. You cannot use it as a pseudo-mailing list. - + 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. + - + Enter an address above. - + 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. + - + You are using TCP port %1. (This can be changed in the settings). - + Bitmessage - Bitmessage + + + + + Identities + + + + + New Identity + + + + + Search + + + + + All + + + + + To + + + + + From + + + + + Subject + + + + + Message + + + + + Received + - Identities - Tożsamości + Messages + - - New Identity - Nowa tożsamość + + Address book + - - Search - Szukaj + + Address + - - All - Wszystkie + + Add Contact + - - To - Do + + Fetch Namecoin ID + - - From - Od + + Subject: + - - Subject - Temat + + From: + - - Message - Wiadomość + + To: + - - Received - Odebrana + + Send ordinary Message + + + + + Send Message to your Subscribers + - Messages - Wiadomości + TTL: + - - Address book - Książka adresowa - - - - Address - Adres - - - - Add Contact - Dodaj kontakt - - - - Fetch Namecoin ID - Pobierz Namecoin ID - - - - Subject: - Temat: - - - - From: - Od: + + Subscriptions + - To: - Do: - - - - Send ordinary Message - Wyślij zwykłą wiadomość - - - - Send Message to your Subscribers - Wyślij wiadomość broadcast - - - - TTL: - Czas życia: + Add new Subscription + - Subscriptions - Subskrypcje + Chans + - Add new Subscription - Dodaj subskrypcję + Add Chan + + + + + File + + + + + Settings + + + + + Help + + + + + Import keys + + + + + Manage keys + + + + + Ctrl+Q + + + + + F1 + + + + + Contact support + + + + + About + + + + + Regenerate deterministic addresses + - 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. - + 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 + - + SOCKS5 Authentication problem: %1 - + 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 + + + + + Problem: Destination is a mobile device who requests that the destination be included in the message but this is disallowed in your settings. %1 + + + + + Doing work necessary to send message. +There is no required difficulty for version 2 addresses like this. + + + + + Doing work necessary to send message. +Receiver's required difficulty: %1 and %2 + + + + + Problem: The work demanded by the recipient (%1 and %2) is more difficult than you are willing to do. %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 + + + + + Doing work necessary to send message. + - 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? - - - - 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 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 - * send broadcast messages like twitter or - * discuss in chan(nel)s with other people - - -Witamy w przyjaznym i bezpiecznym Bitmessage -* wysyłaj wiadomości do innych użytkowników -* wysyłaj wiadomości subskrypcji (jak na Twitterze) -* dyskutuj na kanałach (chany) z innymi ludźmi - - - - not recommended for chans - niezalecany dla kanałów - - - - Quiet Mode - Tryb cichy - - - - Problems connecting? Try enabling UPnP in the Network Settings - Problem z połączeniem? Spróbuj włączyć UPnP w ustawieniach sieci. - - - - You are trying to send an email instead of a bitmessage. This requires registering with a gateway. Attempt to register? - Próbujesz wysłać e-mail zamiast wiadomość bitmessage. To wymaga zarejestrowania się na bramce. Czy zarejestrować? - - - - Error: Bitmessage addresses start with BM- Please check the recipient address %1 - Błąd: adresy Bitmessage zaczynają się od BM-. Proszę sprawdzić adres odbiorcy %1. - - - - Error: The recipient address %1 is not typed or copied correctly. Please check it. - Błąd: adres odbiorcy %1 nie został skopiowany lub przepisany poprawnie. Proszę go sprawdzić. - - - - Error: The recipient address %1 contains invalid characters. Please check it. - Błąd: adres odbiorcy %1 zawiera nieprawidłowe znaki. Proszę go sprawdzić. - - - - Error: The version of the recipient address %1 is too high. Either you need to upgrade your Bitmessage software or your acquaintance is being clever. - Błąd: wersja adresu odbiorcy %1 jest za wysoka. Musisz albo zaktualizować Twoje oprogramowanie Bitmessage, albo twój znajomy Cię trolluje. - - - - Error: Some data encoded in the recipient address %1 is too short. There might be something wrong with the software of your acquaintance. - Błąd: niektóre dane zakodowane w adresie odbiorcy %1 są zbyt krótkie. Być może coś nie działa należycie w programie Twojego znajomego. - - - - Error: Some data encoded in the recipient address %1 is too long. There might be something wrong with the software of your acquaintance. - Błąd: niektóre dane zakodowane w adresie odbiorcy %1 są zbyt długie. Być może coś nie działa należycie w programie Twojego znajomego. - - - - Error: Some data encoded in the recipient address %1 is malformed. There might be something wrong with the software of your acquaintance. - Błąd: niektóre dane zakodowane w adresie odbiorcy %1 są uszkodzone. Być może coś nie działa należycie w programie Twojego znajomego. - - - - Error: Something is wrong with the recipient address %1. - Błąd: coś jest nie tak z adresem odbiorcy %1. - - - - Error: %1 - Błąd: %1 - - - - From %1 - Od %1 - - - - 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 - - - - MsgDecode - - - The message has an unknown encoding. -Perhaps you should upgrade Bitmessage. - Wiadomość zawiera nierozpoznane kodowanie. -Prawdopodobnie powinieneś zaktualizować Bitmessage. - - - - Unknown encoding - Nierozpoznane kodowanie + NewAddressDialog - + Create new Address - Generuj nowy adres + - + Here you may generate as many addresses as you like. Indeed, creating and abandoning addresses is encouraged. You may generate addresses by using either random numbers or by using a passphrase. If you use a passphrase, the address is called a "deterministic" address. The 'Random Number' option is selected by default but deterministic addresses have several pros and cons: - Tutaj możesz utworzyć tyle adresów, ile tylko potrzebujesz. W istocie, tworzenie nowych i porzucanie adresów jest zalecane. Możesz wygenerować adres używając albo losowych liczb albo hasła. Jeżeli użyjesz hasła, adres taki jest nazywany adresem „deterministycznym”. -Generowanie adresów „losowych” jest wybrane domyślnie, jednak deterministyczne adresy mają swoje wady i zalety: + - + <html><head/><body><p><span style=" font-weight:600;">Pros:<br/></span>You can recreate your addresses on any computer from memory. <br/>You need-not worry about backing up your keys.dat file as long as you can remember your passphrase. <br/><span style=" font-weight:600;">Cons:<br/></span>You must remember (or write down) your passphrase if you expect to be able to recreate your keys if they are lost. <br/>You must remember the address version number and the stream number along with your passphrase. <br/>If you choose a weak passphrase and someone on the Internet can brute-force it, they can read your messages and send messages as you.</p></body></html> - <html><head/><body><p><span style=" font-weight:600;">Zalety:<br/></span>Możesz wygenerować swój adres na każdym komputerze z głowy.<br/>Nie musisz się martwić o tworzenie kopii zapasowej pliku keys.dat tak długo jak pamiętasz hasło.</br><span style=" font-weight:600;">Wady:<br/></span>Musisz zapamiętać (bądź zapisać) swoje hasło, jeżeli chcesz odzyskać swój adres po utracie kluczy.</br>Musisz zapamiętać numer wersji adresu i numer strumienia razem z hasłem.</br>Jeżeli użyjesz słabego hasła, ktoś z Internetu może je odgadnąć i przeczytać wszystkie Twoje wiadomości i wysyłać wiadomości jako Ty.</p></body></html> + - + Use a random number generator to make an address - Użyj generatora liczb losowych do utworzenia adresu + - + Use a passphrase to make addresses - Użyj hasła do utworzenia adresu + - + Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter - Dołóż kilka minut dodatkowych obliczeń aby wygenerować adres(y) krótsze o 1 lub 2 znaki + - + Make deterministic addresses - Utwórz adres deterministyczny + - + Address version number: 4 - Numer wersji adresu: 4 + - + In addition to your passphrase, you must remember these numbers: - Razem ze swoim hasłem musisz zapamiętać te liczby: + - + Passphrase - Hasło + - + Number of addresses to make based on your passphrase: - Liczba adresów do wygenerowanie na podstawie hasła: + - + Stream number: 1 - Numer strumienia: 1 + - + Retype passphrase - Hasło ponownie + - + Randomly generate address - Adres losowy + - + Label (not shown to anyone except you) - Etykieta (nie wyświetlana komukolwiek oprócz Ciebie) + - + Use the most available stream - Użyj najbardziej dostępnego strumienia + - + (best if this is the first of many addresses you will create) - (zalecane, jeżeli jest to pierwszy z adresów który chcesz utworzyć) + - + Use the same stream as an existing address - Użyj tego samego strumienia co istniejący adres + - + (saves you some bandwidth and processing power) - (oszczędza trochę transferu i mocy procesora) + NewSubscriptionDialog - + Add new entry - Dodaj nowy wpis + - + Label - Etykieta + - + Address - Adres + - + Enter an address above. - Wprowadź adres powyżej. + SpecialAddressBehaviorDialog - + Special Address Behavior - Specjalne zachowanie adresu + - + Behave as a normal address - Zachowuj się jak normalny adres + - + Behave as a pseudo-mailing-list address - Zachowuj się jak pseudo-lista-dyskusyjna + - + Mail received to a pseudo-mailing-list address will be automatically broadcast to subscribers (and thus will be public). - Wiadomości wysłane na pseudo-listę-dyskusyjną zostaną automatycznie rozesłane do abonentów (i w ten sposób będą publiczne). + - + Name of the pseudo-mailing-list: - Nazwa pseudo-listy-dyskusyjnej: - - - - This is a chan address. You cannot use it as a pseudo-mailing list. - To jest adres kanału. Nie możesz go użyć jako pseudo-listy-dyskusyjnej. + aboutDialog - + About - O programie + - + PyBitmessage - + 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>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. - - <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 The Bitmessage Developers</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> - - - <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> + + + This is Beta software. + + + + + <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 The Bitmessage Developers</p></body></html> + @@ -1900,101 +1450,96 @@ Generowanie adresów „losowych” jest wybrane domyślnie, jednak deterministy Use a Blacklist (Allow all incoming messages except those on the Blacklist) - Użyj czarnej listy (zezwala na wszystkie przychodzące wiadomości, z wyjątkiem tych na czarnej liście) + Use a Whitelist (Block all incoming messages except those on the Whitelist) - Użyj białej listy (blokuje wszystkie wiadomości, z wyjątkiem o tych na białej liście) + Add new entry - Dodaj wpis + Name or Label - Nazwa lub etykieta + Address - Adres + Blacklist - Czarna lista + Whitelist - Biała lista + connectDialog - + Bitmessage - Bitmessage + - + Bitmessage won't connect to anyone until you let it. - Bitmessage nie połączy się, zanim mu na to nie pozwolisz. + - + Connect now - Połącz teraz + - + Let me configure special network settings first - Pozwól mi najpierw ustawić specjalne opcje konfiguracji sieci - - - - Work offline - Działaj bez sieci + helpDialog - + Help - Pomoc + - + <a href="https://bitmessage.org/wiki/PyBitmessage_Help">https://bitmessage.org/wiki/PyBitmessage_Help</a> - <a href="https://bitmessage.org/wiki/PyBitmessage_Help">https://bitmessage.org/wiki/PyBitmessage_Help</a> + - + As Bitmessage is a collaborative project, help can be found online in the Bitmessage Wiki: - Ponieważ Bitmessage jest tworzone przez społeczność, możesz uzyskać pomoc w sieci na wiki Bitmessage: + iconGlossaryDialog - + Icon Glossary - Opis ikon + - + You have no connections with other peers. - Nie masz żadnych połączeń z innymi użytkownikami. + - + You have made at least one connection to a peer using an outgoing connection but you have not yet received any incoming connections. Your firewall or home router probably isn't configured to forward incoming TCP connections to your computer. Bitmessage will work just fine but it would help the Bitmessage network if you allowed for incoming connections and will help you be a better-connected node. - Masz co najmniej jedno połączenie wychodzące z innymi użytkownikami, ale nie masz jeszcze żadnych połączeń przychodzących. Twoja zapora sieciowa lub domowy ruter prawdopodobnie nie są poprawnie skonfigurowane aby przekazywać połączenia przychodzące TCP na Twój komputer. Bitmessage będzie działał dobrze, ale byłoby fajnie, gdybyś pomógł sieci Bitmessage i zezwolił na połączenia przychodzące. + @@ -2002,14 +1547,9 @@ Generowanie adresów „losowych” jest wybrane domyślnie, jednak deterministy - + You do have connections with other peers and your firewall is correctly configured. - Masz połączenia z innymi użytkownikami i twoja zapora sieciowa jest skonfigurowana poprawnie. - - - - You are using TCP port %1. (This can be changed in the settings). - Btimessage używa portu TCP %1. (Można go zmienić w ustawieniach). + @@ -2017,42 +1557,42 @@ Generowanie adresów „losowych” jest wybrane domyślnie, jednak deterministy Total connections: - Wszystkich połączeń: + - + Since startup: - Od startu: + + + + + Processed 0 person-to-person messages. + + + + + Processed 0 public keys. + + + + + Processed 0 broadcasts. + + + + + Inventory lookups per second: 0 + - Processed 0 person-to-person messages. - Przetworzono 0 wiadomości zwykłych. - - - - Processed 0 public keys. - Przetworzono 0 kluczy publicznych. - - - - Processed 0 broadcasts. - Przetworzono 0 wiadomości subskrypcji. - - - - Inventory lookups per second: 0 - Zapytań o elementy na sekundę: 0 - - - Objects to be synced: - Obiektów do zsynchronizowania: + - + Stream # - Strumień # + @@ -2060,114 +1600,69 @@ 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 + - + Up: 0 kB/s - Wysyłanie: 0 kB/s + - + Down: 0 kB/s - 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. - - - - Peer - Użytkownik - - - - IP address or hostname - IP lub nazwa hosta - - - - Rating - Ocena - - - - PyBitmessage tracks the success rate of connection attempts to individual nodes. The rating ranges from -1 to 1 and affects the likelihood of selecting the node in the future - PyBitmessage rejestruje pomyślność prób połączeń z indywidualnymi węzłami. Ocena przyjmuje wartości od -1 do 1 i ma wpływ na prawdopodobieństwo wybrania węzła w przyszłości. - - - - User agent - Klient - - - - Peer's self-reported software - Ogłaszana aplikacja kliencka - - - - TLS - TLS - - - - Connection encryption - Szyfrowanie połączenia - - - - List of streams negotiated between you and the peer - Lista strumieni negocjowanych pomiędzy Tobą i użytkownikiem + @@ -2212,343 +1707,277 @@ Generowanie adresów „losowych” jest wybrane domyślnie, jednak deterministy Chan bitmessage address: - - - Create or join a chan - Utwórz lub dołącz do kanału - - - - <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>Kanał powstałe wtedy, jeżeli grupa ludzi dzieli się tymi samymi kluczami szyfrującymi. Klucze i adres używane przez kanał są generowane z łatwego do zapamiętania słowa bądź hasła (nazwa kanału). Aby wysłać wiadomość do każdego na kanale, wyślij zwykłą wiadomość na adres kanału.</p><p>Kanały są eksperymentem i są poza wszelką kontrolą.</p><p>Wpisz nazwę swojego kanału. Jeżeli wybierzesz wystarczająco złożoną nazwę kanału (tak jak mocne i unikalne hasło) i nikt z Twoich przyjaciół nie ujawni go publicznie, wtedy Twój kanał będzie bezpieczny i prywatny. Jeżeli Ty i ktoś inny utworzy kanał o tej samej nazwie, wtedy prawdopodobnie będzie to ten sam kanał.</p></body></html> - - - - Chan passphrase/name: - Nazwa/hasło kanału: - - - - Optional, for advanced usage - Opcjonalne, dla zaawansowanych - - - - Chan address - Adres kanału - - - - Please input chan name/passphrase: - Proszę wpisz nazwę/hasło kanału: - - - - 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 - - - - 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. - regenerateAddressesDialog - + Regenerate Existing Addresses - Odtwórz istniejące adresy + - + Regenerate existing addresses - Odtwórz istniejące adresy + - + Passphrase - Hasło + - + Number of addresses to make based on your passphrase: - Liczba adresów do wygenerowanie na podstawie hasła: + - + Address version number: - Numer wersji adresu: + - + Stream number: - Numer strumienia: + - + 1 - 1 + - + Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter - Dołóż kilka minut dodatkowych obliczeń aby wygenerować adres(y) krótsze o 1 lub 2 znaki + - + You must check (or not check) this box just like you did (or didn't) when you made your addresses the first time. - Musisz to zaznaczyć (albo nie zaznaczyć) jeżeli zaznaczyłeś (bądź nie zaznaczyłeś) to podczas tworzenia adresu po raz pierwszy. + - + If you have previously made deterministic addresses but lost them due to an accident (like hard drive failure), you can regenerate them here. If you used the random number generator to make your addresses then this form will be of no use to you. - Jeżeli poprzednio wygenerowałeś deterministyczne adresy, ale je straciłeś przez przypadek (jak np. awaria dysku), możesz je tutaj odtworzyć. Jeżeli użyłeś generatora liczb losowych do utworzenia adresu, wtedy ten formularz jest dla Ciebie bezużyteczny. + 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,89 +1985,74 @@ 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): + \ No newline at end of file diff --git a/src/translations/bitmessage_pt.qm b/src/translations/bitmessage_pt.qm index 9c6b3402..6bf1240e 100644 Binary files a/src/translations/bitmessage_pt.qm and b/src/translations/bitmessage_pt.qm differ diff --git a/src/translations/bitmessage_pt.ts b/src/translations/bitmessage_pt.ts index 8c43b926..05be735a 100644 --- a/src/translations/bitmessage_pt.ts +++ b/src/translations/bitmessage_pt.ts @@ -1,5 +1,4 @@ - - + AddAddressDialog @@ -23,7 +22,7 @@ Email gateway - Email gateway + @@ -33,51 +32,51 @@ 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. - + Desired email address (including @mailchuck.com): - + EmailGatewayRegistrationDialog - + Registration failed: - + - + The requested email address is not available, please try a new one. Fill out the new desired email address (including @mailchuck.com) below: - + Email 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: - + @@ -124,90 +123,90 @@ Please type the desired email address (including @mailchuck.com) below: # the money directly. To turn it off again, set "feeamount" to 0. Requires # subscription. - + MainWindow - + Reply to sender - + - + Reply to channel - + + + + + Add sender to your Address Book + - Add sender to your Address Book - - - - Add sender to your Blacklist - + - + Move to Trash - + - + Undelete - + + + + + View HTML code as formatted text + - View HTML code as formatted text - + Save message as... + - Save message as... - - - - Mark Unread - + - + New - + Enable - + Disable - + Set avatar... - + Copy address to clipboard - + - + Special address behavior... - + - + Email gateway - Email gateway + @@ -215,34 +214,34 @@ Please type the desired email address (including @mailchuck.com) below: Apagar - + Send message to this address Enviar mensagem para esta morada - + Subscribe to this address Subscrever esta morada - + Add New Address Adiciona nova morada - + Copy destination address to clipboard Copia morada de destino para o 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? - + @@ -252,7 +251,7 @@ Please type the desired email address (including @mailchuck.com) below: Encryption key request queued. - + @@ -272,42 +271,42 @@ Please type the desired email address (including @mailchuck.com) below: Need to do work to send message. Work is queued. - + Acknowledgement of the message received %1 - + Broadcast queued. - + Broadcast on %1 - + Problem: The work demanded by the recipient is more difficult than you are willing to do. %1 - + Problem: The recipient's encryption key is no good. Could not encrypt message. %1 - + Forced difficulty override. Send should start soon. - + Unknown status: %1 %2 - + @@ -342,31 +341,31 @@ 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. - + You may manage your keys by editing the keys.dat file stored in %1 It is important that you back up this file. - + Open 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.) - + 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.) - + @@ -376,112 +375,112 @@ It is important that you back up this file. Would you like to open the file now? 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. - + Your address version number must be either 3 or 4. - + Chan name needed - + You didn't enter a chan name. - + Address already present - + Could not add chan because it appears to already be one of your identities. - + Success - + Successfully created chan. To let others join your chan, give them the chan name and this Bitmessage address: %1. This address also appears in 'Your Identities'. - + 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. - + Address invalid - + That Bitmessage address is not valid. - + Address does not match chan name - + Although the Bitmessage address you entered was valid, it doesn't match the chan name. - + Successfully joined chan. - + Connection lost - + Connected - + Message trashed - + @@ -489,516 +488,516 @@ It is important that you back up this file. Would you like to open the file now? 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. - + 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. - + 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. - + Error: Bitmessage addresses start with BM- Please check %1 - + Error: The address %1 is not typed or copied correctly. Please check it. - + Error: The address %1 contains invalid characters. Please check it. - + Error: The address version in %1 is too high. Either you need to upgrade your Bitmessage software or your acquaintance is being clever. - + Error: Some data encoded in the address %1 is too short. There might be something wrong with the software of your acquaintance. - + Error: Some data encoded in the address %1 is too long. There might be something wrong with the software of your acquaintance. - + Error: Some data encoded in the address %1 is malformed. There might be something wrong with the software of your acquaintance. - + Error: Something is wrong with the address %1. - + 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. - + Stream number - + Concerning the address %1, Bitmessage cannot handle stream numbers of %2. Perhaps upgrade Bitmessage to the latest 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. - + 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. - + - + New Message - + - + From - + - + Sending email gateway registration request - + Address is valid. - + The address you entered was invalid. Ignoring it. - + - + Error: You cannot add the same address to your address book twice. Try renaming the existing one if you want. - + - + Error: You cannot add the same address to your subscriptions twice. Perhaps rename the existing one if you want. - + - + Restart - + - + You must restart Bitmessage for the port number change to take effect. - + - + Bitmessage will use your proxy from now on but you may want to manually restart Bitmessage now to close existing connections (if any). - + - + Number needed - + - + Your maximum download and upload rate must be numbers. Ignoring what you typed. - + - + Will not resend ever - + - + Note that the time limit you entered is less than the amount of time Bitmessage waits for the first resend attempt therefore your messages will never be resent. - + - + Sending email gateway unregistration request - + - + Sending email gateway status request - + - + Passphrase mismatch - + - + The passphrase you entered twice doesn't match. Try again. - + - + Choose a passphrase - + - + You really do need a passphrase. - + - + Address is gone - + - + Bitmessage cannot find your address %1. Perhaps you removed it? - + - + 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... + - Testing... - - - - This is a chan address. You cannot use it as a pseudo-mailing list. - + - + The address should start with ''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). - + Bitmessage - + Identities - + New Identity - + Search - + All - + To - + From - + Subject - + Message - + Received - + Messages - + Address book - + Address - Morada + Add Contact - + Fetch Namecoin ID - + Subject: - + From: - + To: - + Send ordinary Message - + Send Message to your Subscribers - + TTL: - + Subscriptions - + Add new Subscription - + Chans - + Add Chan - + File - + Settings - Configurações + Help - + @@ -1018,12 +1017,12 @@ Are you sure you want to delete the channel? F1 - + Contact support - + @@ -1033,250 +1032,234 @@ Are you sure you want to delete the channel? Regenerate deterministic addresses - + Delete all trashed messages - + Join / Create chan - + All accounts - + Zoom level %1% - + Error: You cannot add the same address to your list twice. Perhaps rename the existing one if you want. - + Add new entry - Adiciona nova entrada + - + Display the %1 recent broadcast(s) from this address. - + New version of PyBitmessage is available: %1. Download it from https://github.com/Bitmessage/PyBitmessage/releases/latest - + - + Waiting for PoW to finish... %1% - + - + Shutting down Pybitmessage... %1% - + - + Waiting for objects to be sent... %1% - + - + Saving settings... %1% - + - + Shutting down core... %1% - + - + Stopping notifications... %1% - + - + Shutdown imminent... %1% - + %n hour(s) - - - - + - + %n day(s) - - - - + - + Shutting down PyBitmessage... %1% - + Sent - + Generating one new address - + Done generating address. Doing work necessary to broadcast it... - + Generating %1 new addresses. - + %1 is already in 'Your Identities'. Not adding it again. - + Done generating address - + - + SOCKS5 Authentication problem: %1 - + - + 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. - + Doing work necessary to send broadcast... - + Broadcast sent on %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 - + Doing work necessary to send message. There is no required difficulty for version 2 addresses like this. - + Doing work necessary to send message. Receiver's required difficulty: %1 and %2 - + Problem: The work demanded by the recipient (%1 and %2) is more difficult than you are willing to do. %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 - + Doing work necessary to send message. - + Message sent. Waiting for acknowledgement. Sent on %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 - + - + UPnP port mapping established on port %1 - + - + UPnP port mapping removed - - - - - Mark all messages as read - - - - - Are you sure you would like to mark all messages read? - + @@ -1284,98 +1267,98 @@ Receiver's required difficulty: %1 and %2 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> - + 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 - + Make deterministic addresses - + Address version number: 4 - + In addition to your passphrase, you must remember these numbers: - + Passphrase - + Number of addresses to make based on your passphrase: - + Stream number: 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) - + @@ -1388,17 +1371,17 @@ The 'Random Number' option is selected by default but deterministic ad Label - Rótulo + Address - Morada + Enter an address above. - + @@ -1406,77 +1389,73 @@ The 'Random Number' option is selected by default but deterministic ad 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: - - - - - Ui_aboutDialog - - - aboutDialog - <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 The Bitmessage Developers</p></body></html> - + aboutDialog - + About Acerca - + PyBitmessage - + - + version ? versão? - + <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> - + - + This is Beta software. Este software é Beta. + + + <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 The Bitmessage Developers</p></body></html> + + blacklist Use a Blacklist (Allow all incoming messages except those on the Blacklist) - + Use a Whitelist (Block all incoming messages except those on the Whitelist) - + @@ -1486,22 +1465,22 @@ The 'Random Number' option is selected by default but deterministic ad Name or Label - + Address - Morada + Blacklist - + Whitelist - + @@ -1509,12 +1488,12 @@ The 'Random Number' option is selected by default but deterministic ad Bitmessage - + Bitmessage won't connect to anyone until you let it. - + @@ -1524,7 +1503,7 @@ The 'Random Number' option is selected by default but deterministic ad Let me configure special network settings first - + @@ -1532,17 +1511,17 @@ The 'Random Number' option is selected by default but deterministic ad Help - + <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: - + @@ -1550,27 +1529,27 @@ The 'Random Number' option is selected by default but deterministic ad 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. - + 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. - + @@ -1578,127 +1557,112 @@ The 'Random Number' option is selected by default but deterministic ad Total connections: - + Since startup: - + Processed 0 person-to-person messages. - + Processed 0 public keys. - + Processed 0 broadcasts. - + Inventory lookups per second: 0 - + Objects to be synced: - + Stream # - + Connections - + Since startup on %1 - + Down: %1/s Total: %2 - + Up: %1/s Total: %2 - + Total Connections: %1 - + Inventory lookups per second: %1 - + Up: 0 kB/s - + Down: 0 kB/s - + Network Status - + byte(s) - - - - + Object(s) to be synced: %n - - - - + Processed %n person-to-person message(s). - - - - + Processed %n broadcast message(s). - - - - + Processed %n public key(s). - - - - + @@ -1706,42 +1670,42 @@ The 'Random Number' option is selected by default but deterministic ad Dialog - + Create a new chan - + Join a chan - + Create a chan - + <html><head/><body><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. If you and someone else both create a chan with the same chan name then it is currently very likely that they will be the same chan.</p></body></html> - + Chan name: - + <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 normal person-to-person message to the chan address.</p><p>Chans are experimental and completely unmoderatable.</p></body></html> - + Chan bitmessage address: - + @@ -1749,32 +1713,32 @@ The 'Random Number' option is selected by default but deterministic ad Regenerate Existing Addresses - + Regenerate existing addresses - + Passphrase - + Number of addresses to make based on your passphrase: - + Address version number: - + Stream number: - + @@ -1784,17 +1748,17 @@ The 'Random Number' option is selected by default but deterministic ad Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter - + 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. - + @@ -1807,118 +1771,118 @@ The 'Random Number' option is selected by default but deterministic ad 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. - + 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: - + Bandwidth limit - + Maximum download rate (kB/s): [0: unlimited] - + Maximum upload rate (kB/s): [0: unlimited] - + Proxy server / Tor - + Type: - + Server hostname: - + @@ -1938,12 +1902,12 @@ The 'Random Number' option is selected by default but deterministic ad Pass: - + Listen for incoming connections when using proxy - + @@ -1973,62 +1937,62 @@ The 'Random Number' option is selected by default but deterministic ad 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. - + 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 - + Hardware GPU acceleration (OpenCL) - + <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> - + Host: - + @@ -2063,12 +2027,12 @@ The 'Random Number' option is selected by default but deterministic ad <html><head/><body><p>By default, if you send a message to someone and he is offline for more than two days, Bitmessage will send the message again after an additional two days. This will be continued with exponential backoff forever; messages will be resent after 5, 10, 20 days ect. until the receiver acknowledges them. Here you may change that behavior by having Bitmessage give up after a certain number of days or months.</p><p>Leave these input fields blank for the default behavior. </p></body></html> - + Give up after - + @@ -2088,7 +2052,7 @@ The 'Random Number' option is selected by default but deterministic ad Resends Expire - + - + \ No newline at end of file diff --git a/src/translations/bitmessage_ru.qm b/src/translations/bitmessage_ru.qm index 8c0269b9..804afbcf 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..9e1510a3 100644 --- a/src/translations/bitmessage_ru.ts +++ b/src/translations/bitmessage_ru.ts @@ -2,17 +2,17 @@ AddAddressDialog - + Add new entry Добавить новую запись - + Label Имя - + Address Адрес @@ -20,93 +20,64 @@ EmailGatewayDialog - + Email gateway Email-шлюз - + Register on email gateway Зарегистрироваться на Email-шлюзе - + Account status at email gateway Статус аккаунта Email-шлюза - + Change account settings at email gateway Изменить настройки аккаунта Email-шлюза - + Unregister from email gateway Отменить регистрацию на Email-шлюзе - + Email gateway allows you to communicate with email users. Currently, only the Mailchuck email gateway (@mailchuck.com) is available. - Email-шлюз позволяет вам обмениваться сообщениями с пользователями обычной электронной почты. В настоящий момент доступен только шлюз Mailchuck (@mailchuck.com). + Email-шлюз позволяет вам обмениваться сообщениями с пользователями обычной электронной почты. В настоящий момент доступен только 1 шлюз Mailchuck (@mailchuck.com). - + Desired email address (including @mailchuck.com): - Желаемый email-адрес (включая @mailchuck.com) - - - - @mailchuck.com - @mailchuck.com - - - - Registration failed: - Регистрация не удалась: - - - - The requested email address is not available, please try a new one. - Запрашиваемый адрес email недоступен, попробуйте ввести другой. - - - - Sending email gateway registration request - Отправка запроса на регистрацию на Email-шлюзе - - - - Sending email gateway unregistration request - Отправка запроса на отмену регистрации на Email-шлюзе - - - - Sending email gateway status request - Отправка запроса статуса аккаунта на Email-шлюзе + Введите желаемый email-адрес (включая @mailchuck.com) EmailGatewayRegistrationDialog - + Registration failed: - + Регистрация не удалась: - + The requested email address is not available, please try a new one. Fill out the new desired email address (including @mailchuck.com) below: - + Запрашиваемый адрес email недоступен, попробуйте ввести другой. Введите желаемый адрес (включая @mailchuck.com) ниже: Email gateway registration - + Регистрация на Email-шлюзе Email gateway allows you to communicate with email users. Currently, only the Mailchuck email gateway (@mailchuck.com) is available. Please type the desired email address (including @mailchuck.com) below: - + Email-шлюз позволяет вам обмениваться сообщениями с пользователями обычной электронной почты. В настоящий момент доступен только 1 шлюз Mailchuck (@mailchuck.com). +Пожалуйста, введите желаемый адрес email (включая @mailchuck.com) ниже: @@ -153,102 +124,96 @@ 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 Новый адрес @@ -273,12 +238,12 @@ Please type the desired email address (including @mailchuck.com) below: Скопировать адрес в буфер обмена - + Special address behavior... Особое поведение адресов... - + Email gateway Email-шлюз @@ -288,281 +253,284 @@ Please type the desired email address (including @mailchuck.com) below: Удалить - + Send message to this address Отправить сообщение на этот адрес - + Subscribe to this address Подписаться на рассылку с этого адреса - + Add New Address Добавить новый адрес - + Copy destination address to clipboard Скопировать адрес отправки в буфер обмена - + Force send Форсировать отправку - + One of your addresses, %1, is an old version 1 address. Version 1 addresses are no longer supported. May we delete it now? Один из Ваших адресов, %1, является устаревшим адресом версии 1. Адреса версии 1 больше не поддерживаются. Хотите ли Вы удалить его сейчас? - + Waiting for their encryption key. Will request it again soon. Ожидаем ключ шифрования от Вашего собеседника. Запрос будет повторен через некоторое время. Encryption key request queued. - + Запрос ключа шифрования поставлен в очередь. - + Queued. В очереди. - + Message sent. Waiting for acknowledgement. Sent at %1 Сообщение отправлено. Ожидаем подтверждения. Отправлено в %1 - + Message sent. Sent at %1 Сообщение отправлено в %1 Need to do work to send message. Work is queued. - + Нужно провести требуемые вычисления, чтобы отправить сообщение. Вычисления ожидают очереди. - + Acknowledgement of the message received %1 - Доставлено в %1 + Сообщение доставлено в %1 - + Broadcast queued. Рассылка ожидает очереди. - + Broadcast on %1 Рассылка на %1 - + Problem: The work demanded by the recipient is more difficult than you are willing to do. %1 Проблема: Ваш получатель требует более сложных вычислений, чем максимум, указанный в Ваших настройках. %1 - + Problem: The recipient's encryption key is no good. Could not encrypt message. %1 Проблема: ключ получателя неправильный. Невозможно зашифровать сообщение. %1 - + Forced difficulty override. Send should start soon. Форсирована смена сложности. Отправляем через некоторое время. - + Unknown status: %1 %2 Неизвестный статус: %1 %2 - + Not Connected Не соединено - + Show Bitmessage Показать Bitmessage - + Send Отправить - + Subscribe Подписки - + Channel Канал - + Quit Выйти - + You may manage your keys by editing the keys.dat file stored in the same directory as this program. It is important that you back up this file. - Вы можете управлять Вашими ключами, редактируя файл keys.dat, находящийся в той же папке, что и эта программа. + Вы можете управлять Вашими ключами, отредактировав файл keys.dat, находящийся в той же папке, что и эта программа. Создайте резервную копию этого файла перед тем как будете его редактировать. - + You may manage your keys by editing the keys.dat file stored in %1 It is important that you back up this file. - Вы можете управлять Вашими ключами, редактируя файл keys.dat, находящийся в + Вы можете управлять Вашими ключами, отредактировав файл keys.dat, находящийся в %1 Создайте резервную копию этого файла перед тем как будете его редактировать. - + Open keys.dat? Открыть файл keys.dat? - + You may manage your keys by editing the keys.dat file stored in the same directory as this program. It is important that you back up this file. Would you like to open the file now? (Be sure to close Bitmessage before making any changes.) - Вы можете управлять вашими ключами, редактируя файл keys.dat, находящийся в той же папке, что и эта программа. -Создайте резервную копию этого файла перед тем как будете его редактировать. Хотите открыть этот файл сейчас? -(пожалуйста, закройте 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. - + Chan name needed - + Требуется имя chan-а - + You didn't enter a chan name. - + Вы не ввели имя chan-a. - + Address already present - + Адрес уже существует - + Could not add chan because it appears to already be one of your identities. - + Не могу добавить chan, потому что это один из Ваших уже существующих адресов. - + Success - + Отлично - + Successfully created chan. To let others join your chan, give them the chan name and this Bitmessage address: %1. This address also appears in 'Your Identities'. - - - - - 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. - - - - - Address invalid - - - - - That Bitmessage address is not valid. - - - - - Address does not match chan name - - - - - Although the Bitmessage address you entered was valid, it doesn't match the chan name. - - - - - Successfully joined chan. - + Chan был успешно создан. Чтобы добавить других в сhan, сообщите им имя chan-а и этот Bitmessage адрес: %1. Этот адрес также отображается во вкладке "Ваши Адреса". + 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. + Этот Bitmessage адрес похож на правильный, но версия этого адреса слишком новая. Возможно, Вам необходимо обновить программу Bitmessage. + + + + Address invalid + Неправильный адрес + + + + That Bitmessage address is not valid. + Этот Bitmessage адрес введен неправильно. + + + + Address does not match chan name + Адрес не сходится с именем chan-а + + + + Although the Bitmessage address you entered was valid, it doesn't match the chan name. + Вы ввели верный адрес Bitmessage, но он не сходится с именем chan-а. + + + + Successfully joined chan. + Успешно присоединились к chan-у. + + + 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,124 +541,124 @@ It is important that you back up this file. Would you like to open the file now? сообщение. Часто разумным вариантом будет установка TTL на 4 или 5 дней. - + Message too long Сообщение слишком длинное - + The message that you are trying to send is too long by %1 bytes. (The maximum is 261644 bytes). Please cut it down before sending. - Сообщение, которое вы пытаетесь отправить, длиннее максимально допустимого на %1 байт. (Максимально допустимое значение 261644 байта). Пожалуйста, сократите сообщение перед отправкой. + Сообщение, которое вы пытаетесь отправить, длиннее максимально допустимого на %1 байт. (Максимально допустимое значение 261644 байт). Пожалуйста, сократите сообщение перед отправкой. - + Error: Your account wasn't registered at an email gateway. Sending registration now as %1, please wait for the registration to be processed before retrying sending. Ошибка: ваш аккаунт не зарегистрирован на Email-шлюзе. Отправка регистрации %1, пожалуйста, подождите пока процесс регистрации не завершится, прежде чем попытаться отправить сообщение заново. - + Error: Bitmessage addresses start with BM- Please check %1 - + Ошибка: Bitmessage адреса начинаются с BM- Пожалуйста, проверьте %1 - + Error: The address %1 is not typed or copied correctly. Please check it. - + Ошибка: адрес %1 внесен или скопирован неправильно. Пожалуйста, перепроверьте. - + Error: The address %1 contains invalid characters. Please check it. - + Ошибка: адрес %1 содержит запрещённые символы. Пожалуйста, перепроверьте. - + Error: The address version in %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 address %1 is too short. There might be something wrong with the software of your acquaintance. - + Ошибка: некоторые данные, закодированные в адресе %1, слишком короткие. Возможно, что-то не так с программой Вашего собеседника. - + Error: Some data encoded in the address %1 is too long. There might be something wrong with the software of your acquaintance. - + Ошибка: некоторые данные, закодированные в адресе %1, слишком длинные. Возможно, что-то не так с программой Вашего собеседника. - + Error: Some data encoded in the address %1 is malformed. There might be something wrong with the software of your acquaintance. - + Ошибка: некоторые данные в адресе %1 имеют неверный формат. Это может быть ошибка программы, с которой вы работаете. - + Error: Something is wrong with the address %1. - + Ошибка: что-то не так с адресом %1. - + 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 Новое сообщение - + From - + От - + Sending email gateway registration request - + Отправка запроса на регистрацию на Email-шлюзе @@ -703,142 +671,142 @@ It is important that you back up this file. Would you like to open the file now? Вы ввели неправильный адрес. Это адрес проигнорирован. - + Error: You cannot add the same address to your address book twice. Try renaming the existing one if you want. Ошибка: Вы не можете добавлять один и тот же адрес в Адресную Книгу несколько раз. Попробуйте переименовать существующий адрес. - + Error: You cannot add the same address to your subscriptions twice. Perhaps rename the existing one if you want. Ошибка: вы не можете добавить один и тот же адрес в ваши подписки дважды. Пожалуйста, переименуйте имеющийся адрес, если хотите. - + Restart Перезапустить - + You must restart Bitmessage for the port number change to take effect. Вы должны перезапустить Bitmessage, чтобы смена номера порта имела эффект. - + Bitmessage will use your proxy from now on but you may want to manually restart Bitmessage now to close existing connections (if any). - Bitmessage будет использовать ваш прокси, начиная с этого момента. Тем не менее, имеет смысл перезапустить Bitmessage, чтобы закрыть уже существующие соединения. + Bitmessage будет использовать Ваш прокси, начиная прямо сейчас. Тем не менее Вам имеет смысл перезапустить Bitmessage, чтобы закрыть уже существующие соединения. - + Number needed Требуется число - + Your maximum download and upload rate must be numbers. Ignoring what you typed. Скорости загрузки и выгрузки должны быть числами. Игнорирую то, что вы набрали. - + Will not resend ever Не пересылать никогда - + Note that the time limit you entered is less than the amount of time Bitmessage waits for the first resend attempt therefore your messages will never be resent. Обратите внимание, что лимит времени, который вы ввели, меньше чем время, которое Bitmessage ждет перед первой попыткой переотправки сообщения, поэтому ваши сообщения никогда не будут переотправлены. - + Sending email gateway unregistration request - + Отправка запроса на отмену регистрации на Email-шлюзе - + Sending email gateway status request - + Отправка запроса статуса аккаунта на Email-шлюзе - + Passphrase mismatch Секретная фраза не подходит - + The passphrase you entered twice doesn't match. Try again. Вы ввели две разные секретные фразы. Пожалуйста, повторите заново. - + Choose a passphrase Придумайте секретную фразу - + You really do need a passphrase. Вы действительно должны ввести секретную фразу. - + Address is gone Адрес утерян - + Bitmessage cannot find your address %1. Perhaps you removed it? Bitmessage не может найти Ваш адрес %1. Возможно Вы удалили его? - + Address disabled Адрес выключен - + Error: The address from which you are trying to send is disabled. You'll have to enable it on the 'Your Identities' tab before using it. - Ошибка: адрес, с которого вы пытаетесь отправить, выключен. Вам нужно включить этот адрес во вкладке "Ваши адреса" перед использованием. + Ошибка: адрес, с которого Вы пытаетесь отправить, выключен. Вам нужно будет включить этот адрес во вкладке "Ваши адреса". - + Entry added to the Address Book. Edit the label to your liking. - + Запись добавлена в Адресную Книгу. Вы можете её отредактировать. - + Entry added to the blacklist. Edit the label to your liking. Запись добавлена в чёрный список. Измените название по своему вкусу. - + Error: You cannot add the same address to your blacklist twice. Try renaming the existing one if you want. Ошибка: вы не можете добавить один и тот же адрес в чёрный список дважды. Попробуйте переименовать существующий адрес. - + Moved items to trash. Удалено в корзину. - + Undeleted item. - Элемент восстановлен. + Отменить удаление записи - + Save As... Сохранить как ... - + Write error. Ошибка записи. - + No addresses selected. Вы не выбрали адрес. - + If you delete the subscription, messages that you already received will become inaccessible. Maybe you can consider disabling the subscription instead. Disabled subscriptions will not receive new messages, but you can still view messages you already received. Are you sure you want to delete the subscription? @@ -847,7 +815,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,287 +824,287 @@ Are you sure you want to delete the channel? Вы уверены, что хотите удалить канал? - + Do you really want to remove this avatar? Вы уверены, что хотите удалить этот аватар? - + You have already set an avatar for this address. Do you really want to overwrite it? У вас уже есть аватар для этого адреса. Вы уверены, что хотите перезаписать аватар? - + Start-on-login not yet supported on your OS. Запуск программы при входе в систему ещё не поддерживается в вашей операционной системе. - + Minimize-to-tray not yet supported on your OS. Сворачивание в трей ещё не поддерживается в вашей операционной системе. - + Tray notifications not yet supported on your OS. Уведомления в трее ещё не поддерживаеются в вашей операционной системе. - + Testing... Проверяем... - + This is a chan address. You cannot use it as a pseudo-mailing list. - + Это адрес chan-а. Вы не можете его использовать как адрес рассылки. - + The address should start with ''BM-'' Адрес должен начинаться с "BM-" - + The address is not typed or copied correctly (the checksum failed). Адрес введен или скопирован неверно (контрольная сумма не сходится). - + The version number of this address is higher than this software can support. Please upgrade Bitmessage. - Версия этого адреса более поздняя, чем те, что поддерживает программа. Пожалуйста, обновите Bitmessage. + Версия этого адреса более поздняя, чем Ваша программа. Пожалуйста, обновите программу Bitmessage. - + The address contains invalid characters. Адрес содержит запрещённые символы. - + Some data encoded in the address is too short. Данные, закодированные в адресе, слишком короткие. - + Some data encoded in the address is too long. Данные, закодированные в адресе, слишком длинные. - + Some data encoded in the address is malformed. Данные, закодированные в адресе, имеют неверный формат. - + Enter an address above. - + Введите адрес выше. - + Address is an old type. We cannot display its past broadcasts. Адрес старого типа. Мы не можем отобразить его прошлые рассылки. - + There are no recent broadcasts from this address to display. Нет недавних рассылок с этого адреса для отображения. - + You are using TCP port %1. (This can be changed in the settings). - + Вы используете TCP порт %1. (Его можно поменять в настройках). - + Bitmessage Bitmessage - + Identities Адреса - + New Identity Создать новый адрес - + Search Поиск - + All По всем полям - + To Кому - + From От кого - + Subject Тема - + Message Текст сообщения - + Received Получено - + Messages Сообщения - + Address book Адресная книга - + Address Адрес - + Add Contact Добавить контакт - + Fetch Namecoin ID Получить Namecoin ID - + Subject: Тема: - + From: От: - + To: Кому: - + Send ordinary Message - Отправить обычное сообщение + Отправить обыкновенное сообщение - + Send Message to your Subscribers Отправить сообщение для ваших подписчиков - + TTL: TTL: - + Subscriptions Подписки - + Add new Subscription Добавить новую подписку - + Chans - Чаны + Chan-ы - + Add Chan - Добавить чан + Добавить chan - + File Файл - + Settings Настройки - + Help Помощь - + Import keys Импортировать ключи - + Manage keys Управлять ключами - + Ctrl+Q Ctrl+Q - + F1 F1 - + Contact support Связаться с поддержкой - + About О программе - + Regenerate deterministic addresses Сгенерировать заново все адреса - + Delete all trashed messages Стереть все сообщения из корзины - + Join / Create chan - Подключить или создать чан + Подсоединиться или создать chan - + All accounts Все аккаунты - + Zoom level %1% Увеличение %1% @@ -1151,592 +1119,300 @@ Are you sure you want to delete the channel? Добавить новую запись - + Display the %1 recent broadcast(s) from this address. - + Показать %1 прошлых рассылок с этого адреса. - + New version of PyBitmessage is available: %1. Download it from https://github.com/Bitmessage/PyBitmessage/releases/latest - Доступна новая версия PyBitmessage: %1. Загрузите её: https://github.com/Bitmessage/PyBitmessage/releases/latest + Доступка новая версия PyBitmessage: %1. Загрузите её: https://github.com/Bitmessage/PyBitmessage/releases/latest - + Waiting for PoW to finish... %1% Ожидание окончания PoW... %1% - + Shutting down Pybitmessage... %1% Завершение PyBitmessage... %1% - + Waiting for objects to be sent... %1% Ожидание отправки объектов... %1% - + Saving settings... %1% Сохранение настроек... %1% - + Shutting down core... %1% Завершение работы ядра... %1% - + Stopping notifications... %1% Остановка сервиса уведомлений... %1% - + Shutdown imminent... %1% Завершение вот-вот произойдет... %1% - + %n hour(s) - %n час%n часа%n часов%n час(а/ов) + %n час%n час(а/ов)%n час(а/ов)%n час(а/ов) - + %n day(s) - %n день%n дня%n дней%n дней + %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 Создание адресов завершено. - + SOCKS5 Authentication problem: %1 - + Проблема аутентификации SOCKS5: %1 - + 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 + Проблема: вы пытаетесь отправить сообщение самому себе или в chan, но ваш ключ шифрования не найден в файле ключей keys.dat. Невозможно зашифровать сообщение. %1 - + Doing work necessary to send message. - Выполнение работы, требуемой для отправки сообщения. + Выполнение задач, необходимых для отправки сообщения. - + Message sent. Waiting for acknowledgement. Sent on %1 - Отправлено. Ожидаем подтверждения. Отправлено в %1 + Сообщение отправлено. Ожидаем подтверждения. Отправлено на %1 - + Doing work necessary to request encryption key. - Выполнение работы, требуемой для запроса ключа шифрования. + Выполнение задач, необходимых для запроса ключа шифрования. - + Broadcasting the public key request. This program will auto-retry if they are offline. Рассылка запросов открытого ключа шифрования. Программа будет повторять попытки, если они оффлайн. - + Sending public key request. Waiting for reply. Requested at %1 Отправка запроса открытого ключа шифрования. Ожидание ответа. Запрошено в %1 - + UPnP port mapping established on port %1 Распределение портов UPnP завершилось выделением порта %1 - + UPnP port mapping removed Распределение портов UPnP отменено - - - Mark all messages as read - Отметить все сообщения как прочтенные - - - - Are you sure you would like to mark all messages read? - Вы уверены, что хотите отметить все сообщения как прочтенные? - - - - Doing work necessary to send broadcast. - Выполнение работы, требуемой для отправки рассылки. - - - - Proof of work pending - Ожидается доказательство работы - - - - %n object(s) pending proof of work - %n объект в ожидании доказательства работы%n объекта в ожидании доказательства работы%n объектов в ожидании доказательства работы%n объектов в ожидании доказательства работы - - - - %n object(s) waiting to be distributed - %n объект ожидает раздачи%n объекта ожидают раздачи%n объектов ожидают раздачи%n объектов ожидают раздачи - - - - Wait until these tasks finish? - Подождать завершения этих задач? - - - - 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 - * send broadcast messages like twitter or - * discuss in chan(nel)s with other people - - -Добро пожаловать в простой и безопасный Bitmessage -* отправляйте сообщения другим людям -* вещайте, как в twitter или -* участвуйте в обсуждениях в чанах - - - - not recommended for chans - не рекомендовано для чанов - - - - Quiet Mode - Тихий режим - - - - Problems connecting? Try enabling UPnP in the Network Settings - Проблемы подключения? Попробуйте включить UPnP в сетевых настройках. - - - - You are trying to send an email instead of a bitmessage. This requires registering with a gateway. Attempt to register? - Вы пытаетесь отправить email вместо bitmessage. Для этого нужно зарегистрироваться на шлюзе. Попробовать зарегистрироваться? - - - - Error: Bitmessage addresses start with BM- Please check the recipient address %1 - Ошибка: адреса Bitmessage начинаются с "BM-". Пожалуйста, проверьте адрес получателя %1. - - - - Error: The recipient address %1 is not typed or copied correctly. Please check it. - Ошибка: адрес получателя %1 набран или скопирован неправильно. Пожалуйста, проверьте его. - - - - Error: The recipient address %1 contains invalid characters. Please check it. - Ошибка: адрес получателя %1 содержит недопустимые символы. Пожалуйста, проверьте его. - - - - Error: The version of the recipient address %1 is too high. Either you need to upgrade your Bitmessage software or your acquaintance is being clever. - Ошибка: версия адреса получателя %1 слишком высокая. Либо вам нужно обновить программу Bitmessage, либо ваш знакомый - умник. - - - - Error: Some data encoded in the recipient address %1 is too short. There might be something wrong with the software of your acquaintance. - Ошибка: часть данных, закодированных в адресе получателя %1 слишком короткая. Видимо, что-то не так с программой, используемой вашим знакомым. - - - - Error: Some data encoded in the recipient address %1 is too long. There might be something wrong with the software of your acquaintance. - Ошибка: часть данных, закодированных в адресе получателя %1 слишком длинная. Видимо, что-то не так с программой, используемой вашим знакомым. - - - - Error: Some data encoded in the recipient address %1 is malformed. There might be something wrong with the software of your acquaintance. - Ошибка: часть данных, закодированных в адресе получателя %1 сформирована неправильно. Видимо, что-то не так с программой, используемой вашим знакомым. - - - - Error: Something is wrong with the recipient address %1. - Ошибка: что-то не так с адресом получателя %1. - - - - Error: %1 - Ошибка: %1 - - - - From %1 - От %1 - - - - Synchronisation pending - Ожидается синхронизация - - - - Bitmessage hasn't synchronised with the network, %n object(s) to be downloaded. If you quit now, it may cause delivery delays. Wait until the synchronisation finishes? - Bitmessage не синхронизирован с сетью, незагруженных объектов: %n. Выход сейчас может привести к задержкам доставки. Подождать завершения синхронизации?Bitmessage не синхронизирован с сетью, незагруженных объектов: %n. Выход сейчас может привести к задержкам доставки. Подождать завершения синхронизации?Bitmessage не синхронизирован с сетью, незагруженных объектов: %n. Выход сейчас может привести к задержкам доставки. Подождать завершения синхронизации?Bitmessage не синхронизирован с сетью, незагруженных объектов: %n. Выход сейчас может привести к задержкам доставки. Подождать завершения синхронизации? - - - - Not connected - Не подключено - - - - Bitmessage isn't connected to the network. If you quit now, it may cause delivery delays. Wait until connected and the synchronisation finishes? - Bitmessage не подключен к сети. Выход сейчас может привести к задержкам доставки. Подождать подключения и завершения синхронизации? - - - - Waiting for network connection... - Ожидание сетевого подключения... - - - - Waiting for finishing synchronisation... - Ожидание окончания синхронизации... - - - - You have already set a notification sound for this address book entry. Do you really want to overwrite it? - У вас уже есть звук уведомления для этого адресата. Вы уверены, что хотите перезаписать звук уведомления? - - - - Error occurred: could not load message from disk. - Произошла ошибка: не удалось загрузить сообщение с диска. - - - - Display the %n recent broadcast(s) from this address. - Показать %1 прошлую рассылку с этого адреса.Показать %1 прошлых рассылки с этого адреса.Показать %1 прошлых рассылок с этого адреса.Показать %1 прошлых рассылок с этого адреса. - - - - 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 - Нажмите здесь для отключения - - - - MsgDecode - - - The message has an unknown encoding. -Perhaps you should upgrade Bitmessage. - Сообщение в неизвестной кодировке. -Возможно, вам следует обновить Bitmessage. - - - - Unknown encoding - Неизвестная кодировка - NewAddressDialog - + Create new Address Создать новый адрес - + Here you may generate as many addresses as you like. Indeed, creating and abandoning addresses is encouraged. You may generate addresses by using either random numbers or by using a passphrase. If you use a passphrase, the address is called a "deterministic" address. The 'Random Number' option is selected by default but deterministic addresses have several pros and cons: Здесь Вы сможете сгенерировать столько адресов сколько хотите. На самом деле, создание и выкидывание адресов даже поощряется. Вы можете сгенерировать адреса используя либо генератор случайных чисел, либо придумав секретную фразу. Если Вы используете секретную фразу, то адреса будут называться "детерминистическими". Генератор случайных чисел выбран по умолчанию, однако детерминистические адреса имеют следующие плюсы и минусы по сравнению с ними: - + <html><head/><body><p><span style=" font-weight:600;">Pros:<br/></span>You can recreate your addresses on any computer from memory. <br/>You need-not worry about backing up your keys.dat file as long as you can remember your passphrase. <br/><span style=" font-weight:600;">Cons:<br/></span>You must remember (or write down) your passphrase if you expect to be able to recreate your keys if they are lost. <br/>You must remember the address version number and the stream number along with your passphrase. <br/>If you choose a weak passphrase and someone on the Internet can brute-force it, they can read your messages and send messages as you.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Плюсы:<br/></span>Вы сможете восстановить адрес по памяти на любом компьютере<br/>Вам не нужно беспокоиться о сохранении файла keys.dat, если Вы запомнили секретную фразу<br/><span style=" font-weight:600;">Минусы:<br/></span>Вы должны запомнить (или записать) секретную фразу, если Вы хотите когда-либо восстановить Ваш адрес на другом компьютере <br/>Вы должны также запомнить версию адреса и номер потока вместе с секретной фразой<br/>Если Вы выберите слишком короткую секретную фразу, кто-нибудь в интернете сможет подобрать ключ и, как следствие, читать и отправлять от Вашего имени сообщения.</p></body></html> - + Use a random number generator to make an address Использовать генератор случайных чисел для создания адреса - + Use a passphrase to make addresses Использовать секретную фразу для создания адресов - + Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter Потратить несколько лишних минут, чтобы сделать адрес(а) короче на 1 или 2 символа - + Make deterministic addresses - Создать детерминистические адреса + Создать детерминистический адрес - + Address version number: 4 Номер версии адреса: 4 - + In addition to your passphrase, you must remember these numbers: В дополнение к секретной фразе, Вам необходимо запомнить эти числа: - + Passphrase - Секретная фраза + Придумайте секретную фразу - + Number of addresses to make based on your passphrase: - Количество адресов, которые нужно создать из секретной фразы: + Кол-во адресов, которые Вы хотите получить из секретной фразы: - + Stream number: 1 Номер потока: 1 - + Retype passphrase Повторите секретную фразу - + Randomly generate address Сгенерировать случайный адрес - + Label (not shown to anyone except you) Имя (не показывается никому кроме Вас) - + Use the most available stream Использовать наиболее доступный поток - + (best if this is the first of many addresses you will create) (выберите этот вариант если это лишь первый из многих адресов, которые Вы планируете создать) - + Use the same stream as an existing address Использовать тот же поток, что и указанный существующий адрес - + (saves you some bandwidth and processing power) (немного сэкономит Вам пропускную способность сети и вычислительную мощь) @@ -1744,22 +1420,22 @@ The 'Random Number' option is selected by default but deterministic ad NewSubscriptionDialog - + Add new entry Добавить новую запись - + Label Имя - + Address Адрес - + Enter an address above. Введите адрес выше. @@ -1767,72 +1443,62 @@ The 'Random Number' option is selected by default but deterministic ad SpecialAddressBehaviorDialog - + Special Address Behavior Особое поведение адреса - + Behave as a normal address Вести себя как обычный адрес - + Behave as a pseudo-mailing-list address Вести себя как адрес псевдо-рассылки - + Mail received to a pseudo-mailing-list address will be automatically broadcast to subscribers (and thus will be public). - Почта, полученная на адрес псевдо-рассылки, будет автоматически разослана всем подписчикам (и поэтому будет публичной). + Почта, полученная на адрес псевдо-рассылки, будет автоматически разослана всем подписчикам (и поэтому будет доступна общей публике). - + Name of the pseudo-mailing-list: Имя псевдо-рассылки: - - - This is a chan address. You cannot use it as a pseudo-mailing list. - Это адрес чана. Вы не можете его использовать как адрес рассылки. - aboutDialog - + About О программе - + PyBitmessage - + PyBitmessage - + version ? - + версия ? - + <html><head/><body><p>Distributed under the MIT/X11 software license; see <a href="http://www.opensource.org/licenses/mit-license.php"><span style=" text-decoration: underline; color:#0000ff;">http://www.opensource.org/licenses/mit-license.php</span></a></p></body></html> <html><head/><body><p>Программа распространяется в соответствии с лицензией MIT/X11; см. <a href="http://www.opensource.org/licenses/mit-license.php"><span style=" text-decoration: underline; color:#0000ff;">http://www.opensource.org/licenses/mit-license.php</span></a></p></body></html> - + This is Beta software. Это бета версия программы. - + <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>Авторское право: &copy; 2012-2016 Джонатан Уоррен<br/>Авторское право: &copy; 2013-2017 Разработчики Bitmessage</p></body></html> + <html><head/><body><p>Все права защищены © 2012-2016 Jonathan Warren<br/>Все права защищены © 2013-2016 Разработчики Bitmessage</p></body></html> @@ -1840,7 +1506,7 @@ The 'Random Number' option is selected by default but deterministic ad Use a Blacklist (Allow all incoming messages except those on the Blacklist) - Использовать чёрный список (разрешить все входящие сообщения, кроме указанных в чёрном списке) + Использовать чёрный список (Разрешить все входящие сообщения, кроме указанных в чёрном списке) @@ -1876,45 +1542,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 - общественный проект. Вы можете найти подсказки и советы на Wiki-страничке Bitmessage: @@ -1922,35 +1583,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 соединений к Вашему компьютеру. Bitmessage будет прекрасно работать и без этого, но Вы могли бы помочь сети если бы разрешили и входящие соединения тоже. Это помогло бы Вам стать более важным узлом сети. + На текущий момент Вы установили по-крайней мере одно исходящее соединение, но пока ни одного входящего. Ваш файрвол или маршрутизатор скорее всего не настроен на переброс входящих TCP соединений к Вашему компьютеру. Bitmessage будет прекрасно работать и без этого, но Вы могли бы помочь сети если бы разрешили и входящие соединения тоже. Это помогло бы Вам стать более важным узлом сети. You are using TCP port ?. (This can be changed in the settings). - + Вы используете TCP порт ?. (Его можно поменять в настройках). - + You do have connections with other peers and your firewall is correctly configured. Вы установили соединение с другими участниками сети и ваш файрвол настроен правильно. - - - You are using TCP port %1. (This can be changed in the settings). - Вы используете TCP порт %1. (Его можно поменять в настройках). - networkstatus @@ -1960,154 +1616,109 @@ The 'Random Number' option is selected by default but deterministic ad Всего соединений: - + Since startup: С начала работы: - + Processed 0 person-to-person messages. Обработано 0 сообщений. - + Processed 0 public keys. Обработано 0 открытых ключей. - + Processed 0 broadcasts. Обработано 0 рассылок. - + Inventory lookups per second: 0 Поисков в каталоге в секунду: 0 - + Objects to be synced: Несинхронизированные объекты: - + Stream # № потока Connections - + Соединений - + Since startup on %1 - С начала работы, %1 + С начала работы в %1 - + Down: %1/s Total: %2 Загрузка: %1/s Всего: %2 - + Up: %1/s Total: %2 Отправка: %1/s Всего: %2 - + Total Connections: %1 Всего соединений: %1 - + Inventory lookups per second: %1 Поисков в каталоге в секунду: %1 - + Up: 0 kB/s Отправка: 0 кБ/с - + Down: 0 kB/s Загрузка: 0 кБ/с - + Network Status - Состояние сети + Статус сети - + byte(s) байтбайтбайтбайт - + Object(s) to be synced: %n Несинхронизированные объекты: %nНесинхронизированные объекты: %nНесинхронизированные объекты: %nНесинхронизированные объекты: %n - + Processed %n person-to-person message(s). - Обработано %n сообщение.Обработано %n сообщения.Обработано %n сообщений.Обработано %n сообщений. + Обработано %n сообщение.Обработано %n сообщений.Обработано %n сообщений.Обработано %n сообщений. - + Processed %n broadcast message(s). - Обработана %n рассылка.Обработано %n рассылки.Обработано %n рассылок.Обработано %n рассылок. + Обработана %n рассылка.Обработано %n рассылок.Обработано %n рассылок.Обработано %n рассылок. - + Processed %n public key(s). - Обработан %n открытый ключ.Обработано %n открытых ключа.Обработано %n открытых ключей.Обработано %n открытых ключей. - - - - 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 - Перечень потоков, согласованных с конкретным узлом + Обработан %n открытый ключ.Обработано %n открытых ключей.Обработано %n открытых ключей.Обработано %n открытых ключей. @@ -2115,470 +1726,389 @@ The 'Random Number' option is selected by default but deterministic ad Dialog - + Новый chan Create a new chan - + Создать новый chan Join a chan - + Присоединиться к chan Create a chan - + Создать chan <html><head/><body><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. If you and someone else both create a chan with the same chan name then it is currently very likely that they will be the same chan.</p></body></html> - + <html><head/><body><p>Введите имя Вашего chan-a. Если Вы выберете достаточно сложное имя для chan-а (например, сложную и необычную секретную фразу) и никто из Ваших друзей не опубликует эту фразу, то Ваш chan будет надёжно зашифрован. Если Вы и кто-то другой независимо создадите chan с полностью идентичным именем, то скорее всего Вы получите в итоге один и тот же chan.</p></body></html> Chan name: - + Имя chan: <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 normal person-to-person message to the chan address.</p><p>Chans are experimental and completely unmoderatable.</p></body></html> - + <html><head/><body><p>Chan - это способ общения, когда набор ключей шифрования известен сразу целой группе людей. Ключи и Bitmessage-адрес, используемый chan-ом, генерируется из слова или фразы (имя chan-а). Чтобы отправить сообщение всем, находящимся в chan-е, отправьте обычное приватное сообщение на адрес chan-a.</p><p>Chan-ы — это экспериментальная фича.</p></body></html> Chan bitmessage address: - - - - - Create or join a chan - Создать или подключить чан - - - - <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> - - - - Chan passphrase/name: - Пароль/имя чана: - - - - Optional, for advanced usage - Необязательно, для продвинутых пользователей - - - - Chan address - Адрес чана - - - - Please input chan name/passphrase: - Пожалуйста, введите имя/пароль чана: - - - - newchandialog - - - Successfully created / joined chan %1 - Успешно создан / подключен чан %1 - - - - Chan creation / joining failed - Не удалось создать / подключить чан - - - - Chan creation / joining cancelled - Создание / подключение чана отменено - - - - proofofwork - - - C PoW module built successfully. - Модуль C для PoW успешно собран. - - - - Failed to build C PoW module. Please build it manually. - Не удалось собрать модуль C для PoW. Пожалуйста, соберите его вручную. - - - - C PoW module unavailable. Please build it. - Модуль C для PoW недоступен. Пожалуйста, соберите его. + Bitmessage адрес chan: 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. - Если Вы ранее создали детерминистические адреса, но случайно потеряли их, Вы можете их восстановить здесь. Если же Вы использовали генератор случайных чисел чтобы создать Ваши адреса, то Вы не сможете их здесь восстановить. + Если Вы ранее создавали детерминистические адреса, но случайно потеряли их, Вы можете их восстановить здесь. Если же Вы использовали генератор случайных чисел чтобы создать Ваши адреса, то Вы не сможете их здесь восстановить. 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 Макс допустимая сложность Hardware GPU acceleration (OpenCL) - + Ускорение на графическом процессоре (GPU), используя OpenCL - + <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> + <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 - \ No newline at end of file diff --git a/src/translations/bitmessage_sk.qm b/src/translations/bitmessage_sk.qm index 26c2a24d..0410eac8 100644 Binary files a/src/translations/bitmessage_sk.qm and b/src/translations/bitmessage_sk.qm differ diff --git a/src/translations/bitmessage_sk.ts b/src/translations/bitmessage_sk.ts index 8c3b0209..25f87902 100644 --- a/src/translations/bitmessage_sk.ts +++ b/src/translations/bitmessage_sk.ts @@ -2,17 +2,17 @@ AddAddressDialog - + Add new entry Pridať nový záznam - + Label Označenie - + Address Adresa @@ -20,99 +20,70 @@ EmailGatewayDialog - + Email gateway E-mailová brána - + Register on email gateway Registrácia na e-mailovej bráne - + Account status at email gateway Stav účtu na e-mailovej bráne - + Change account settings at email gateway Zmena nastavení účtu na e-mailovej bráne - + Unregister from email gateway Zrušiť registráciu na e-mailovej bráne - + Email gateway allows you to communicate with email users. Currently, only the Mailchuck email gateway (@mailchuck.com) is available. E-mailové brány umožňujú komunikovať s užívateľmi e-mailu. Momentálne je k dispozícii iba e-mailová brána Mailchuck (@mailchuck.com). - + Desired email address (including @mailchuck.com): Požadovaná e-mailová adresa (vrátane @ mailchuck.com): - - - @mailchuck.com - @mailchuck.com - - - - Registration failed: - Registrácia zlyhala: - - - - The requested email address is not available, please try a new one. - Požadovaná e-mailová adresa nie je k dispozícii, skúste znova. - - - - Sending email gateway registration request - Odosielam žiadosť o registráciu na e-mailovej bráne - - - - Sending email gateway unregistration request - Odosielam žiadosť o odhlásenie z e-mailovej brány - - - - Sending email gateway status request - Odosielam žiadosť o stav e-mailovej brány - EmailGatewayRegistrationDialog - + Registration failed: - + Registrácia zlyhala: - + The requested email address is not available, please try a new one. Fill out the new desired email address (including @mailchuck.com) below: - + Požadovaná e-mailová adresa nie je k dispozícii, skúste znova. Vyplňte novú požadovanú e-mailovú adresu (vrátane @mailchuck.com) nižšie: Email gateway registration - + Registrácia na e-mailovej bráne Email gateway allows you to communicate with email users. Currently, only the Mailchuck email gateway (@mailchuck.com) is available. Please type the desired email address (including @mailchuck.com) below: - + E-mailové brány umožňujú komunikovať s užívateľmi e-mailu. Momentálne je k dispozícii iba e-mailová brána Mailchuck (@mailchuck.com). +Vyplňte požadovanú e-mailovú adresu (vrátane @mailchuck.com) nižšie: Mailchuck - + # You can use this to configure your email gateway account # Uncomment the setting you want to use # Here are the options: @@ -200,222 +171,222 @@ Please type the desired email address (including @mailchuck.com) below: MainWindow - + Reply to sender Odpovedať odosielateľovi - + Reply to channel Odpoveď na kanál - + Add sender to your Address Book Pridať odosielateľa do adresára - + Add sender to your Blacklist Pridať odosielateľa do svojho zoznamu zakázaných - + Move to Trash Presunúť do koša - + Undelete Obnoviť - + View HTML code as formatted text Zobraziť HTML kód ako formátovaný text - + Save message as... Uložiť správu ako... - + Mark Unread Označiť ako neprečítané - + New Nová - + Enable Aktivovať - + Disable Deaktivovať - + Set avatar... Nastaviť avatar ... - + Copy address to clipboard Kopírovať adresu do clipboardu - + Special address behavior... Zvláštne správanie adresy... - + Email gateway E-mailová brána - + Delete Zmazať - + Send message to this address Poslať správu na túto adresu - + Subscribe to this address Prihlásiť sa k odberu tejto adresy - + Add New Address Pridať novú adresu - + Copy destination address to clipboard Kopírovať cieľovú adresu do clipboardu - + Force send Vynútiť odoslanie - + One of your addresses, %1, is an old version 1 address. Version 1 addresses are no longer supported. May we delete it now? Jedna z vašich adries, %1, je stará verzia adresy, 1. Verzie adresy 1 už nie sú podporované. Odstrániť ju teraz? - + Waiting for their encryption key. Will request it again soon. Čakanie na šifrovací kľúč príjemcu. Čoskoro bude vyžiadaný znova. Encryption key request queued. - + Požiadavka na šifrovací kľúč je vo fronte. - + Queued. Vo fronte. - + Message sent. Waiting for acknowledgement. Sent at %1 Správa odoslaná. Čakanie na potvrdenie. Odoslaná %1 - + Message sent. Sent at %1 Správa odoslaná. Odoslaná %1 Need to do work to send message. Work is queued. - + Potrebné vykonať prácu pre odoslanie správy. Práca je vo fronte. - + Acknowledgement of the message received %1 Potvrdenie prijatia správy %1 - + Broadcast queued. Rozoslanie vo fronte. - + Broadcast on %1 Rozoslané 1% - + Problem: The work demanded by the recipient is more difficult than you are willing to do. %1 Problém: práca požadovná príjemcom je oveľa ťažšia, než je povolené v nastaveniach. %1 - + Problem: The recipient's encryption key is no good. Could not encrypt message. %1 Problém: šifrovací kľúč príjemcu je nesprávny. Nie je možné zašifrovať správu. %1 - + Forced difficulty override. Send should start soon. Obmedzenie obtiažnosti práce zrušené. Odosielanie by malo čoskoro začať. - + Unknown status: %1 %2 Neznámy stav: %1 %2 - + Not Connected Nepripojený - + Show Bitmessage Ukázať Bitmessage - + Send Odoslať - + Subscribe Prihlásiť sa k odberu - + Channel Kanál - + Quit Ukončiť - + You may manage your keys by editing the keys.dat file stored in the same directory as this program. It is important that you back up this file. Kľúče môžete spravovať úpravou súboru keys.dat, ktorý je uložený v rovnakom adresári ako tento program. Tento súbor je dôležité zálohovať. - + You may manage your keys by editing the keys.dat file stored in %1 It is important that you back up this file. @@ -424,17 +395,17 @@ It is important that you back up this file. Tento súbor je dôležité zálohovať. - + Open keys.dat? Otvoriť keys.dat? - + You may manage your keys by editing the keys.dat file stored in the same directory as this program. It is important that you back up this file. Would you like to open the file now? (Be sure to close Bitmessage before making any changes.) Kľúče môžete spravovať úpravou súboru keys.dat, ktorý je uložený v rovnakom adresári ako tento program. Tento súbor je dôležité zálohovať. Chcete tento súbor teraz otvoriť? (Nezabudnite zatvoriť Bitmessage pred vykonaním akýchkoľvek zmien.) - + You may manage your keys by editing the keys.dat file stored in %1 It is important that you back up this file. Would you like to open the file now? (Be sure to close Bitmessage before making any changes.) @@ -443,122 +414,122 @@ It is important that you back up this file. Would you like to open the file now? Tento súbor je dôležité zálohovať. Chcete tento súbor teraz otvoriť? (Nezabudnite zatvoriť Bitmessage pred vykonaním akýchkoľvek zmien.) - + Delete trash? Vyprázdniť kôš? - + Are you sure you want to delete all trashed messages? Ste si istí, že chcete všetky správy z koša odstrániť? - + bad passphrase zlé heslo - + You must type your passphrase. If you don't have one then this is not the form for you. Je nutné zadať prístupové heslo. Ak heslo nemáte, tento formulár nie je pre Vás. - + Bad address version number Nesprávne číslo verzie adresy - + Your address version number must be a number: either 3 or 4. Číslo verzie adresy musí byť číslo: buď 3 alebo 4. - + Your address version number must be either 3 or 4. Vaše číslo verzie adresy musí byť buď 3 alebo 4. - + Chan name needed - + Potrebný názov kanálu + + + + You didn't enter a chan name. + Nezadali ste meno kanálu. + + + + Address already present + Adresa už existuje + + + + Could not add chan because it appears to already be one of your identities. + Nemožno pridať kanál, pretože sa zdá, že už existuje ako jedna z vašich identít. + + + + Success + Úspešné + + + + Successfully created chan. To let others join your chan, give them the chan name and this Bitmessage address: %1. This address also appears in 'Your Identities'. + Úspešne vytvorený kanál. Ak chcete umožniť ostatným pripojiť váš kanál, dajte im meno kanálu a túto Bitmessage adresu: %1. Táto adresa sa objavuje aj vo "Vaše identity". + + + + Address too new + Adresa príliš nová + + + + Although that Bitmessage address might be valid, its version number is too new for us to handle. Perhaps you need to upgrade Bitmessage. + Aj keď Bitmessage adresa vyzerá byť platná, číslo verzie je príliš nové pre tento program. Možno budete musieť upgradovať Bitmessage. - You didn't enter a chan name. - - - - - Address already present - - - - - Could not add chan because it appears to already be one of your identities. - - - - - Success - - - - - Successfully created chan. To let others join your chan, give them the chan name and this Bitmessage address: %1. This address also appears in 'Your Identities'. - - - - - 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. - - - - Address invalid - + Adresa neplatná + + + + That Bitmessage address is not valid. + Táto Bitmessage adresa nie je platná. - That Bitmessage address is not valid. - - - - Address does not match chan name - + Adresa nezodpovedá názvu kanálu + + + + Although the Bitmessage address you entered was valid, it doesn't match the chan name. + Hoci zadaná Bitmessage adresa je platná, nezodpovedá názvu kanálu. - Although the Bitmessage address you entered was valid, it doesn't match the chan name. - - - - Successfully joined chan. - + Úspešné pripojenie na kanál. - + Connection lost Spojenie bolo stratené - + Connected Spojený - + Message trashed Správa odstránenia - + The TTL, or Time-To-Live is the length of time that the network will hold the message. The recipient must get it during this time. If your Bitmessage client does not hear an acknowledgement, it will resend the message automatically. The longer the Time-To-Live, the @@ -566,272 +537,272 @@ Tento súbor je dôležité zálohovať. Chcete tento súbor teraz otvoriť? (Ne TTL (doba životnosti) je čas, počas ktorého bude sieť udržiavať správu. Príjemca musí správu prijať počas tejto životnosti. Keď odosielateľov Bitmessage nedostane po vypršaní životnosti potvrdenie o prijatí, automaticky správu odošle znova. Čím vyššia doba životnosti, tým viac práce musí počítač odosielateľa vykonat na odoslanie správy. Zvyčajne je vhodná doba životnosti okolo štyroch-piatich dní. - + Message too long Správa je príliš dlhá - + The message that you are trying to send is too long by %1 bytes. (The maximum is 261644 bytes). Please cut it down before sending. Správa, ktorú skúšate poslať, má %1 bajtov naviac. (Maximum je 261 644 bajtov). Prosím pred odoslaním skrátiť. - + Error: Your account wasn't registered at an email gateway. Sending registration now as %1, please wait for the registration to be processed before retrying sending. Chyba: Váš účet nebol registrovaný na e-mailovej bráne. Skúšam registrovať ako %1, prosím počkajte na spracovanie registrácie pred opakovaným odoslaním správy. - + Error: Bitmessage addresses start with BM- Please check %1 - + Chyba: Bitmessage adresy začínajú s BM- Prosím skontrolujte %1 - + Error: The address %1 is not typed or copied correctly. Please check it. - + Chyba: adresa %1 nie je na správne napísaná alebo skopírovaná. Prosím skontrolujte ju. - + Error: The address %1 contains invalid characters. Please check it. - + Chyba: adresa %1 obsahuje neplatné znaky. Prosím skontrolujte ju. - + Error: The address version in %1 is too high. Either you need to upgrade your Bitmessage software or your acquaintance is being clever. - + Chyba: verzia adresy %1 je príliš veľká. Buď budete musieť aktualizovať program Bitmessage alebo váš známy s vami žartuje. - + Error: Some data encoded in the address %1 is too short. There might be something wrong with the software of your acquaintance. - + Chyba: niektoré údaje zakódované v adrese %1 sú príliš krátke. Softér vášho známeho možno nefunguje správne. - + Error: Some data encoded in the address %1 is too long. There might be something wrong with the software of your acquaintance. - + Chyba: niektoré údaje zakódované v adrese %1 sú príliš dlhé. Softvér vášho známeho možno nefunguje správne. - + Error: Some data encoded in the address %1 is malformed. There might be something wrong with the software of your acquaintance. - + Chyba: niektoré údaje zakódované v adrese %1 sú poškodené. Softvér vášho známeho možno nefunguje správne. - + Error: Something is wrong with the address %1. - + Chyba: niečo s adresou %1 je nie je v poriadku. - + Error: You must specify a From address. If you don't have one, go to the 'Your Identities' tab. Chyba: musíte zadať adresu "Od". Ak žiadnu nemáte, prejdite na kartu "Vaše identity". - + Address version number Číslo verzie adresy - + Concerning the address %1, Bitmessage cannot understand address version numbers of %2. Perhaps upgrade Bitmessage to the latest version. Čo sa týka adresy %1, Bitmessage nepozná číslo verzie adresy %2. Možno by ste mali upgradenúť Bitmessage na najnovšiu verziu. - + Stream number Číslo prúdu - + Concerning the address %1, Bitmessage cannot handle stream numbers of %2. Perhaps upgrade Bitmessage to the latest version. Čo sa týka adresy %1, Bitmessage nespracováva číslo prúdu %2. Možno by ste mali upgradenúť Bitmessage na najnovšiu verziu. - + Warning: You are currently not connected. Bitmessage will do the work necessary to send the message but it won't send until you connect. Upozornenie: momentálne nie ste pripojení. Bitmessage vykoná prácu potrebnú na odoslanie správy, ale odoslať ju môže, až keď budete pripojení. - + Message queued. Správa vo fronte. - + Your 'To' field is empty. Pole "Komu" je prázdne. - + Right click one or more entries in your address book and select 'Send message to this address'. Vybertie jednu alebo viacero položiek v adresári, pravým tlačidlom myši zvoľte "Odoslať správu na túto adresu". - + Fetched address from namecoin identity. Prebratá adresa z namecoin-ovej identity. - + New Message Nová správa - + From - + Od - + Sending email gateway registration request - + Odosielam požiadavku o registráciu na e-mailovej bráne - + Address is valid. Adresa je platná. - + The address you entered was invalid. Ignoring it. Zadaná adresa bola neplatná a bude ignorovaná. - + Error: You cannot add the same address to your address book twice. Try renaming the existing one if you want. Chyba: tú istú adresu nemožno pridať do adresára dvakrát. Ak chcete, môžete skúsiť premenovať existujúcu menovku. - + Error: You cannot add the same address to your subscriptions twice. Perhaps rename the existing one if you want. Chyba: nemožno pridať rovnakú adresu k odberu dvakrát. Keď chcete, môžete premenovať existujúci záznam. - + Restart Reštart - + You must restart Bitmessage for the port number change to take effect. Aby sa zmena čísla portu prejavila, musíte reštartovať Bitmessage. - + Bitmessage will use your proxy from now on but you may want to manually restart Bitmessage now to close existing connections (if any). Bitmessage bude odteraz používať proxy, ale ak chcete ukončiť existujúce spojenia, musíte Bitmessage manuálne reštartovať. - + Number needed Číslo potrebné - + Your maximum download and upload rate must be numbers. Ignoring what you typed. Maxímálna rýchlosť príjmu a odoslania musí byť uvedená v číslach. Ignorujem zadané údaje. - + Will not resend ever Nikdy opätovne neodosielať - + Note that the time limit you entered is less than the amount of time Bitmessage waits for the first resend attempt therefore your messages will never be resent. Upozornenie: časový limit, ktorý ste zadali, je menší ako čas, ktorý Bitmessage čaká na prvý pokus o opätovné zaslanie, a preto vaše správy nebudú nikdy opätovne odoslané. - + Sending email gateway unregistration request - + Odosielam žiadosť o odhlásenie z e-mailovej brány - + Sending email gateway status request - + Odosielam požiadavku o stave e-mailovej brány - + Passphrase mismatch Nezhoda hesla - + The passphrase you entered twice doesn't match. Try again. Zadané heslá sa rôznia. Skúste znova. - + Choose a passphrase Vyberte heslo - + You really do need a passphrase. Heslo je skutočne potrebné. - + Address is gone Adresa zmizla - + Bitmessage cannot find your address %1. Perhaps you removed it? Bitmessage nemôže nájsť vašu adresu %1. Možno ste ju odstránili? - + Address disabled Adresa deaktivovaná - + Error: The address from which you are trying to send is disabled. You'll have to enable it on the 'Your Identities' tab before using it. Chyba: adresa, z ktorej sa pokúšate odoslať, je neaktívna. Pred použitím ju musíte aktivovať v karte "Vaše identity". - + Entry added to the Address Book. Edit the label to your liking. - + Záznam pridaný do adresára. Upravte označenie podľa vašich predstáv. - + Entry added to the blacklist. Edit the label to your liking. Záznam pridaný na zoznam zakázaných. Upravte označenie podľa vašich predstáv. - + Error: You cannot add the same address to your blacklist twice. Try renaming the existing one if you want. Chyba: tú istú adresu nemožno pridať na zoznam zakázaných dvakrát. Ak chcete, môžete skúsiť premenovať existujúce označenie. - + Moved items to trash. Položky presunuté do koša. - + Undeleted item. Položka obnovená. - + Save As... Uložiť ako... - + Write error. Chyba pri zapisovaní. - + No addresses selected. Nevybraná žiadna adresa. - + If you delete the subscription, messages that you already received will become inaccessible. Maybe you can consider disabling the subscription instead. Disabled subscriptions will not receive new messages, but you can still view messages you already received. Are you sure you want to delete the subscription? @@ -840,7 +811,7 @@ Are you sure you want to delete the subscription? Ste si istý, že chcete odber odstrániť? - + If you delete the channel, messages that you already received will become inaccessible. Maybe you can consider disabling the channel instead. Disabled channels will not receive new messages, but you can still view messages you already received. Are you sure you want to delete the channel? @@ -849,899 +820,596 @@ Are you sure you want to delete the channel? Ste si istý, že chcete kanál odstrániť? - + Do you really want to remove this avatar? Naozaj chcete odstrániť tento avatar? - + You have already set an avatar for this address. Do you really want to overwrite it? Pre túto adresu ste už ste nastavili avatar. Naozaj ho chcete ho zmeniť? - + Start-on-login not yet supported on your OS. Spustenie pri prihlásení zatiaľ pre váš operačný systém nie je podporované. - + Minimize-to-tray not yet supported on your OS. Minimalizovanie do panelu úloh zatiaľ pre váš operačný systém nie je podporované. - + Tray notifications not yet supported on your OS. Oblasť oznámení zatiaľ pre váš operačný systém nie je podporovaná. - + Testing... Testujem... - + This is a chan address. You cannot use it as a pseudo-mailing list. - + Toto je adresa kanálu. Nie je možné ju používať ako pseudo poštový zoznam. - + The address should start with ''BM-'' Adresa by mala začínať ''BM-'' - + The address is not typed or copied correctly (the checksum failed). Nesprávne zadaná alebo skopírovaná adresa (kontrolný súčet zlyhal). - + The version number of this address is higher than this software can support. Please upgrade Bitmessage. Číslo verzie tejto adresy je vyššie ako tento softvér podporuje. Prosím inovujte Bitmessage. - + The address contains invalid characters. Adresa obsahuje neplatné znaky. - + Some data encoded in the address is too short. Niektoré dáta zakódované v adrese sú príliš krátke. - + Some data encoded in the address is too long. Niektoré dáta zakódované v adrese sú príliš dlhé. - + Some data encoded in the address is malformed. Niektoré dáta zakódované v adrese sú poškodené. - + Enter an address above. - + Zadajte adresu vyššie. - + Address is an old type. We cannot display its past broadcasts. Starý typ adresy. Nie je možné zobraziť jej predchádzajúce hromadné správy. - + There are no recent broadcasts from this address to display. Neboli nájdené žiadne nedávne hromadé správy z tejto adresy. - + You are using TCP port %1. (This can be changed in the settings). - + Používate port TCP %1. (Možno zmeniť v nastaveniach). - + Bitmessage Bitmessage - + Identities Identity - + New Identity Nová identita - + Search Hľadaj - + All Všetky - + To Príjemca - + From Odosielateľ - + Subject Predmet - + Message Správa - + Received Prijaté - + Messages Správy - + Address book Adresár - + Address Adresa - + Add Contact Pridať kontakt - + Fetch Namecoin ID Získať identifikátor namecoin-u - + Subject: Predmet: - + From: Odosielateľ: - + To: Príjemca: - + Send ordinary Message Poslať obyčajnú správu - + Send Message to your Subscribers Poslať správu vašim odberateľom - + TTL: Doba životnosti: - + Subscriptions Odbery - + Add new Subscription Pridať nový odber - + Chans Kanály - + Add Chan Pridať kanál - + File Súbor - + Settings Nastavenia - + Help Pomoc - + Import keys Importovať kľúče - + Manage keys Spravovať kľúče - + Ctrl+Q Ctrl+Q - + F1 F1 - + Contact support Kontaktovať používateľskú podporu - + About O - + Regenerate deterministic addresses Znova vytvoriť deterministické adresy - + Delete all trashed messages Odstrániť všetky správy z koša - + Join / Create chan Pripojiť / vytvoriť kanál - + All accounts Všetky účty - + Zoom level %1% Úroveň priblíženia %1% - + Error: You cannot add the same address to your list twice. Perhaps rename the existing one if you want. Chyba: nemožno pridať rovnakú adresu do vášho zoznamu dvakrát. Keď chcete, môžete premenovať existujúci záznam. - + Add new entry Pridať nový záznam - + Display the %1 recent broadcast(s) from this address. - + Zobraziť posledných %1 hromadných správ z tejto adresy. - + New version of PyBitmessage is available: %1. Download it from https://github.com/Bitmessage/PyBitmessage/releases/latest K dispozícii je nová verzia PyBitmessage: %1. Môžete ju stiahnuť na https://github.com/Bitmessage/PyBitmessage/releases/latest - + Waiting for PoW to finish... %1% Čakám na ukončenie práce... %1% - + Shutting down Pybitmessage... %1% Ukončujem PyBitmessage... %1% - + Waiting for objects to be sent... %1% Čakám na odoslanie objektov... %1% - + Saving settings... %1% Ukladám nastavenia... %1% - + Shutting down core... %1% Ukončujem jadro... %1% - + Stopping notifications... %1% Zastavujem oznámenia... %1% - + Shutdown imminent... %1% Posledná fáza ukončenia... %1% - + %n hour(s) %n hodina%n hodiny%n hodín - + %n day(s) %n deň%n dni%n dní - + Shutting down PyBitmessage... %1% Ukončujem PyBitmessage... %1% - + Sent Odoslané - + Generating one new address Vytváram jednu novú adresu - + Done generating address. Doing work necessary to broadcast it... Vytváranie adresy ukončené. Vykonávam prácu potrebnú na rozoslanie... - + Generating %1 new addresses. Vytváram %1 nových adries. - + %1 is already in 'Your Identities'. Not adding it again. %1 sa už nachádza medzi vášmi identitami, nepridávam dvojmo. - + Done generating address Vytváranie adresy ukončené - + SOCKS5 Authentication problem: %1 - + Problém autentikácie SOCKS5: %1 - + Disk full Disk plný - + Alert: Your disk or data storage volume is full. Bitmessage will now exit. Upozornenie: Váš disk alebo priestor na ukladanie dát je plný. Bitmessage bude teraz ukončený. - + Error! Could not find sender address (your address) in the keys.dat file. Chyba! Nemožno nájsť adresu odosielateľa (vašu adresu) v súbore keys.dat. - + Doing work necessary to send broadcast... Vykonávam prácu potrebnú na rozoslanie... - + Broadcast sent on %1 Rozoslané %1 - + Encryption key was requested earlier. Šifrovací klúč bol vyžiadaný. - + Sending a request for the recipient's encryption key. Odosielam požiadavku na kľúč príjemcu. - + Looking up the receiver's public key Hľadám príjemcov verejný kľúč - + Problem: Destination is a mobile device who requests that the destination be included in the message but this is disallowed in your settings. %1 Problém: adresa príjemcu je na mobilnom zariadení a požaduje, aby správy obsahovali nezašifrovanú adresu príjemcu. Vaše nastavenia však túto možnost nemajú povolenú. %1 - + Doing work necessary to send message. There is no required difficulty for version 2 addresses like this. Vykonávam prácu potrebnú na odoslanie správy. Adresy verzie dva, ako táto, nepožadujú obtiažnosť. - + Doing work necessary to send message. Receiver's required difficulty: %1 and %2 Vykonávam prácu potrebnú na odoslanie správy. Priímcova požadovaná obtiažnosť: %1 a %2 - + Problem: The work demanded by the recipient (%1 and %2) is more difficult than you are willing to do. %3 Problém: Práca požadovná príjemcom (%1 a %2) je obtiažnejšia, ako máte povolené. %3 - + Problem: You are trying to send a message to yourself or a chan but your encryption key could not be found in the keys.dat file. Could not encrypt message. %1 Problém: skúšate odslať správu sami sebe, ale nemôžem nájsť šifrovací kľúč v súbore keys.dat. Nemožno správu zašifrovať: %1 - + Doing work necessary to send message. Vykonávam prácu potrebnú na odoslanie... - + Message sent. Waiting for acknowledgement. Sent on %1 Správa odoslaná. Čakanie na potvrdenie. Odoslaná %1 - + Doing work necessary to request encryption key. Vykonávam prácu potrebnú na vyžiadanie šifrovacieho kľúča. - + Broadcasting the public key request. This program will auto-retry if they are offline. Rozosielam požiadavku na verejný kľúč. Ak nebude príjemca spojený zo sieťou, budem skúšať znova. - + Sending public key request. Waiting for reply. Requested at %1 Odosielam požiadavku na verejný kľúč. Čakám na odpoveď. Vyžiadaný %1 - + UPnP port mapping established on port %1 Mapovanie portov UPnP vytvorené na porte %1 - + UPnP port mapping removed Mapovanie portov UPnP zrušené - - - Mark all messages as read - Označiť všetky správy ako prečítané - - - - Are you sure you would like to mark all messages read? - Ste si istý, že chcete označiť všetky správy ako prečítané? - - - - Doing work necessary to send broadcast. - Vykonávam prácu potrebnú na rozoslanie. - - - - Proof of work pending - Vykonávaná práca - - - - %n object(s) pending proof of work - %n objekt čaká na vykonanie práce%n objekty čakajú na vykonanie práce%n objektov čaká na vykonanie práce - - - - %n object(s) waiting to be distributed - %n objekt čaká na rozoslanie%n objekty čakajú na rozoslanie%n objektov čaká na rozoslanie - - - - Wait until these tasks finish? - Počkať, kým tieto úlohy skončia? - - - - Problem communicating with proxy: %1. Please check your network settings. - Problém komunikácie s proxy: %1. Prosím skontrolujte nastavenia siete. - - - - SOCKS5 Authentication problem: %1. Please check your SOCKS5 settings. - Problém autentikácie SOCKS5: %1. Prosím skontrolujte nastavenia SOCKS5. - - - - The time on your computer, %1, may be wrong. Please verify your settings. - Čas na vašom počítači, %1, možno nie je správny. Prosím, skontrolujete nastavenia. - - - - The name %1 was not found. - Meno % nenájdené. - - - - The namecoin query failed (%1) - Dotaz prostredníctvom namecoinu zlyhal (%1) - - - - The namecoin query failed. - Dotaz prostredníctvom namecoinu zlyhal. - - - - The name %1 has no valid JSON data. - Meno %1 neobsahuje planté JSON dáta. - - - - The name %1 has no associated Bitmessage address. - Meno %1 nemá priradenú žiadnu adresu Bitmessage. - - - - Success! Namecoind version %1 running. - Úspech! Namecoind verzia %1 spustený. - - - - Success! NMControll is up and running. - Úspech! NMControl spustený. - - - - Couldn't understand NMControl. - Nie je rozumieť NMControl-u. - - - - Your GPU(s) did not calculate correctly, disabling OpenCL. Please report to the developers. - Vaša grafická karta vykonala nesprávny výpočet, deaktivujem OpenCL. Prosím, pošlite správu vývojárom. - - - - Set notification sound... - Nastaviť zvuk oznámenia... - - - - - Welcome to easy and secure Bitmessage - * send messages to other people - * send broadcast messages like twitter or - * discuss in chan(nel)s with other people - - -Vitajte v jednoduchom a bezpečnom Bitmessage -* posielajte správy druhým ľuďom -* posielajte hromadné správy ako na twitteri alebo -* diskutuje s druhými v kanáloch - - - - - not recommended for chans - nie je odporúčaná pre kanály - - - - Quiet Mode - Tichý režim - - - - Problems connecting? Try enabling UPnP in the Network Settings - Problémy so spojením? Skúste zapnúť UPnP v Nastaveniach siete - - - - You are trying to send an email instead of a bitmessage. This requires registering with a gateway. Attempt to register? - Pokúšate sa odoslať e-mail namiesto bitmessage. To si vyžaduje registráciu na bráne. Pokúsiť sa o registráciu? - - - - Error: Bitmessage addresses start with BM- Please check the recipient address %1 - Chyba: Bitmessage adresy začínajú s BM- Prosím skontrolujte adresu príjemcu %1 - - - - Error: The recipient address %1 is not typed or copied correctly. Please check it. - Chyba: adresa príjemcu %1 nie je na správne napísaná alebo skopírovaná. Prosím skontrolujte ju. - - - - Error: The recipient address %1 contains invalid characters. Please check it. - Chyba: adresa príjemcu %1 obsahuje neplatné znaky. Prosím skontrolujte ju. - - - - Error: The version of the recipient address %1 is too high. Either you need to upgrade your Bitmessage software or your acquaintance is being clever. - Chyba: verzia adresy príjemcu %1 je príliš veľká. Buď musíte aktualizovať program Bitmessage alebo váš známy s vami žartuje. - - - - Error: Some data encoded in the recipient address %1 is too short. There might be something wrong with the software of your acquaintance. - Chyba: niektoré údaje zakódované v adrese príjemcu %1 sú príliš krátke. Softér vášho známeho možno nefunguje správne. - - - - Error: Some data encoded in the recipient address %1 is too long. There might be something wrong with the software of your acquaintance. - Chyba: niektoré údaje zakódované v adrese príjemcu %1 sú príliš dlhé. Softvér vášho známeho možno nefunguje správne. - - - - Error: Some data encoded in the recipient address %1 is malformed. There might be something wrong with the software of your acquaintance. - Chyba: niektoré údaje zakódované v adrese príjemcu %1 sú poškodené. Softvér vášho známeho možno nefunguje správne. - - - - Error: Something is wrong with the recipient address %1. - Chyba: niečo s adresou príjemcu %1 je nie je v poriadku. - - - - Error: %1 - Chyba: %1 - - - - From %1 - Od %1 - - - - Synchronisation pending - Prebieha synchronizácia - - - - Bitmessage hasn't synchronised with the network, %n object(s) to be downloaded. If you quit now, it may cause delivery delays. Wait until the synchronisation finishes? - Bitmessage sa nezosynchronizoval so sieťou, treba prevzať ešte %n objekt. Keď program ukončíte teraz, môže dôjsť k spozdeniu doručenia. Počkať, kým sa synchronizácia ukončí?Bitmessage sa nezosynchronizoval so sieťou, treba prevzať ešte %n objekty. Keď program ukončíte teraz, môže dôjsť k spozdeniu doručenia. Počkať, kým sa synchronizácia ukončí?Bitmessage sa nezosynchronizoval so sieťou, treba prevzať ešte %n objektov. Keď program ukončíte teraz, môže dôjsť k spozdeniu doručenia. Počkať, kým sa synchronizácia ukončí? - - - - Not connected - Nepripojený - - - - Bitmessage isn't connected to the network. If you quit now, it may cause delivery delays. Wait until connected and the synchronisation finishes? - Bitmessage nie je pripojený do siete. Keď program ukončíte teraz, môže dôjsť k spozdeniu doručenia. Počkať, kým dôjde k spojeniu a prebehne synchronizácia? - - - - Waiting for network connection... - Čakám na pripojenie do siete... - - - - Waiting for finishing synchronisation... - Čakám na ukončenie synchronizácie... - - - - You have already set a notification sound for this address book entry. Do you really want to overwrite it? - Pre túto adresu ste už ste nastavili zvuk oznámenia. Naozaj ho chcete ho zmeniť? - - - - Error occurred: could not load message from disk. - Chyba pri načítaní správy. - - - - Display the %n recent broadcast(s) from this address. - Zobraziť poslednú %1 hromadnú správu z tejto adresy.Zobraziť posledné %1 hromadné správy z tejto adresy.Zobraziť posledných %1 hromadných správ z tejto adresy. - - - - Go online - Pripojiť k sieti - - - - Go offline - Odpojiť od siete - - - - Clear - Vymazať - - - - inbox - Doručená pošta - - - - new - Nová doručená pošta - - - - sent - - - - - trash - - - - - MessageView - - - Follow external link - Nasledovať externý odkaz - - - - The link "%1" will open in a browser. It may be a security risk, it could de-anonymise you or download malicious data. Are you sure? - Odkaz "%1" bude otvorený v prehliadači. Tento úkon môže predstavovať bezpečnostné riziko a Vás deanonymizovať, alebo vykonať škodlivú činnost. Ste si istý? - - - - HTML detected, click here to display - Nájdené HTML, kliknite sem ak chcete zobraziť - - - - Click here to disable HTML - Kliknite sem na vypnutie HTML - - - - MsgDecode - - - The message has an unknown encoding. -Perhaps you should upgrade Bitmessage. - Správa má neznáme kódovanie. -Možno by ste mali inovovať Bitmessage. - - - - Unknown encoding - Neznáme kódovanie - NewAddressDialog - + Create new Address Vytvoriť novú adresu - + Here you may generate as many addresses as you like. Indeed, creating and abandoning addresses is encouraged. You may generate addresses by using either random numbers or by using a passphrase. If you use a passphrase, the address is called a "deterministic" address. The 'Random Number' option is selected by default but deterministic addresses have several pros and cons: Tu si môžete vygenerovať toľko adries, koľko chcete. Vytváranie a opúšťanie adries je vrelo odporúčané. Adresy môžete generovať buď pomocou náhodných čísel alebo pomocou hesla. Ak používate heslo, takáto adresa sa nazýva "deterministická". Predvoľba je pomocou generátora náhodných čísiel, ale deterministické adresy majú niekoľko výhod a nevýhod: - + <html><head/><body><p><span style=" font-weight:600;">Pros:<br/></span>You can recreate your addresses on any computer from memory. <br/>You need-not worry about backing up your keys.dat file as long as you can remember your passphrase. <br/><span style=" font-weight:600;">Cons:<br/></span>You must remember (or write down) your passphrase if you expect to be able to recreate your keys if they are lost. <br/>You must remember the address version number and the stream number along with your passphrase. <br/>If you choose a weak passphrase and someone on the Internet can brute-force it, they can read your messages and send messages as you.</p></body></html> <html><head/><body><p> <span style=" font-weight:600;">Pros: <br/></span>Svoje adresy môžete znovu vytvoriť na ľubovoľnom počítači z pamäte.<br/>Dokým si pamätáte heslo, nemusíte sa starať o zálohovanie keys.dat<br/> <span style=" font-weight:600;"Nevýhody: <br/></span>Ak chcete znovu vytvoriť kľúče ak ich stratíte, musíte si pamätať (alebo niekam zapísať) heslo. <br/> Zároveň si musíte si zapamätať aj číslo verzie adresy a číslo toku.<br/>Ak si zvolíte slabé prístupové heslo a niekto na internete ho uhádne, napr. hrubou silou, môže čítať vaše správy a odosielať ich za vás.</p></body></html> - + Use a random number generator to make an address Vytvoriť novú adresu pomocou generátora náhodných čísel - + Use a passphrase to make addresses Vytvoriť novú adresu pomocou hesla - + Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter Stráviť niekoľko minút výpočtového času navyše, aby adresa(y) bola o 1 alebo 2 znakov kratšia - + Make deterministic addresses Vytvoriť deterministické adresy - + Address version number: 4 Číslo verzie adresy: 4 - + In addition to your passphrase, you must remember these numbers: Okrem svojho hesla si musíte zapamätať tiejto údaje: - + Passphrase Heslo - + Number of addresses to make based on your passphrase: Počet adries, ktoré majú byť na základe vášho hesla vytvorené: - + Stream number: 1 Číslo prúdu: 1 - + Retype passphrase Opakovať heslo - + Randomly generate address Adresu generovať náhodne - + Label (not shown to anyone except you) Označenie (zobrazené len vám a nikomu inému) - + Use the most available stream Použiť najviac prístupný prúd - + (best if this is the first of many addresses you will create) (najlepšie, ak ide o prvú z mnohých vytvorených adries) - + Use the same stream as an existing address Použiť ten istý prúd ako existujúca adresa - + (saves you some bandwidth and processing power) (ušetrí nejaké množstvo prenesených dát a výpočtový výkon) @@ -1749,22 +1417,22 @@ Predvoľba je pomocou generátora náhodných čísiel, ale deterministické adr NewSubscriptionDialog - + Add new entry Pridať nový záznam - + Label Označenie - + Address Adresa - + Enter an address above. Zadajte adresu vyššie. @@ -1772,72 +1440,62 @@ Predvoľba je pomocou generátora náhodných čísiel, ale deterministické adr SpecialAddressBehaviorDialog - + Special Address Behavior Zvláštne správanie adresy - + Behave as a normal address Správanie ako normálna adresa - + Behave as a pseudo-mailing-list address Správanie ako pseudo poštový zoznam - + Mail received to a pseudo-mailing-list address will be automatically broadcast to subscribers (and thus will be public). Správy prijaté na adresu pseudo poštového zoznamu budú automaticky rozoslaná odberateľom (a teda budú zverejnené). - + Name of the pseudo-mailing-list: Meno pseudo poštového zoznamu: - - - This is a chan address. You cannot use it as a pseudo-mailing list. - Toto je adresa kanálu. Nie je možné ju používať ako pseudo poštový zoznam. - aboutDialog - + About O - + PyBitmessage - + PyBitmessage - + version ? - + verzia ? - + <html><head/><body><p>Distributed under the MIT/X11 software license; see <a href="http://www.opensource.org/licenses/mit-license.php"><span style=" text-decoration: underline; color:#0000ff;">http://www.opensource.org/licenses/mit-license.php</span></a></p></body></html> <html><head/><body><p>Šírený pod licenciou na softvér MIT / X11; pozri <a href="http://www.opensource.org/licenses/mit-license.php"><span style=" text-decoration: underline; color:#0000ff;">http://www.opensource.org/licenses/mit-license.php</span></a></p></body></html> - + This is Beta software. Toto je beta softvér. - + <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 Vývojári Bitmessage</p></body></html> + <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 Vývojári Bitmessage</p></body></html> @@ -1868,12 +1526,12 @@ Predvoľba je pomocou generátora náhodných čísiel, ale deterministické adr Adresa - + Blacklist Zoznam zakázaných - + Whitelist Zoznam povolených @@ -1881,45 +1539,40 @@ Predvoľba je pomocou generátora náhodných čísiel, ale deterministické adr connectDialog - + Bitmessage Bitmessage - + Bitmessage won't connect to anyone until you let it. Bitmessage sa s nikým nespojí, dokým to nepovolíte. - + Connect now Spojiť teraz - + Let me configure special network settings first Najprv upraviť zvláštne sieťové nastavenia - - - Work offline - Zostať odpojený - helpDialog - + Help Pomoc - + <a href="https://bitmessage.org/wiki/PyBitmessage_Help">https://bitmessage.org/wiki/PyBitmessage_Help</a> <a href="https://bitmessage.org/wiki/PyBitmessage_Help">https://bitmessage.org/wiki/PyBitmessage_Help</a> - + As Bitmessage is a collaborative project, help can be found online in the Bitmessage Wiki: Keďže Bitmessage je projekt založený na spolupráci, pomoc možno nájsť na internete v Bitmessage Wiki: @@ -1927,35 +1580,30 @@ Predvoľba je pomocou generátora náhodných čísiel, ale deterministické adr iconGlossaryDialog - + Icon Glossary Legenda ikon - + You have no connections with other peers. Nemáte žiadne spojenia s partnerskými uzlami. - + You have made at least one connection to a peer using an outgoing connection but you have not yet received any incoming connections. Your firewall or home router probably isn't configured to forward incoming TCP connections to your computer. Bitmessage will work just fine but it would help the Bitmessage network if you allowed for incoming connections and will help you be a better-connected node. Vykonali ste aspoň jedno vychádzajúce spojenie do siete, ale ešte ste nenaviazali žiadne prichádzajúce spojenia. Váš firewall alebo domáci router pravdepodobne nie je nakonfigurovaný tak, aby presmeroval prichádzajúce TCP spojenia k vášmu počítaču. Bitmessage bude fungovať v pohode, keby ste však mali fungujúce prichádzajúce spojenia, pomôžete sieti Bitmessage a váš uzol bude lepšie pripojený. You are using TCP port ?. (This can be changed in the settings). - + Používate port TCP ?. (Možno zmeniť v nastaveniach). - + You do have connections with other peers and your firewall is correctly configured. Máte spojenia s partnerskými uzlami a vaša brána firewall je nastavená správne. - - - You are using TCP port %1. (This can be changed in the settings). - Používate port TCP %1. (Možno zmeniť v nastaveniach). - networkstatus @@ -1965,322 +1613,203 @@ Predvoľba je pomocou generátora náhodných čísiel, ale deterministické adr Spojení spolu: - + Since startup: Od spustenia: - + Processed 0 person-to-person messages. Spracovaných 0 bežných správ. - + Processed 0 public keys. Spracovaných 0 verejných kľúčov. - + Processed 0 broadcasts. Spracovaných 0 hromadných správ. - + Inventory lookups per second: 0 Vyhľadaní v inventári za sekundu: 0 - + Objects to be synced: Zostáva synchronizovať objektov: - + Stream # Prúd # Connections - + Spojenia - + Since startup on %1 Od spustenia %1 - + Down: %1/s Total: %2 Prijatých: %1/s Spolu: %2 - + Up: %1/s Total: %2 Odoslaných: %1/s Spolu: %2 - + Total Connections: %1 Spojení spolu: %1 - + Inventory lookups per second: %1 Vyhľadaní v inventári za sekundu: %1 - + Up: 0 kB/s Odoslaných: 0 kB/s - + Down: 0 kB/s Prijatých: 0 kB/s - + Network Status Stav siete - + byte(s) bajtbajtybajtov - + Object(s) to be synced: %n Zostáva synchronizovať %n objektZostáva synchronizovať %n objektyZostáva synchronizovať %n objektov - + Processed %n person-to-person message(s). Spracovaná %n bežná správa.Spracované %n bežné správy.Spracovaných %n bežných správ. - + Processed %n broadcast message(s). Spracovaná %n hromadná správa.Spracované %n hromadné správy.Spracovaných %n hromadných správ. - + Processed %n public key(s). Spracovaný %n verejný kľúč.Spracované %n verejné kľúče.Spracovaných %n verejných kľúčov. - - - Peer - Partnerský uzol - - - - IP address or hostname - IP adresa alebo názov hostiteľa - - - - Rating - Hodnotenie - - - - PyBitmessage tracks the success rate of connection attempts to individual nodes. The rating ranges from -1 to 1 and affects the likelihood of selecting the node in the future - PyBitmessage sleduje úspešnosť pokusov o pripojenie k jednotlivým uzlom. Hodnotenie sa pohybuje od -1 do 1 a ovplyvňuje pravdepodobnosť výberu uzla v budúcnosti - - - - User agent - Užívateľský agent - - - - Peer's self-reported software - Software hlásený partnerským uzlom - - - - TLS - TLS - - - - Connection encryption - Šifrovanie pripojenia - - - - List of streams negotiated between you and the peer - Zoznam prúdov dohodnutých medzi vami a partnerom - newChanDialog Dialog - + Kanál Create a new chan - + Vytvoriť nový kanál Join a chan - + Pripojiť sa na kanál Create a chan - + Vytvoriť kanál <html><head/><body><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. If you and someone else both create a chan with the same chan name then it is currently very likely that they will be the same chan.</p></body></html> - + <html><head/><body><p>Zadajte názov pre váš kanál. Ak zvolíte dostatočne zložitý názov kanálu (napríklad zložité a jedinečné heslo) a nikto z vašich známych ju ho nebude verejne zdieľať, potom bude kanál bezpečný a súkromný. Ak vy a niekto iný vytvoríte kanál s rovnakým názvom, bude to de facto ten istý kanál.</p></body></html> Chan name: - + Meno kanálu <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 normal person-to-person message to the chan address.</p><p>Chans are experimental and completely unmoderatable.</p></body></html> - + <html><head/><body><p>Kanál existuje, keď skupina ľudí zdieľa tie isté dešifrovacie kľúče. Kľúče a bitmessage adresa používané v kanáli sú generované zo slova alebo frázy (názov kanálu). Ak chcete poslať správu všetkým užívateľom v kanáli, pošlite bežnú správu na adresu kanálu.</p><p>Kanály sú experimentálne a vôbec sa nedajú moderovať/cenzúrovať.</p></body></html> Chan bitmessage address: - - - - - Create or join a chan - Vytvoriť alebo pripojiť sa ku kanálu - - - - <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>Kanál existuje, keď skupina ľudí zdieľa tie isté dešifrovacie kľúče. Kľúče a bitmessage adresa používané v kanáli sú generované zo slova alebo frázy (názov kanálu). Ak chcete poslať správu všetkým užívateľom v kanáli, pošlite bežnú správu na adresu kanálu.</p><p>Kanály sú experimentálne a vôbec sa nedajú moderovať/cenzúrovať.</p><p>Zadajte názov pre váš kanál. Ak zvolíte dostatočne zložitý názov kanálu (napríklad zložité a jedinečné heslo) a nikto z vašich známych ho nebude verejne zdieľať, potom bude kanál bezpečný a súkromný. Ak vy a niekto iný vytvoríte kanál s rovnakým názvom, bude to v skutočnosti ten istý kanál, ktorý budete zdieľať.</p></body></html> - - - - Chan passphrase/name: - Názov kanálu: - - - - Optional, for advanced usage - Nepovinné, pre pokročilé možnosti - - - - Chan address - Adresa kanálu - - - - Please input chan name/passphrase: - Prosím zadajte názov kanálu - - - - newchandialog - - - Successfully created / joined chan %1 - Kanál %1 úspešne vytvorený/pripojený - - - - Chan creation / joining failed - Vytvorenie/pripojenie kanálu zlyhalo - - - - Chan creation / joining cancelled - Vytvorenie/pripojenie kanálu zrušené - - - - proofofwork - - - C PoW module built successfully. - C PoW modul úspešne zostavený. - - - - Failed to build C PoW module. Please build it manually. - Zostavenie C PoW modulu zlyhalo. Prosím, zostavte ho manuálne. - - - - C PoW module unavailable. Please build it. - C PoW modul nie je dostupný. Prosím, zostavte ho. - - - - qrcodeDialog - - - QR-code - QR kód + Bitmessage adresa kanálu: regenerateAddressesDialog - + Regenerate Existing Addresses Znova vytvoriť existujúce adresy - + Regenerate existing addresses Znova vytvoriť existujúce adresy - + Passphrase Heslo - + Number of addresses to make based on your passphrase: Počet adries, ktoré majú byť na základe vášho hesla vytvorené: - + Address version number: Číslo verzie adresy: - + Stream number: Číslo prúdu: - + 1 1 - + Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter Stráviť niekoľko minút výpočtového času navyše, aby adresa(y) bola o 1 alebo 2 znakov kratšia - + You must check (or not check) this box just like you did (or didn't) when you made your addresses the first time. Je nutné začiarknuť (alebo nezačiarknuť) toto políčko tak isto, ako keď ste vytvárali svoje adresy prvýkrát. - + If you have previously made deterministic addresses but lost them due to an accident (like hard drive failure), you can regenerate them here. If you used the random number generator to make your addresses then this form will be of no use to you. Ak ste v minulosti používali deterministické adresy, ale stratili ich kvôli nehode (ako je napráklad zlyhanie pevného disku), môžete ich vytvoriť znova. Ak ste na vytvorenie adries použili generátor náhodných čísel, potom je vám tento formulár zbytočný. @@ -2288,310 +1817,295 @@ Predvoľba je pomocou generátora náhodných čísiel, ale deterministické adr settingsDialog - + Settings Nastavenia - + Start Bitmessage on user login Spustiť Bitmessage pri prihlásení používateľa - + Tray Panel úloh - + Start Bitmessage in the tray (don't show main window) Spustiť Bitmessage v paneli úloh (nezobrazovať hlavné okno) - + Minimize to tray Minimalizovať do panelu úloh - + Close to tray Zavrieť do panelu úloh - + Show notification when message received Zobraziť oznámenie, ked obdržíte správu - + Run in Portable Mode Spustiť v prenosnom režime - + 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. V prenosnom režime budú správy a konfiguračné súbory uložené v rovnakom priečinku ako program, namiesto v bežnom priečinku pre údaje aplikácie. Vďaka tomu je pohodlné používať Bitmessage na USB kľúči. - + Willingly include unencrypted destination address when sending to a mobile device Povoliť pridanie nezašifrovanej adresy prijímateľa pri posielaní na mobilné zariadenie - + Use Identicons Zobrazuj identikony (ikony automaticky vytvorené pre každú adresu) - + Reply below Quote Odpovedať pod citáciou - + Interface Language Jazyk rozhrania - + System Settings system Systémové nastavenia - + User Interface Užívateľské rozhranie - + Listening port Prijímajúci port - + Listen for connections on port: Prijímať spojenia na porte: - + UPnP: UPnP: - + Bandwidth limit Obmedzenie šírky pásma - + Maximum download rate (kB/s): [0: unlimited] Maximálna rýchlosť sťahovania (kB/s): [0: bez obmedzenia] - + Maximum upload rate (kB/s): [0: unlimited] Maximálna rýchlosť odosielania (kB/s): [0: bez obmedzenia] - + Proxy server / Tor Proxy server / Tor - + Type: Typ: - + Server hostname: Názov hostiteľského servera: - + Port: Port: - + Authentication Overovanie - + Username: Používateľské meno: - + Pass: Heslo: - + Listen for incoming connections when using proxy Prijímať prichádzajúce spojenia ak je používaný proxy - + none žiadny - + SOCKS4a SOCKS4a - + SOCKS5 SOCKS5 - + Network Settings Nastavenia siete - + Total difficulty: Celková obtiažnosť: - + The 'Total difficulty' affects the absolute amount of work the sender must complete. Doubling this value doubles the amount of work. 'Celková obtiažnosť' ovplyvňuje celkové množstvo práce, ktorú musí odosielateľ vykonať. Zdvojnásobenie tejto hodnoty zdvojnásobí potrebné množstvo práce. - + Small message difficulty: Obtiažnosť malých správ: - + 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. Keď vám niekto pošle správu, ich počítač musí najprv vykonať nejakú prácu. Obtiažnosť tejto práce je predvolená na 1. Túto predvoľbu môžete zvýšiť nastavením parametrov. Každá novo vygenerovaná adresa bude od odosielateľa požadovať túto zvýšenú obtiažnosť. Existuje však výnimka: ak vášho známeho máte v adresári, pri poslaní nasledujúcej správy im Bitmessage automaticky oznámi, že im stačí minimálne množstvo práce: obtiažnosť 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. 'Obtiažnosť malých správ' ovplyvňuje najmä náročnosť odosielania malých správ. Zdvojnásobenie tejto hodnoty zdvojnásobí potrebné množstvo práce na odoslanie malých správ, ale veľké správy príliš neovplyvňuje. - + Demanded difficulty Požadovaná obtiažnosť - + 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. Tu môžete nastaviť maximálne množstvo práce, ktorú váš počítač je ochotný urobiť pre odoslanie správy inej osobe. Nastavenie týchto hodnôt na 0 znamená, že ľubovoľné množtvo práce je prijateľné. - + Maximum acceptable total difficulty: Maximálna prijateľná celková obtiažnosť: - + Maximum acceptable small message difficulty: Maximálna prijateľná obtiažnost malých správ: - + Max acceptable difficulty Max. prijateľná obtiažnosť Hardware GPU acceleration (OpenCL) - + Hardvérové GPU urýchľovanie (OpenCL) - + <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 sa môže pripojiť k systému s názvom Namecoin, ktorý je podobný Bitcoinu, a s jeho pomocou používať používateľsky príjemné identifikátory. Napríklad namiesto zverejňovania dlhej Bitmessage adresy môžete jednoducho zverejniť meno, povedzme <span style=" font-style:italic;">test.</span></p><p>(Dostať vašu vlastnú adresu do Namecoin-u je však zatiaľ pomerne zložité).</p><p>Bitmessage sa môže pripojiť priamo na namecoind, alebo na aktívnu inštanciu nmcontrol.</p></body</html> - + Host: Hostiteľ: - + Password: Heslo: - + Test Test - + Connect to: Pripojiť ku: - + Namecoind Namecoind - + NMControl NMControl - + Namecoin integration Integrácia namecoin-u - + <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>Predvoľba spôsobí opätovné odoslanie správy ak nebude príjemca pripojený na sieť viac ako dva dni. Tieto pokusy budú opakované, dokým príjemca nepotvrdí obdržanie správy. Toto správanie môžete zmeniť zadaním počtu dní alebo mesiacov, po ktorých má Bitmessage s opätovným odosielaním prestať.</p><p>Vstupné polia nechajte prázdne, ak chcete predvolené správanie. </p></body></html> - + Give up after Vzdať po - + and a - + days dňoch - + months. mesiacoch. - + Resends Expire Vypršanie opätovného odosielania - - - Hide connection notifications - Skryť oznámenia o stave pripojenia - - - - Maximum outbound connections: [0: none] - Maximálny počet odchádzajúcich spojení: [0: žiadne] - - - - Hardware GPU acceleration (OpenCL): - Hardvérové GPU urýchľovanie (OpenCL): - \ No newline at end of file diff --git a/src/translations/bitmessage_zh_cn.qm b/src/translations/bitmessage_zh_cn.qm index 7cb18983..121389c8 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..356b2b56 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,93 +20,63 @@ 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)如下: @@ -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,63 +156,62 @@ 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 新建 @@ -311,12 +236,12 @@ Please type the desired email address (including @mailchuck.com) below: 将地址复制到剪贴板 - + Special address behavior... 特别的地址行为... - + Email gateway 电子邮件网关 @@ -326,402 +251,402 @@ Please type the desired email address (including @mailchuck.com) below: 删除 - + Send message to this address 发送消息到这个地址 - + Subscribe to this address 订阅到这个地址 - + Add New Address 创建新地址 - + Copy destination address to clipboard 复制目标地址到剪贴板 - + Force send 强制发送 - + One of your addresses, %1, is an old version 1 address. Version 1 addresses are no longer supported. May we delete it now? 您的地址中的一个, %1,是一个过时的版本1地址. 版本1地址已经不再受到支持了. 我们可以将它删除掉么? - + Waiting for their encryption key. Will request it again soon. 正在等待他们的加密密钥,我们会在稍后再次请求。 Encryption key request queued. - + 加密密钥请求已经添加到队列中。 - + Queued. 已经添加到队列。 - + Message sent. Waiting for acknowledgement. Sent at %1 消息已经发送. 正在等待回执. 发送于 %1 - + Message sent. Sent at %1 消息已经发送. 发送于 %1 Need to do work to send message. Work is queued. - + 发生消息需要做工。做工正在队列中等待。 - + 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. - + Chan name needed - + 需要频道的名称 - + You didn't enter a chan name. - + 您没有输入一个频道的名称。 - + Address already present - + 地址已经在这里了 - + Could not add chan because it appears to already be one of your identities. - + 无法添加频道,因为它似乎已经是您的身份之一。 - + Success - - - - - Successfully created chan. To let others join your chan, give them the chan name and this Bitmessage address: %1. This address also appears in 'Your Identities'. - - - - - 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. - - - - - Address invalid - - - - - That Bitmessage address is not valid. - - - - - Address does not match chan name - - - - - Although the Bitmessage address you entered was valid, it doesn't match the chan name. - - - - - Successfully joined chan. - + 成功 + Successfully created chan. To let others join your chan, give them the chan name and this Bitmessage address: %1. This address also appears in 'Your Identities'. + 成功的创建了频道。要让他人加入,请告诉他们频道的名称和这个比特信地址 %1 。这个比特信地址也会出现在“您的身份”中。 + + + + 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. + 尽管比特信地址也许是有效的,不过比特信地址的版本号比我们能处理的要新。也许您应该升级比特信了。 + + + + Address invalid + 地址有效 + + + + That Bitmessage address is not valid. + 比特信地址无效。 + + + + Address does not match chan name + 地址和频道的名称不符 + + + + Although the Bitmessage address you entered was valid, it doesn't match the chan name. + 尽管您输入的比特信地址是有效的,不过它和频道的名称不符。 + + + + Successfully joined chan. + 成功的加入到频道。 + + + 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​​, 注册正在处理请稍候重试发送. - + Error: Bitmessage addresses start with BM- Please check %1 - + 错误:比特信地址以BM-开始,请检查 %1 - + Error: The address %1 is not typed or copied correctly. Please check it. - + 错误:地址 %1 没有被正确的键入或复制。 请检查一下。 - + Error: The address %1 contains invalid characters. Please check it. - + 错误: 比特信地址 %1 包含无效的字符。请检查一下。 - + Error: The address version in %1 is too high. Either you need to upgrade your Bitmessage software or your acquaintance is being clever. - + 错误:地址 %1 的版本号过高。您可能需要升级您的比特信软件或者您的朋友正在使用本程序的非主线版本。 - + Error: Some data encoded in the address %1 is too short. There might be something wrong with the software of your acquaintance. - + 错误:在地址 %1 中编码的部分信息过短。您的朋友的软件可能有点问题。 - + Error: Some data encoded in the address %1 is too long. There might be something wrong with the software of your acquaintance. - + 错误:在地址 %1 中编码的部分信息过长。您的朋友的软件可能有点问题。 - + Error: Some data encoded in the address %1 is malformed. There might be something wrong with the software of your acquaintance. - + 错误: 一些地址数据%1的格式不正确. 可能有一些错误认识您的软件 - + Error: Something is wrong with the address %1. - + 错误: 地址%1 有为未知的错误。 - + 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 新消息 - + From - + 来自 - + Sending email gateway registration request - + 发送电​​子邮件网关注册请求 @@ -734,440 +659,440 @@ It is important that you back up this file. Would you like to open the file now? 您输入的地址是无效的,将被忽略。 - + Error: You cannot add the same address to your address book twice. Try renaming the existing one if you want. 错误:您无法将一个地址添加到您的地址本两次,请尝试重命名已经存在的那个。 - + Error: You cannot add the same address to your subscriptions twice. Perhaps rename the existing one if you want. 错误: 您不能在同一地址添加到您的订阅两次. 也许您可重命名现有之一. - + Restart 重启 - + You must restart Bitmessage for the port number change to take effect. 您必须重启以便使比特信对于使用的端口的改变生效。 - + Bitmessage 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% @@ -1182,708 +1107,386 @@ Are you sure you want to delete the channel? 添加新条目 - + Display the %1 recent broadcast(s) from this address. - + 显示从这个地址%1的最近广播 - + New version of PyBitmessage is available: %1. Download it from https://github.com/Bitmessage/PyBitmessage/releases/latest PyBitmessage的新版本可用: %1. 从https://github.com/Bitmessage/PyBitmessage/releases/latest下载 - + Waiting for PoW to finish... %1% 等待PoW完成...%1% - + Shutting down Pybitmessage... %1% 关闭Pybitmessage ...%1% - + Waiting for objects to be sent... %1% 等待要发送对象...%1% - + Saving settings... %1% 保存设置...%1% - + Shutting down core... %1% 关闭核心...%1% - + Stopping notifications... %1% 停止通知...%1% - + Shutdown imminent... %1% 关闭即将来临...%1% - + %n hour(s) %n 小时 - + %n 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 完成生成地址 - + SOCKS5 Authentication problem: %1 - + SOCKS5 认证问题: %1 - + 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? - 等待所有任务执行完? - - - - 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 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。请报告给开发者。 - - - - Set notification sound... - 设置通知提示音... - - - - - Welcome to easy and secure Bitmessage - * send messages to other people - * send broadcast messages like twitter or - * discuss in chan(nel)s with other people - - -欢迎使用简便安全的比特信 -*发送信息给其他人 -*像推特那样发送广播信息 -*在频道里和其他人讨论 - - - - not recommended for chans - 频道内不建议的内容 - - - - Quiet Mode - 静默模式 - - - - Problems connecting? Try enabling UPnP in the Network Settings - 连接问题?请尝试在网络设置里打开UPnP - - - - You are trying to send an email instead of a bitmessage. This requires registering with a gateway. Attempt to register? - 您将要尝试经由 Bitmessage 发送一封电子邮件。该操作需要在一个网关上注册。尝试注册? - - - - Error: Bitmessage addresses start with BM- Please check the recipient address %1 - 错误:Bitmessage地址是以BM-开头的,请检查收信地址%1. - - - - Error: The recipient address %1 is not typed or copied correctly. Please check it. - 错误:收信地址%1未填写或复制错误。请检查。 - - - - Error: The recipient address %1 contains invalid characters. Please check it. - 错误:收信地址%1还有非法字符。请检查。 - - - - Error: The version of the recipient address %1 is too high. Either you need to upgrade your Bitmessage software or your acquaintance is being clever. - 错误:收信地址 %1 版本太高。要么您需要更新您的软件,要么对方需要降级 。 - - - - Error: Some data encoded in the recipient address %1 is too short. There might be something wrong with the software of your acquaintance. - 错误:收信地址%1编码数据太短。可能对方使用的软件有问题。 - - - - Error: Some data encoded in the recipient address %1 is too long. There might be something wrong with the software of your acquaintance. - 错误: - - - - Error: Some data encoded in the recipient address %1 is malformed. There might be something wrong with the software of your acquaintance. - 错误:收信地址%1编码数据太长。可能对方使用的软件有问题。 - - - - Error: Something is wrong with the recipient address %1. - 错误:收信地址%1有问题。 - - - - Error: %1 - 错误:%1 - - - - From %1 - 来自 %1 - - - - 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 件任务需要下载。如果您现在退出软件,可能会造成传输延时。是否等同步完成? - - - - 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”将在浏览器中打开。可能会有安全风险,可能会暴露您或下载恶意数据。确定吗? - - - - HTML detected, click here to display - 检测到HTML,单击此处来显示内容。 - - - - Click here to disable HTML - 单击此处以禁止HTML。 - - - - MsgDecode - - - The message has an unknown encoding. -Perhaps you should upgrade Bitmessage. - 这些消息使用了未知编码方式。 -您可能需要更新Bitmessage软件。 - - - - Unknown encoding - 未知编码 - 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 - + 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阶段。 - + <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>版权©2012-2016 Jonathan Warren<br/>版权©2013-2016的比特信开发人员</p></body></html> @@ -1927,45 +1530,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 +1571,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 @@ -2011,314 +1604,203 @@ The 'Random Number' option is selected by default but deterministic ad 总连接: - + Since startup: 自启动: - + Processed 0 person-to-person messages. 处理0人对人的信息. - + Processed 0 public keys. 处理0公钥。 - + Processed 0 broadcasts. 处理0广播. - + Inventory lookups per second: 0 每秒库存查询: 0 - + Objects to be synced: 对象 已同步: - + Stream # 数据流 # Connections - + 连接 - + Since startup on %1 自从%1启动 - + Down: %1/s Total: %2 下: %1/秒 总计: %2 - + Up: %1/s Total: %2 上: %1/秒 总计: %2 - + Total Connections: %1 总的连接数: %1 - + Inventory lookups per second: %1 每秒库存查询: %1 - + Up: 0 kB/s 上载: 0 kB /秒 - + Down: 0 kB/s 下载: 0 kB /秒 - + 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公钥. - - - Peer - 节点 - - - - IP address or hostname - IP地址或主机名 - - - - Rating - 评分 - - - - PyBitmessage tracks the success rate of connection attempts to individual nodes. The rating ranges from -1 to 1 and affects the likelihood of selecting the node in the future - PyBitmessage 跟踪连接尝试到各个节点的成功率。评分范围从 -1 到 1 ,这会影响到未来选择节点的可能性。 - - - - User agent - 用户代理 - - - - Peer's self-reported software - 节点自我报告的软件 - - - - TLS - TLS - - - - Connection encryption - 连接加密 - - - - List of streams negotiated between you and the peer - 您和节点之间已协商的流列表 - newChanDialog Dialog - + 对话框 Create a new chan - + 创建一个新的频道 Join a chan - + 加入一个频道 Create a chan - + 创建一个频道 <html><head/><body><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. If you and someone else both create a chan with the same chan name then it is currently very likely that they will be the same chan.</p></body></html> - + <html><head/><body><p>为您的频道起一个名字。如果您选择了一个足够难的名字(比如一个唯一而且强度很高的密码)而您的朋友们也没有公开这个名字,那么频道将会是私密并安全的。目前看来,如果有人和您使用相同的名字创建频道,创建的频道将和您的相同。</p></body></html> Chan name: - + 频道名称: <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 normal person-to-person message to the chan address.</p><p>Chans are experimental and completely unmoderatable.</p></body></html> - + <html><head/><body><p>一个频道存在于一群共有同一个解密密钥的人之间。频道的密钥和比特信地址生成自可读的文字或密码(频道的名字)。要给一个频道中的每一个人发送消息,仅仅需要发送一个普通的点对点消息到频道的地址。</p><p>频道是实验性的且不受到监管。</p></body></html> Chan bitmessage address: - - - - - Create or join a chan - 创建或加入一个频道 - - - - <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> - - - - Chan passphrase/name: - 通道密码/名称: - - - - Optional, for advanced usage - 可选,适用于高级应用 - - - - Chan address - 频道地址 - - - - Please input chan name/passphrase: - 请输入频道名字: - - - - newchandialog - - - Successfully created / joined chan %1 - 成功创建或加入频道%1 - - - - Chan creation / joining failed - 频道创建或加入失败 - - - - Chan creation / joining cancelled - 频道创建或加入已取消 - - - - proofofwork - - - C PoW module built successfully. - C PoW模块编译成功。 - - - - Failed to build C PoW module. Please build it manually. - 无法编译C PoW模块。请手动编译。 - - - - C PoW module unavailable. Please build it. - C PoW模块不可用。请编译它。 + 频道的地址: 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,310 +1808,295 @@ 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 最大可接受难度 Hardware GPU acceleration (OpenCL) - + 硬件GPU加速 (OpenCL) - + <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): - \ No newline at end of file diff --git a/src/upnp.py b/src/upnp.py index 42ff0c6d..50b25b8e 100644 --- a/src/upnp.py +++ b/src/upnp.py @@ -1,30 +1,17 @@ -# 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 queues -import state +import socket +from struct import unpack, pack +import threading +import time +from helper_threading import * +import shared import tr -from bmconfigparser import config -from debug import logger -from network import connectionpool, knownnodes, StoppableThread -from network.node import Peer - -def createRequestXML(service, action, arguments=None): - """Router UPnP requests are XML formatted""" +def createRequestXML(service, action, arguments=[]): + from xml.dom.minidom import Document doc = Document() @@ -49,12 +36,11 @@ def createRequestXML(service, action, arguments=None): # iterate over arguments, create nodes, create text nodes, # append text nodes to nodes, and finally add the ready product # to argument_list - if arguments is not None: - for k, v in arguments: - tmp_node = doc.createElement(k) - tmp_text_node = doc.createTextNode(v) - tmp_node.appendChild(tmp_text_node) - argument_list.append(tmp_node) + for k, v in arguments: + tmp_node = doc.createElement(k) + tmp_text_node = doc.createTextNode(v) + tmp_node.appendChild(tmp_text_node) + argument_list.append(tmp_node) # append the prepared argument nodes to the function element for arg in argument_list: @@ -72,24 +58,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 +87,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 +103,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 - - def soapRequest(self, service, action, arguments=None): - """Make a request to a router""" - + 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=[]): + from xml.dom.minidom import parseString + from debug import logger conn = httplib.HTTPConnection(self.routerPath.hostname, self.routerPath.port) conn.request( 'POST', @@ -181,8 +150,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,161 +159,133 @@ 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") + self.localPort = shared.config.getint('bitmessagesettings', 'port') + try: + self.extPort = shared.config.getint('bitmessagesettings', 'extport') + except: + self.extPort = None self.localIP = self.getLocalIP() self.routers = [] self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - self.sock.bind((self.localIP, 0)) + self.sock.bind((self.localIP, 10000)) 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 - - # wait until asyncore binds so that we know the listening port - bound = False - while state.shutdown == 0 and not self._stopped and not bound: - for s in connectionpool.pool.listeningSockets.values(): - if s.is_bound(): - bound = True - 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: + while shared.shutdown == 0 and shared.safeConfigGetBoolean('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 shared.shutdown == 0 and shared.safeConfigGetBoolean('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)))) + shared.UISignalQueue.put(('updateStatusBar', tr._translate("MainWindow",'UPnP port mapping established on port %1').arg(str(self.extPort)))) 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'))) + shared.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() + shared.config.set('bitmessagesettings', 'extport', str(extPort)) 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/src/version.py b/src/version.py deleted file mode 100644 index 076b8c56..00000000 --- a/src/version.py +++ /dev/null @@ -1,2 +0,0 @@ -softwareName = 'PyBitmessage' -softwareVersion = '0.6.3.2' 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 3524bb57..00000000 --- a/tox.ini +++ /dev/null @@ -1,86 +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:lint] -skip_install = true -basepython = python3 -deps = - -rrequirements.txt - pylint -commands = pylint --rcfile=tox.ini --exit-zero pybitmessage - -[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 - -[pylint.main] -disable = - invalid-name,consider-using-f-string,fixme,raise-missing-from, - super-with-arguments,unnecessary-pass,unknown-option-value, - unspecified-encoding,useless-object-inheritance,useless-option-value -ignore = bitmessagecurses,bitmessagekivy,bitmessageqt,messagetypes,mockbm, - network,plugins,umsgpack,bitmessagecli.py - -max-args = 8 -max-attributes = 8