diff --git a/.buildbot/android/Dockerfile b/.buildbot/android/Dockerfile deleted file mode 100755 index 2d722834..00000000 --- a/.buildbot/android/Dockerfile +++ /dev/null @@ -1,102 +0,0 @@ -# A container for buildbot - -FROM ubuntu:focal AS android - -ENV DEBIAN_FRONTEND=noninteractive -ENV ANDROID_HOME="/opt/android" - -RUN apt-get update -qq > /dev/null \ - && apt-get -y install -qq --no-install-recommends locales \ - && locale-gen en_US.UTF-8 -ENV LANG="en_US.UTF-8" \ - LANGUAGE="en_US.UTF-8" \ - LC_ALL="en_US.UTF-8" - -# install system/build dependencies -RUN apt-get -y update -qq \ - && apt-get -y install -qq --no-install-recommends \ - curl autoconf automake build-essential cmake git nano libtool \ - libltdl-dev libffi-dev libssl-dev \ - patch pkg-config python-is-python3 python3-dev python3-pip unzip zip - -RUN apt-get -y install -qq --no-install-recommends openjdk-17-jdk \ - && apt-get -y autoremove - -RUN pip install pip install buildozer cython virtualenv - - -ENV ANDROID_NDK_HOME="${ANDROID_HOME}/android-ndk" -ENV ANDROID_NDK_VERSION="25b" -ENV ANDROID_NDK_HOME_V="${ANDROID_NDK_HOME}-r${ANDROID_NDK_VERSION}" - -# get the latest version from https://developer.android.com/ndk/downloads/index.html -ENV ANDROID_NDK_ARCHIVE="android-ndk-r${ANDROID_NDK_VERSION}-linux.zip" -ENV ANDROID_NDK_DL_URL="https://dl.google.com/android/repository/${ANDROID_NDK_ARCHIVE}" -# download and install Android NDK -RUN curl "${ANDROID_NDK_DL_URL}" --output "${ANDROID_NDK_ARCHIVE}" \ - && mkdir -p "${ANDROID_NDK_HOME_V}" \ - && unzip -q "${ANDROID_NDK_ARCHIVE}" -d "${ANDROID_HOME}" \ - && ln -sfn "${ANDROID_NDK_HOME_V}" "${ANDROID_NDK_HOME}" \ - && rm -rf "${ANDROID_NDK_ARCHIVE}" - -ENV ANDROID_SDK_HOME="${ANDROID_HOME}/android-sdk" - -# get the latest version from https://developer.android.com/studio/index.html -ENV ANDROID_SDK_TOOLS_VERSION="11076708" -ENV ANDROID_SDK_BUILD_TOOLS_VERSION="34.0.0" -ENV ANDROID_SDK_CMDLINE_TOOLS_VERSION="12.0" -ENV ANDROID_SDK_TOOLS_ARCHIVE="commandlinetools-linux-${ANDROID_SDK_TOOLS_VERSION}_latest.zip" -ENV ANDROID_SDK_TOOLS_DL_URL="https://dl.google.com/android/repository/${ANDROID_SDK_TOOLS_ARCHIVE}" -ENV ANDROID_CMDLINE_TOOLS_DIR="${ANDROID_SDK_HOME}/cmdline-tools/${ANDROID_SDK_CMDLINE_TOOLS_VERSION}" -ENV ANDROID_SDK_MANAGER="${ANDROID_CMDLINE_TOOLS_DIR}/bin/sdkmanager --sdk_root=${ANDROID_SDK_HOME}" - -# download and install Android SDK -RUN curl "${ANDROID_SDK_TOOLS_DL_URL}" --output "${ANDROID_SDK_TOOLS_ARCHIVE}" \ - && mkdir -p "${ANDROID_SDK_HOME}/cmdline-tools" \ - && unzip -q "${ANDROID_SDK_TOOLS_ARCHIVE}" \ - -d "${ANDROID_SDK_HOME}/cmdline-tools" \ - && mv "${ANDROID_SDK_HOME}/cmdline-tools/cmdline-tools" \ - ${ANDROID_CMDLINE_TOOLS_DIR} \ - && ln -sfn ${ANDROID_CMDLINE_TOOLS_DIR} "${ANDROID_SDK_HOME}/tools" \ - && rm -rf "${ANDROID_SDK_TOOLS_ARCHIVE}" - -# update Android SDK, install Android API, Build Tools... -RUN mkdir -p "${ANDROID_SDK_HOME}/.android/" \ - && echo '### User Sources for Android SDK Manager' \ - > "${ANDROID_SDK_HOME}/.android/repositories.cfg" - -# accept Android licenses (JDK necessary!) -RUN yes | ${ANDROID_SDK_MANAGER} --licenses > /dev/null - -# download platforms, API, build tools -RUN ${ANDROID_SDK_MANAGER} "platforms;android-30" > /dev/null \ - && ${ANDROID_SDK_MANAGER} "platforms;android-28" > /dev/null \ - && ${ANDROID_SDK_MANAGER} "platform-tools" > /dev/null \ - && ${ANDROID_SDK_MANAGER} "build-tools;${ANDROID_SDK_BUILD_TOOLS_VERSION}" \ - > /dev/null \ - && ${ANDROID_SDK_MANAGER} "extras;android;m2repository" > /dev/null \ - && chmod +x "${ANDROID_CMDLINE_TOOLS_DIR}/bin/avdmanager" - -# download ANT -ENV APACHE_ANT_VERSION="1.9.4" -ENV APACHE_ANT_ARCHIVE="apache-ant-${APACHE_ANT_VERSION}-bin.tar.gz" -ENV APACHE_ANT_DL_URL="https://archive.apache.org/dist/ant/binaries/${APACHE_ANT_ARCHIVE}" -ENV APACHE_ANT_HOME="${ANDROID_HOME}/apache-ant" -ENV APACHE_ANT_HOME_V="${APACHE_ANT_HOME}-${APACHE_ANT_VERSION}" - -RUN curl "${APACHE_ANT_DL_URL}" --output "${APACHE_ANT_ARCHIVE}" \ - && tar -xf "${APACHE_ANT_ARCHIVE}" -C "${ANDROID_HOME}" \ - && ln -sfn "${APACHE_ANT_HOME_V}" "${APACHE_ANT_HOME}" \ - && rm -rf "${APACHE_ANT_ARCHIVE}" - - -RUN useradd -m -U builder && mkdir /android - -WORKDIR /android - -RUN chown -R builder.builder /android "${ANDROID_SDK_HOME}" \ - && chmod -R go+w "${ANDROID_SDK_HOME}" - -USER builder - -ADD . . diff --git a/.buildbot/android/build.sh b/.buildbot/android/build.sh deleted file mode 100755 index 10176ef3..00000000 --- a/.buildbot/android/build.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash -export LC_ALL=en_US.UTF-8 -export LANG=en_US.UTF-8 -pushd packages/android -buildozer android debug || exit $? -popd - -mkdir -p ../out -cp packages/android/bin/*.apk ../out diff --git a/.buildbot/android/test.sh b/.buildbot/android/test.sh deleted file mode 100755 index b61fac85..00000000 --- a/.buildbot/android/test.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -unzip -p packages/android/bin/*.apk assets/private.tar \ - | tar --list -z > package.list -cat package.list -cat package.list | grep '\.sql$' || exit 1 diff --git a/.buildbot/appimage/Dockerfile b/.buildbot/appimage/Dockerfile deleted file mode 100644 index 6c2b87d5..00000000 --- a/.buildbot/appimage/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -FROM ubuntu:bionic - -RUN apt-get update - -RUN apt-get install -yq --no-install-suggests --no-install-recommends \ - ca-certificates software-properties-common \ - build-essential libcap-dev libssl-dev \ - python-all-dev python-setuptools wget \ - git gtk-update-icon-cache \ - binutils-multiarch crossbuild-essential-armhf crossbuild-essential-arm64 - -RUN dpkg --add-architecture armhf -RUN dpkg --add-architecture arm64 - -RUN sed -iE "s|deb |deb [arch=amd64] |g" /etc/apt/sources.list \ - && echo "deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports/ bionic main universe" >> /etc/apt/sources.list \ - && echo "deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main universe" >> /etc/apt/sources.list - -RUN apt-get update | true - -RUN apt-get install -yq libssl-dev:armhf libssl-dev:arm64 - -RUN wget -qO appimage-builder-x86_64.AppImage \ - https://github.com/AppImageCrafters/appimage-builder/releases/download/v1.1.0/appimage-builder-1.1.0-x86_64.AppImage - -ADD . . diff --git a/.buildbot/appimage/build.sh b/.buildbot/appimage/build.sh deleted file mode 100755 index c592c1e8..00000000 --- a/.buildbot/appimage/build.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/bash - -export APPIMAGE_EXTRACT_AND_RUN=1 -BUILDER=appimage-builder-x86_64.AppImage -RECIPE=packages/AppImage/AppImageBuilder.yml - -export APP_VERSION=$(git describe --tags | cut -d- -f1,3 | tr -d v) - -function set_sourceline { - if [ ${ARCH} == amd64 ]; then - export SOURCELINE="deb http://archive.ubuntu.com/ubuntu/ bionic main universe" - else - export SOURCELINE="deb [arch=${ARCH}] http://ports.ubuntu.com/ubuntu-ports/ bionic main universe" - fi -} - -[ -f ${BUILDER} ] || wget -qO ${BUILDER} \ - https://github.com/AppImageCrafters/appimage-builder/releases/download/v1.1.0/appimage-builder-1.1.0-x86_64.AppImage \ - && chmod +x ${BUILDER} - - -export ARCH=amd64 -export APPIMAGE_ARCH=x86_64 -export RUNTIME=${APPIMAGE_ARCH} -set_sourceline - -./${BUILDER} --recipe ${RECIPE} || exit 1 - -export ARCH=armhf -export APPIMAGE_ARCH=${ARCH} -export RUNTIME=gnueabihf -export CC=arm-linux-gnueabihf-gcc -export CXX=${CC} -set_sourceline - -./${BUILDER} --recipe ${RECIPE} || exit 1 - -export ARCH=arm64 -export APPIMAGE_ARCH=aarch64 -export RUNTIME=${APPIMAGE_ARCH} -export CC=aarch64-linux-gnu-gcc -export CXX=${CC} -set_sourceline - -./${BUILDER} --recipe ${RECIPE} - -mkdir -p ../out -sha256sum PyBitmessage*.AppImage > ../out/SHA256SUMS -cp PyBitmessage*.AppImage ../out diff --git a/.buildbot/appimage/test.sh b/.buildbot/appimage/test.sh deleted file mode 100755 index 871fc83a..00000000 --- a/.buildbot/appimage/test.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -export APPIMAGE_EXTRACT_AND_RUN=1 - -chmod +x PyBitmessage-*-x86_64.AppImage -./PyBitmessage-*-x86_64.AppImage -t diff --git a/.buildbot/kivy/Dockerfile b/.buildbot/kivy/Dockerfile deleted file mode 100644 index 10b1e569..00000000 --- a/.buildbot/kivy/Dockerfile +++ /dev/null @@ -1,18 +0,0 @@ -# A container for buildbot -FROM ubuntu:focal AS kivy - -ENV DEBIAN_FRONTEND=noninteractive - -ENV SKIPCACHE=2022-08-29 - -RUN apt-get update - -RUN apt-get install -yq \ - build-essential libcap-dev libssl-dev \ - libmtdev-dev libpq-dev \ - python3-dev python3-pip python3-virtualenv \ - xvfb ffmpeg xclip xsel - -RUN ln -sf /usr/bin/python3 /usr/bin/python - -RUN pip3 install --upgrade setuptools pip diff --git a/.buildbot/kivy/build.sh b/.buildbot/kivy/build.sh deleted file mode 100755 index 87aae8f7..00000000 --- a/.buildbot/kivy/build.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh - -pip3 install -r kivy-requirements.txt - -export INSTALL_TESTS=True - -pip3 install . diff --git a/.buildbot/kivy/test.sh b/.buildbot/kivy/test.sh deleted file mode 100755 index 3231f250..00000000 --- a/.buildbot/kivy/test.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -export INSTALL_TESTS=True - -xvfb-run --server-args="-screen 0, 720x1280x24" python3 tests-kivy.py diff --git a/.buildbot/snap/Dockerfile b/.buildbot/snap/Dockerfile deleted file mode 100644 index 7fde093d..00000000 --- a/.buildbot/snap/Dockerfile +++ /dev/null @@ -1,7 +0,0 @@ -FROM ubuntu:bionic - -ENV SKIPCACHE=2022-07-17 - -RUN apt-get update - -RUN apt-get install -yq --no-install-suggests --no-install-recommends snapcraft diff --git a/.buildbot/snap/build.sh b/.buildbot/snap/build.sh deleted file mode 100755 index 5d65a745..00000000 --- a/.buildbot/snap/build.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -pushd packages && snapcraft || exit 1 - -popd -mkdir -p ../out -mv packages/pybitmessage*.snap ../out -cd ../out -sha256sum pybitmessage*.snap > SHA256SUMS diff --git a/.buildbot/tox-bionic/Dockerfile b/.buildbot/tox-bionic/Dockerfile deleted file mode 100644 index 5cc36b7f..00000000 --- a/.buildbot/tox-bionic/Dockerfile +++ /dev/null @@ -1,22 +0,0 @@ -FROM ubuntu:bionic - -ENV DEBIAN_FRONTEND=noninteractive - -RUN apt-get update - -RUN apt-get install -yq --no-install-suggests --no-install-recommends \ - software-properties-common build-essential libcap-dev libffi-dev \ - libssl-dev python-all-dev python-setuptools \ - python3-dev python3-pip python3.8 python3.8-dev python3.8-venv \ - python-msgpack python-qt4 language-pack-en qt5dxcb-plugin tor xvfb - -RUN apt-get install -yq sudo - -RUN echo 'builder ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers - -RUN python3.8 -m pip install setuptools wheel -RUN python3.8 -m pip install --upgrade pip tox virtualenv - -ENV LANG en_US.UTF-8 -ENV LANGUAGE en_US:en -ENV LC_ALL en_US.UTF-8 diff --git a/.buildbot/tox-bionic/build.sh b/.buildbot/tox-bionic/build.sh deleted file mode 100755 index 87f670ce..00000000 --- a/.buildbot/tox-bionic/build.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -sudo service tor start diff --git a/.buildbot/tox-bionic/test.sh b/.buildbot/tox-bionic/test.sh deleted file mode 100755 index b280953a..00000000 --- a/.buildbot/tox-bionic/test.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -tox -e lint-basic || exit 1 -tox diff --git a/.buildbot/tox-focal/Dockerfile b/.buildbot/tox-focal/Dockerfile deleted file mode 100644 index c2ba0ffc..00000000 --- a/.buildbot/tox-focal/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM ubuntu:focal - -ENV DEBIAN_FRONTEND=noninteractive - -RUN apt-get update - -RUN apt-get install -yq --no-install-suggests --no-install-recommends \ - software-properties-common build-essential libcap-dev libffi-dev \ - libssl-dev python-all-dev python-setuptools \ - python3-dev python3-pip python3.9 python3.9-dev python3.9-venv \ - language-pack-en qt5dxcb-plugin tor xvfb - -RUN python3.9 -m pip install --upgrade pip tox virtualenv - -ADD . . diff --git a/.buildbot/tox-focal/test.sh b/.buildbot/tox-focal/test.sh deleted file mode 120000 index a9f8525c..00000000 --- a/.buildbot/tox-focal/test.sh +++ /dev/null @@ -1 +0,0 @@ -../tox-bionic/test.sh \ No newline at end of file diff --git a/.buildbot/tox-jammy/Dockerfile b/.buildbot/tox-jammy/Dockerfile deleted file mode 100644 index 8ca63aa0..00000000 --- a/.buildbot/tox-jammy/Dockerfile +++ /dev/null @@ -1,12 +0,0 @@ -FROM ubuntu:jammy - -ENV DEBIAN_FRONTEND=noninteractive - -RUN apt-get update - -RUN apt-get install -yq --no-install-suggests --no-install-recommends \ - software-properties-common build-essential libcap-dev libffi-dev \ - libssl-dev python-all-dev python-is-python3 python-setuptools \ - python3-dev python3-pip language-pack-en qt5dxcb-plugin tor xvfb - -RUN pip install tox diff --git a/.buildbot/tox-jammy/test.sh b/.buildbot/tox-jammy/test.sh deleted file mode 100755 index 3de38252..00000000 --- a/.buildbot/tox-jammy/test.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -tox -e lint-basic # || exit 1 -tox -e py310 diff --git a/.buildbot/winebuild/Dockerfile b/.buildbot/winebuild/Dockerfile deleted file mode 100644 index 9b687f8f..00000000 --- a/.buildbot/winebuild/Dockerfile +++ /dev/null @@ -1,14 +0,0 @@ -FROM ubuntu:bionic - -ENV DEBIAN_FRONTEND=noninteractive - -RUN dpkg --add-architecture i386 - -RUN apt-get update - -RUN apt-get install -yq --no-install-suggests --no-install-recommends \ - software-properties-common build-essential libcap-dev libffi-dev \ - libssl-dev python-all-dev python-setuptools xvfb \ - mingw-w64 wine-stable winetricks wine32 wine64 - -ADD . . diff --git a/.buildbot/winebuild/build.sh b/.buildbot/winebuild/build.sh deleted file mode 100755 index fdf5bedc..00000000 --- a/.buildbot/winebuild/build.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh - -xvfb-run -a buildscripts/winbuild.sh || exit 1 - -mkdir -p ../out -mv packages/pyinstaller/dist/Bitmessage*.exe ../out -cd ../out -sha256sum Bitmessage*.exe > SHA256SUMS diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index 38a67283..00000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,41 +0,0 @@ -FROM ubuntu:latest - -ARG USERNAME=user -ARG USER_UID=1000 -ARG USER_GID=$USER_UID - -RUN apt-get update -RUN DEBIAN_FRONTEND=noninteractive apt-get install -y \ - curl \ - flake8 \ - gh \ - git \ - gnupg2 \ - jq \ - libcap-dev \ - libssl-dev \ - pylint \ - python-setuptools \ - python2.7 \ - python2.7-dev \ - python3 \ - python3-dev \ - python3-flake8 \ - python3-pip \ - python3-pycodestyle \ - software-properties-common \ - sudo \ - zsh - -RUN apt-add-repository ppa:deadsnakes/ppa - -RUN pip install 'tox<4' 'virtualenv<20.22.0' - -RUN groupadd --gid $USER_GID $USERNAME \ - && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \ - && chsh -s /usr/bin/zsh user \ - && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \ - && chmod 0440 /etc/sudoers.d/$USERNAME - -USER $USERNAME -WORKDIR /home/$USERNAME diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index 91a470aa..00000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "Codespaces Python3", - "extensions": [ - "cschleiden.vscode-github-actions", - "eamodio.gitlens", - "github.vscode-pull-request-github", - "ms-azuretools.vscode-docker", - "ms-python.flake8", - "ms-python.pylint", - "ms-python.python", - "ms-vsliveshare.vsliveshare", - "nwgh.bandit", - "the-compiler.python-tox", - "vscode-icons-team.vscode-icons", - "visualstudioexptteam.vscodeintellicode" - ], - "dockerFile": "Dockerfile", - "postCreateCommand": "pip3 install -r requirements.txt", - "updateContentCommand": "python2.7 setup.py install --user", - "remoteEnv": { - "PATH": "${containerEnv:PATH}:/home/user/.local/bin" - }, - "settings": { - "flake8.args": ["--config=setup.cfg"], - "pylint.args": ["--rcfile=setup.cfg"], - "terminal.integrated.shell.linux": "/usr/bin/zsh", - "terminal.integrated.defaultProfile.linux": "zsh", - "terminal.integrated.fontFamily": "'SourceCodePro+Powerline+Awesome Regular'", - "terminal.integrated.fontSize": 14, - "files.exclude": { - "**/CODE_OF_CONDUCT.md": true, - "**/LICENSE": true - } - } -} diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 77d72c64..00000000 --- a/.dockerignore +++ /dev/null @@ -1,7 +0,0 @@ -bin -build -dist -__pycache__ -.buildozer -.tox -mprofile_* diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 776a13c1..00000000 --- a/.gitattributes +++ /dev/null @@ -1,3 +0,0 @@ -# Pickle files (for testing) should always have UNIX line endings. -# Windows issue like here https://stackoverflow.com/questions/556269 -knownnodes.dat text eol=lf diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index fb735a84..00000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,30 +0,0 @@ -## Repository contributions to the PyBitmessage project - -### Code - -- Try to refer to github issue tracker or other permanent sources of discussion about the issue. -- It is clear from the diff *what* you have done, it may be less clear *why* you have done it so explain why this change is necessary rather than what it does. - -### Documentation - -Use `tox -e py27-doc` to build a local copy of the documentation. - -### Tests - -- If there has been a change to the code, there's a good possibility there should be a corresponding change to the tests -- To run tests locally use `tox` or `./run-tests-in-docker.sh` - -## Translations - -- For helping with translations, please use [Transifex](https://www.transifex.com/bitmessage-project/pybitmessage/). -- There is no need to submit pull requests for translations. -- For translating technical terms it is recommended to consult the [Microsoft Language Portal](https://www.microsoft.com/Language/en-US/Default.aspx). - -### Gitiquette - -- Make the pull request against the ["v0.6" branch](https://github.com/Bitmessage/PyBitmessage/tree/v0.6) -- PGP-sign the commits included in the pull request -- Use references to tickets, e.g. `addresses #123` or `fixes #234` in your commit messages -- Try to use a good editor that removes trailing whitespace, highlights potential python issues and uses unix line endings -- If for some reason you don't want to use github, you can submit the patch using Bitmessage to the "bitmessage" chan, or to one of the developers. - diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 415583a9..00000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,7 +0,0 @@ -# Basic dependabot.yml for kivymd -version: 2 -updates: - - package-ecosystem: "pip" - directory: "/" - schedule: - interval: "daily" diff --git a/.gitignore b/.gitignore index fc331499..36fbec0d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,27 +1,2 @@ **pyc -**.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 +**dat \ 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/.travis.yml b/.travis.yml deleted file mode 100644 index 9ecb65ba..00000000 --- a/.travis.yml +++ /dev/null @@ -1,23 +0,0 @@ -language: python -cache: pip -dist: bionic -python: - - "2.7_with_system_site_packages" - - "3.7" -addons: - apt: - packages: - - build-essential - - libcap-dev - - python-qt4 - - python-pyqt5 - - tor - - xvfb -install: - - pip install -r requirements.txt - - python setup.py install - - export PYTHONWARNINGS=all -script: - - python checkdeps.py - - python src/bitmessagemain.py -t - - python -bm tests diff --git a/COPYING b/COPYING index 279cef2a..e386f371 100644 --- a/COPYING +++ b/COPYING @@ -1,5 +1,4 @@ -Copyright (c) 2012-2016 Jonathan Warren -Copyright (c) 2012-2022 The Bitmessage Developers +Copyright (c) 2012 Jonathan Warren 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 deleted file mode 100644 index 7942a957..00000000 --- a/INSTALL.md +++ /dev/null @@ -1,86 +0,0 @@ -# PyBitmessage Installation Instructions -- Binary (64bit, no separate installation of dependencies required) - - Windows: https://download.bitmessage.org/snapshots/ - - Linux AppImages: https://artifacts.bitmessage.at/appimage/ - - Linux snaps: https://artifacts.bitmessage.at/snap/ - - Mac (not up to date): https://github.com/Bitmessage/PyBitmessage/releases/tag/v0.6.1 -- Source - `git clone git://github.com/Bitmessage/PyBitmessage.git` - -## Helper Script for building from source -Go to the directory with PyBitmessage source code and run: -``` -python checkdeps.py -``` -If there are missing dependencies, it will explain you what is missing -and for many Unix-like systems also what you have to do to resolve it. You need -to repeat calling the script until you get nothing mandatory missing. How you -then run setuptools depends on whether you want to install it to -user's directory or system. - -### If checkdeps fails, then verify manually which dependencies are missing from below -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 - -For Debian-based (Ubuntu, Raspbian, PiBang, others) -``` -python2.7 openssl libssl-dev python-msgpack python-qt4 python-six -``` -For Arch Linux -``` -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 -``` - -## setuptools -This is now the recommended and in most cases the easiest way for -installing PyBitmessage. - -There are 2 options for installing with setuptools: root and user. - -### as root: -``` -python setup.py install -pybitmessage -``` - -### as user: -``` -python setup.py install --user -~/.local/bin/pybitmessage -``` - -## pip venv (daemon): -Create virtualenv with Python 2.x version -``` -virtualenv -p python2 env -``` - -Activate env -``` -source env/bin/activate -``` - -Build & run pybitmessage -``` -pip install . -pybitmessage -d -``` - -## Alternative way to run PyBitmessage, without setuptools (this isn't recommended) -run `./start.sh`. diff --git a/LICENSE b/LICENSE index fd772201..de7d6159 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,5 @@ The MIT License (MIT) -Copyright (c) 2012-2016 Jonathan Warren -Copyright (c) 2012-2022 The Bitmessage Developers +Copyright (c) 2013 Bitmessage 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 +18,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 100755 index 00000000..5031881c --- /dev/null +++ b/Makefile @@ -0,0 +1,49 @@ +APP=pybitmessage +VERSION=0.3.1 +DEST_SHARE=/usr/share +DEST_APP=$(DEST_SHARE)/$(APP) + +all: + +debug: + +source: + tar -cvzf ../$(APP)_$(VERSION).orig.tar.gz ../$(APP)-$(VERSION) --exclude=.git + +install: + mkdir -m 755 -p $(DEST_APP) + mkdir -m 755 -p $(DEST_SHARE)/applications + mkdir -m 755 -p $(DEST_APP)/images + mkdir -m 755 -p $(DEST_APP)/pyelliptic + mkdir -m 755 -p $(DEST_APP)/socks + mkdir -m 755 -p $(DEST_APP)/bitmessageqt + mkdir -m 755 -p $(DEST_SHARE)/pixmaps + mkdir -m 755 -p $(DEST_SHARE)/icons + mkdir -m 755 -p $(DEST_SHARE)/icons/hicolor + mkdir -m 755 -p $(DEST_SHARE)/icons/hicolor/scalable + mkdir -m 755 -p $(DEST_SHARE)/icons/hicolor/scalable/apps + mkdir -m 755 -p $(DEST_SHARE)/icons/hicolor/24x24 + mkdir -m 755 -p $(DEST_SHARE)/icons/hicolor/24x24/apps + + install -m 644 src/*.ui $(DEST_APP) + install -m 644 src/*.py $(DEST_APP) + install -m 644 src/*.qrc $(DEST_APP) + + install -m 644 src/images/*.png $(DEST_APP)/images + install -m 644 src/images/*.ico $(DEST_APP)/images + install -m 644 src/pyelliptic/*.py $(DEST_APP)/pyelliptic + install -m 644 src/socks/*.py $(DEST_APP)/socks + install -m 644 src/bitmessageqt/*.py $(DEST_APP)/bitmessageqt + install -m 755 debian/pybm /usr/bin/pybitmessage + + install -m 644 desktop/$(APP).desktop $(DEST_SHARE)/applications/$(APP).desktop + install -m 644 src/images/can-icon-24px.png $(DEST_SHARE)/icons/hicolor/24x24/apps/$(APP).png + install -m 644 desktop/can-icon.svg $(DEST_SHARE)/icons/hicolor/scalable/apps/$(APP).svg + install -m 644 desktop/can-icon.svg $(DEST_SHARE)/pixmaps/$(APP).svg + +clean: + rm -rf debian/$(APP) + rm -f ../$(APP)_*.deb ../$(APP)_*.asc ../$(APP)_*.dsc ../$(APP)*.changes + rm -f *.sh~ src/*.pyc src/socks/*.pyc src/pyelliptic/*.pyc + rm -f *.deb \#* \.#* debian/*.log debian/*.substvars + rm -f Makefile~ diff --git a/README.md b/README.md index 06c97c01..979872f9 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,17 @@ 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. +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. -Development +references ---------- -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. - -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) -* [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) - +* [project website](https://bitmessage.org) +* [protocol specification](https://bitmessage.org/wiki/Protocol_specification) +* [whitepaper](https://bitmessage.org/bitmessage.pdf) 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/osx.sh b/buildscripts/osx.sh deleted file mode 100755 index e58a49f4..00000000 --- a/buildscripts/osx.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash - -# OS X Build script wrapper around the py2app script. -# This build can only be generated on OS X. -# Requires all build dependencies for Bitmessage -# Especially important is OpenSSL installed through brew - -export ARCHFLAGS="-arch i386 -arch x86_64" - -if [[ -z "$1" ]]; then - echo "Please supply a version number for this release as the first argument." - exit -fi - -echo "Creating OS X packages for Bitmessage." - -export PYBITMESSAGEVERSION=$1 - -cd src && python2.7 build_osx.py py2app - -if [[ $? = "0" ]]; then - hdiutil create -fs HFS+ -volname "Bitmessage" -srcfolder dist/Bitmessage.app dist/bitmessage-v$1.dmg -else - echo "Problem creating Bitmessage.app, stopping." - exit -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/debian.sh b/debian.sh new file mode 100755 index 00000000..ac2121d5 --- /dev/null +++ b/debian.sh @@ -0,0 +1,21 @@ +# To build a debian package first ensure that the code exists +# within a directory called pybitmessage-x.x.x (where the x's +# are the version number), make sure that the VERSION parameter +# within debian/rules and this script are correct, then run +# this script. + +#!/bin/bash + +APP=pybitmessage +VERSION=0.3.1 +ARCH_TYPE=all + +# Create a source archive +make clean +make source + +# Build the package +fakeroot dpkg-buildpackage -A + +gpg -ba ../${APP}_${VERSION}-1_${ARCH_TYPE}.deb +gpg -ba ../${APP}_${VERSION}.orig.tar.gz diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 00000000..c90ca319 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,112 @@ +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) Tue, 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) Tue, 1 Apr 2013 17:12:14 +0100 diff --git a/debian/compat b/debian/compat new file mode 100644 index 00000000..45a4fb75 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +8 diff --git a/debian/control b/debian/control new file mode 100644 index 00000000..2dd19194 --- /dev/null +++ b/debian/control @@ -0,0 +1,21 @@ +Source: pybitmessage +Section: contrib/comm +Priority: extra +Maintainer: Jonathan Warren +Build-Depends: debhelper (>= 8.0.0), python (>= 2.7.0), openssl, python-qt4, libqt4-dev (>= 4.8.0), python-qt4-dev, sqlite3, libsqlite3-dev, libmessaging-menu-dev +Standards-Version: 3.9.2 +Homepage: https://bitmessage.org/ +Vcs-Browser: https://github.com/Bitmessage/PyBitmessage +Vcs-Git: https://github.com/Bitmessage/PyBitmessage.git + +Package: pybitmessage +Architecture: all +Depends: ${misc:Depends}, python (>= 2.7.0), openssl, python-qt4, libqt4-dev (>= 4.8.0), python-qt4-dev, sqlite3, libsqlite3-dev, libmessaging-menu-dev +Description: Send encrypted messages to another person or to many subscribers + 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..4c5f69f3 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,30 @@ +Format: http://dep.debian.net/deps/dep5 +Upstream-Name: PyBitmessage +Source: https://github.com/Bitmessage/PyBitmessage + +Files: * +Copyright: 2012 Jonathan Warren +License: MIT + +Files: debian/* +Copyright: 2012 Jonathan Warren +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. \ No newline at end of file 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/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..0f873425 --- /dev/null +++ b/debian/rules @@ -0,0 +1,72 @@ +#!/usr/bin/make -f +APP=pybitmessage + +DEST_MAIN=$(CURDIR)/debian/$(APP)/usr/bin +DEST_SHARE=$(CURDIR)/debian/$(APP)/usr/share +DEST_APP=$(DEST_SHARE)/$(APP) + +build: build-stamp + make +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 + + mkdir -m 755 -p $(CURDIR)/debian/$(APP)/usr + mkdir -m 755 -p $(CURDIR)/debian/$(APP)/usr/bin + mkdir -m 755 -p $(DEST_APP) + mkdir -m 755 -p $(DEST_SHARE)/applications + mkdir -m 755 -p $(DEST_APP)/images + mkdir -m 755 -p $(DEST_APP)/pyelliptic + mkdir -m 755 -p $(DEST_APP)/socks + mkdir -m 755 -p $(DEST_APP)/bitmessageqt + mkdir -m 755 -p $(DEST_SHARE)/pixmaps + mkdir -m 755 -p $(DEST_SHARE)/icons + mkdir -m 755 -p $(DEST_SHARE)/icons/hicolor + mkdir -m 755 -p $(DEST_SHARE)/icons/hicolor/scalable + mkdir -m 755 -p $(DEST_SHARE)/icons/hicolor/scalable/apps + mkdir -m 755 -p $(DEST_SHARE)/icons/hicolor/24x24 + mkdir -m 755 -p $(DEST_SHARE)/icons/hicolor/24x24/apps + + install -m 644 $(CURDIR)/src/*.ui $(DEST_APP) + install -m 644 $(CURDIR)/src/*.py $(DEST_APP) + install -m 644 $(CURDIR)/src/*.qrc $(DEST_APP) + + install -m 644 $(CURDIR)/src/images/*.png $(DEST_APP)/images + install -m 644 $(CURDIR)/src/images/*.ico $(DEST_APP)/images + install -m 644 $(CURDIR)/src/pyelliptic/*.py $(DEST_APP)/pyelliptic + install -m 644 $(CURDIR)/src/socks/*.py $(DEST_APP)/socks + install -m 644 $(CURDIR)/src/bitmessageqt/*.py $(DEST_APP)/bitmessageqt + install -m 755 $(CURDIR)/debian/pybm $(DEST_MAIN)/pybitmessage + + install -m 644 $(CURDIR)/desktop/$(APP).desktop $(DEST_SHARE)/applications/$(APP).desktop + install -m 644 $(CURDIR)/src/images/can-icon-24px.png $(DEST_SHARE)/icons/hicolor/24x24/apps/$(APP).png + install -m 644 $(CURDIR)/desktop/can-icon.svg $(DEST_SHARE)/icons/hicolor/scalable/apps/$(APP).svg + install -m 644 $(CURDIR)/desktop/can-icon.svg $(DEST_SHARE)/pixmaps/$(APP).svg +binary-indep: build install + dh_shlibdeps + 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 +.PHONY: build clean binary-indep 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/icon14.xpm b/desktop/icon14.xpm deleted file mode 100644 index 6d50c8c7..00000000 --- a/desktop/icon14.xpm +++ /dev/null @@ -1,111 +0,0 @@ -/* XPM */ -static char * icon14_xpm[] = { -"14 14 94 2", -" c None", -". c #B9BABC", -"+ c #D2D3D4", -"@ c #BEBFC1", -"# c #CBCCCF", -"$ c #E0E3E1", -"% c #F6F8F8", -"& c #F3F3F3", -"* c #B9BABD", -"= c #C8C9CB", -"- c #DADCDB", -"; c #E6E8E7", -"> c #F7F7F7", -", c #FCFCFC", -"' c #F5F5F5", -") c #BCBDBF", -"! c #D3D5D5", -"~ c #E3E5E4", -"{ c #F1F2F2", -"] c #FDFDFD", -"^ c #F8F8F8", -"/ c #CBCCCC", -"( c #B2B3B6", -"_ c #B0B1B3", -": c #D3D4D6", -"< c #DFE0E0", -"[ c #EAEDEB", -"} c #FAF9F9", -"| c #DFE0DF", -"1 c #B9BBBD", -"2 c #C2C3C5", -"3 c #B7B8BC", -"4 c #CDCED0", -"5 c #DCDDDE", -"6 c #E7E9E7", -"7 c #F6F6F6", -"8 c #C0C1C2", -"9 c #DDDFDF", -"0 c #BCBCBF", -"a c #D7D9DA", -"b c #E2E4E3", -"c c #F0F2F1", -"d c #FAFAFA", -"e c #F9F9F9", -"f c #CCCDCD", -"g c #B6B7B9", -"h c #C7C8CA", -"i c #A6A7A9", -"j c #D3D4D5", -"k c #F2F5F3", -"l c #F1F2F1", -"m c #F6F8F7", -"n c #FCFBFC", -"o c #E8EAE9", -"p c #B6B7B8", -"q c #BFC0C2", -"r c #323138", -"s c #1D1D22", -"t c #111117", -"u c #4C4C51", -"v c #ECECED", -"w c #FFFFFF", -"x c #BBBDBD", -"y c #C9CACB", -"z c #333238", -"A c #313036", -"B c #27272C", -"C c #1E1F24", -"D c #16171D", -"E c #919193", -"F c #F2F3F3", -"G c #B4B5B7", -"H c #CDCFCF", -"I c #67666B", -"J c #37363C", -"K c #2C2B31", -"L c #2A292F", -"M c #16171C", -"N c #68696B", -"O c #C7C8C9", -"P c #CBCDCC", -"Q c #49474E", -"R c #39383E", -"S c #36353B", -"T c #333138", -"U c #28272D", -"V c #CED0D0", -"W c #67676C", -"X c #414046", -"Y c #424147", -"Z c #39383F", -"` c #8C8D8F", -" . c #6B6C70", -".. c #75757A", -" . + ", -" @ # $ % & ", -" * = - ; > , ' ", -" ) ! ~ { ] ^ / ( ", -" _ : < [ } , | 1 2 ", -" 3 4 5 6 7 , { 8 . 9 ", -" 2 0 a b c d e f g h ", -" i j k l m n o p q h ", -" r s t u v w ' x g y ", -" z A B C D E F G H ", -" I J A K L M N O P ", -" Q R R S T U V ", -" W X Y X Z ` ", -" ... "}; diff --git a/desktop/icon24.png b/desktop/icon24.png deleted file mode 100644 index 30f7313e..00000000 Binary files a/desktop/icon24.png and /dev/null differ diff --git a/desktop/pybitmessage.desktop b/desktop/pybitmessage.desktop index c30276e4..363908dd 100644 --- a/desktop/pybitmessage.desktop +++ b/desktop/pybitmessage.desktop @@ -1,9 +1,30 @@ [Desktop Entry] Type=Application Name=PyBitmessage -GenericName=Bitmessage Client -Comment=Send encrypted messages -Exec=pybitmessage %F +GenericName=PyBitmessage +X-GNOME-FullName=PyBitmessage Secure Messaging +Comment=Send encrypted messages to another person or to many subscribers +Exec=pybitmessage %U Icon=pybitmessage Terminal=false -Categories=Office;Email;Network; +Categories=Network;Email;Application +Keywords=Email;E-mail;Newsgroup;Messaging +X-MessagingMenu-UsesChatSection=true +X-Ubuntu-Gettext-Domain=pybitmessage + +Actions=Send;Subscribe;AddressBook + +[Desktop Action Send] +Name=Send +Exec=pybitmessage -s +OnlyShowIn=Unity; + +[Desktop Action Subscribe] +Name=Subscribe +Exec=pybitmessage -b +OnlyShowIn=Unity; + +[Desktop Action AddressBook] +Name=Address Book +Exec=pybitmessage -a +OnlyShowIn=Unity; \ No newline at end of file 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/docs/Makefile b/docs/Makefile deleted file mode 100644 index 3e91a7ea..00000000 --- a/docs/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -SPHINXPROJ = PyBitmessage -SOURCEDIR = . -BUILDDIR = _build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file diff --git a/docs/_static/custom.css b/docs/_static/custom.css deleted file mode 100644 index e0ba75c1..00000000 --- a/docs/_static/custom.css +++ /dev/null @@ -1,18 +0,0 @@ -/* Hide "On GitHub" section from versions menu */ -li.wy-breadcrumbs-aside > a.fa { - display: none; -} - -/* Override table width restrictions */ -/* @media screen and (min-width: 700px) { */ - -.wy-table-responsive table td { - /* !important prevents the common CSS stylesheets from overriding - this as on RTD they are loaded after this stylesheet */ - white-space: normal !important; -} - -.wy-table-responsive { - overflow: visible !important; -} -/* } */ diff --git a/docs/address.rst b/docs/address.rst deleted file mode 100644 index eec4bd2c..00000000 --- a/docs/address.rst +++ /dev/null @@ -1,106 +0,0 @@ -Address -======= - -Bitmessage adresses are Base58 encoded public key hashes. An address looks like -``BM-BcbRqcFFSQUUmXFKsPJgVQPSiFA3Xash``. All Addresses start with ``BM-``, -however clients should accept addresses without the prefix. PyBitmessage does -this. The reason behind this idea is the fact, that when double clicking on an -address for copy and paste, the prefix is usually not selected due to the dash -being a common separator. - -Public Key usage ----------------- - -Addresses may look complicated but they fulfill the purpose of verifying the -sender. A Message claiming to be from a specific address can simply be checked by -decoding a special field in the data packet with the public key, that represents -the address. If the decryption succeeds, the message is from the address it -claims to be. - -Length ------- - -Without the ``BM-`` prefix, an address is usually 32-34 chars long. Since an -address is a hash it can be calculated by the client in a way, that the first -bytes are zero (``\0``) and bitmessage strips these. This causes the client to do -much more work to be lucky and find such an address. This is an optional checkbox -in address generation dialog. - -Versions --------- - - * v1 addresses used a single RSA key pair - * v2 addresses use 2 ECC key pairs - * v3 addresses extends v2 addresses to allow specifying the proof of work - requirements. The pubkey object is signed to mitigate against - forgery/tampering. - * v4 addresses protect against harvesting addresses from getpubkey and pubkey - objects - -Address Types -------------- - -There are two address types the user can generate in PyBitmessage. The resulting -addresses have no difference, but the method how they are created differs. - -Random Address -^^^^^^^^^^^^^^ - -Random addresses are generated from a randomly chosen number. The resulting -address cannot be regenerated without knowledge of the number and therefore the -keys.dat should be backed up. Generating random addresses takes slightly longer -due to the POW required for the public key broadcast. - -Usage -""""" - - * Generate unique addresses - * Generate one time addresses. - - -Deterministic Address -^^^^^^^^^^^^^^^^^^^^^ - -For this type of Address a passphrase is required, that is used to seed the -random generator. Using the same passphrase creates the same addresses. -Using deterministic addresses should be done with caution, using a word from a -dictionary or a common number can lead to others generating the same address and -thus being able to receive messages not intended for them. Generating a -deterministic address will not publish the public key. The key is sent in case -somebody requests it. This saves :doc:`pow` time, when generating a bunch of -addresses. - -Usage -""""" - - * Create the same address on multiple systems without the need of copying - keys.dat or an Address Block. - * create a Channel. (Use the *Join/create chan* option in the file menu instead) - * Being able to restore the address in case of address database corruption or - deletation. - -Address generation ------------------- - - 1. Create a private and a public key for encryption and signing (resulting in - 4 keys) - 2. Merge the public part of the signing key and the encryption key together. - (encoded in uncompressed X9.62 format) (A) - 3. Take the SHA512 hash of A. (B) - 4. Take the RIPEMD160 of B. (C) - 5. Repeat step 1-4 until you have a result that starts with a zero - (Or two zeros, if you want a short address). (D) - 6. Remove the zeros at the beginning of D. (E) - 7. Put the stream number (as a var_int) in front of E. (F) - 8. Put the address version (as a var_int) in front of F. (G) - 9. Take a double SHA512 (hash of a hash) of G and use the first four bytes as a - checksum, that you append to the end. (H) - 10. base58 encode H. (J) - 11. Put "BM-" in front J. (K) - -K is your full address - - .. note:: Bitmessage's base58 encoding uses the following sequence - (the same as Bitcoin's): - "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz". - Many existing libraries for base58 do not use this ordering. diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index b0cfef7b..00000000 --- a/docs/conf.py +++ /dev/null @@ -1,276 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Configuration file for the Sphinx documentation builder. - -For a full list of options see the documentation: -http://www.sphinx-doc.org/en/master/config -""" - -import os -import sys - -sys.path.insert(0, os.path.abspath('../src')) - -from importlib import import_module - -import version # noqa:E402 - - -# -- Project information ----------------------------------------------------- - -project = u'PyBitmessage' -copyright = u'2019-2022, The Bitmessage Team' # pylint: disable=redefined-builtin -author = u'The Bitmessage Team' - -# The short X.Y version -version = unicode(version.softwareVersion) - -# The full version, including alpha/beta/rc tags -release = version - -# -- General configuration --------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.coverage', # FIXME: unused - 'sphinx.ext.imgmath', # legacy unused - 'sphinx.ext.intersphinx', - 'sphinx.ext.linkcode', - 'sphinx.ext.napoleon', - 'sphinx.ext.todo', - 'sphinxcontrib.apidoc', - 'm2r', -] - -default_role = 'obj' - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -source_suffix = ['.rst', '.md'] - -# The master toctree document. -master_doc = 'index' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -# language = None - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path . -exclude_patterns = ['_build'] - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# Don't prepend every class or function name with full module path -add_module_names = False - -# A list of ignored prefixes for module index sorting. -modindex_common_prefix = ['pybitmessage.'] - - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = 'sphinx_rtd_theme' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -# html_theme_options = {} - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -html_css_files = [ - 'custom.css', -] - -# Custom sidebar templates, must be a dictionary that maps document names -# to template names. -# -# The default sidebars (for documents that don't match any pattern) are -# defined by theme itself. Builtin themes are using these templates by -# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', -# 'searchbox.html']``. -# -# html_sidebars = {} - -html_show_sourcelink = False - -# -- Options for HTMLHelp output --------------------------------------------- - -# Output file base name for HTML help builder. -htmlhelp_basename = 'PyBitmessagedoc' - - -# -- Options for LaTeX output ------------------------------------------------ - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'PyBitmessage.tex', u'PyBitmessage Documentation', - u'The Bitmessage Team', 'manual'), -] - - -# -- Options for manual page output ------------------------------------------ - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'pybitmessage', u'PyBitmessage Documentation', - [author], 1) -] - - -# -- Options for Texinfo output ---------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'PyBitmessage', u'PyBitmessage Documentation', - author, 'PyBitmessage', 'One line description of project.', - 'Miscellaneous'), -] - - -# -- Options for Epub output ------------------------------------------------- - -# Bibliographic Dublin Core info. -epub_title = project -epub_author = author -epub_publisher = author -epub_copyright = copyright - -# The unique identifier of the text. This can be a ISBN number -# or the project homepage. -# -# epub_identifier = '' - -# A unique identification for the text. -# -# epub_uid = '' - -# A list of files that should not be packed into the epub file. -epub_exclude_files = ['search.html'] - - -# -- Extension configuration ------------------------------------------------- - -autodoc_mock_imports = [ - 'debug', - 'pybitmessage.bitmessagekivy', - 'pybitmessage.bitmessageqt.foldertree', - 'pybitmessage.helper_startup', - 'pybitmessage.mockbm', - 'pybitmessage.network.httpd', - 'pybitmessage.network.https', - 'ctypes', - 'dialog', - 'gi', - 'kivy', - 'logging', - 'msgpack', - 'numpy', - 'pkg_resources', - 'pycanberra', - 'pyopencl', - 'PyQt4', - 'PyQt5', - 'qrcode', - 'stem', - 'xdg', -] -autodoc_member_order = 'bysource' - -# Apidoc settings -apidoc_module_dir = '../pybitmessage' -apidoc_output_dir = 'autodoc' -apidoc_excluded_paths = [ - 'bitmessagekivy', 'build_osx.py', - 'bitmessageqt/addressvalidator.py', 'bitmessageqt/foldertree.py', - 'bitmessageqt/migrationwizard.py', 'bitmessageqt/newaddresswizard.py', - 'helper_startup.py', - 'kivymd', 'mockbm', 'main.py', 'navigationdrawer', 'network/http*', - 'src', 'tests', 'version.py' -] -apidoc_module_first = True -apidoc_separate_modules = True -apidoc_toc_file = False -apidoc_extra_args = ['-a'] - -# Napoleon settings -napoleon_google_docstring = True - - -# linkcode function -def linkcode_resolve(domain, info): - """This generates source URL's for sphinx.ext.linkcode""" - if domain != 'py' or not info['module']: - return - try: - home = os.path.abspath(import_module('pybitmessage').__path__[0]) - mod = import_module(info['module']).__file__ - except ImportError: - return - repo = 'https://github.com/Bitmessage/PyBitmessage/blob/v0.6/src%s' - path = mod.replace(home, '') - if path != mod: - # put the link only for top level definitions - if len(info['fullname'].split('.')) > 1: - return - if path.endswith('.pyc'): - path = path[:-1] - return repo % path - - -# -- Options for intersphinx extension --------------------------------------- - -# Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'https://docs.python.org/2.7/': None} - -# -- Options for todo extension ---------------------------------------------- - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True diff --git a/docs/encrypted_payload.rst b/docs/encrypted_payload.rst deleted file mode 100644 index 346d370d..00000000 --- a/docs/encrypted_payload.rst +++ /dev/null @@ -1,19 +0,0 @@ -+------------+-------------+-----------+--------------------------------------------+ -| Field Size | Description | Data type | Comments | -+============+=============+===========+============================================+ -| 16 | IV | uchar[] | Initialization Vector used for AES-256-CBC | -+------------+-------------+-----------+--------------------------------------------+ -| 2 | Curve type | uint16_t | Elliptic Curve type 0x02CA (714) | -+------------+-------------+-----------+--------------------------------------------+ -| 2 | X length | uint16_t | Length of X component of public key R | -+------------+-------------+-----------+--------------------------------------------+ -| X length | X | uchar[] | X component of public key R | -+------------+-------------+-----------+--------------------------------------------+ -| 2 | Y length | uint16_t | Length of Y component of public key R | -+------------+-------------+-----------+--------------------------------------------+ -| Y length | Y | uchar[] | Y component of public key R | -+------------+-------------+-----------+--------------------------------------------+ -| ? | encrypted | uchar[] | Cipher text | -+------------+-------------+-----------+--------------------------------------------+ -| 32 | MAC | uchar[] | HMACSHA256 Message Authentication Code | -+------------+-------------+-----------+--------------------------------------------+ diff --git a/docs/encryption.rst b/docs/encryption.rst deleted file mode 100644 index 61c7fb3e..00000000 --- a/docs/encryption.rst +++ /dev/null @@ -1,257 +0,0 @@ -Encryption -========== - -Bitmessage uses the Elliptic Curve Integrated Encryption Scheme -`(ECIES) `_ -to encrypt the payload of the Message and Broadcast objects. - -The scheme uses Elliptic Curve Diffie-Hellman -`(ECDH) `_ to generate a shared secret used -to generate the encryption parameters for Advanced Encryption Standard with -256bit key and Cipher-Block Chaining -`(AES-256-CBC) `_. -The encrypted data will be padded to a 16 byte boundary in accordance to -`PKCS7 `_. This -means that the data is padded with N bytes of value N. - -The Key Derivation Function -`(KDF) `_ used to -generate the key material for AES is -`SHA512 `_. The Message Authentication -Code (MAC) scheme used is `HMACSHA256 `_. - -Format ------- - -(See also: :doc:`protocol`) - -.. include:: encrypted_payload.rst - -In order to reconstitute a usable (65 byte) public key (starting with 0x04), -the X and Y components need to be expanded by prepending them with 0x00 bytes -until the individual component lengths are 32 bytes. - -Encryption ----------- - - 1. The destination public key is called K. - 2. Generate 16 random bytes using a secure random number generator. - Call them IV. - 3. Generate a new random EC key pair with private key called r and public key - called R. - 4. Do an EC point multiply with public key K and private key r. This gives you - public key P. - 5. Use the X component of public key P and calculate the SHA512 hash H. - 6. The first 32 bytes of H are called key_e and the last 32 bytes are called - key_m. - 7. Pad the input text to a multiple of 16 bytes, in accordance to PKCS7. [#f1]_ - 8. Encrypt the data with AES-256-CBC, using IV as initialization vector, - key_e as encryption key and the padded input text as payload. Call the - output cipher text. - 9. Calculate a 32 byte MAC with HMACSHA256, using key_m as salt and - IV + R [#f2]_ + cipher text as data. Call the output MAC. - -The resulting data is: IV + R + cipher text + MAC - -Decryption ----------- - - 1. The private key used to decrypt is called k. - 2. Do an EC point multiply with private key k and public key R. This gives you - public key P. - 3. Use the X component of public key P and calculate the SHA512 hash H. - 4. The first 32 bytes of H are called key_e and the last 32 bytes are called - key_m. - 5. Calculate MAC' with HMACSHA256, using key_m as salt and - IV + R + cipher text as data. - 6. Compare MAC with MAC'. If not equal, decryption will fail. - 7. Decrypt the cipher text with AES-256-CBC, using IV as initialization - vector, key_e as decryption key and the cipher text as payload. The output - is the padded input text. - -.. highlight:: nasm - -Partial Example ---------------- - -.. list-table:: Public key K: - :header-rows: 1 - :widths: auto - - * - Data - - Comments - * - - - :: - - 04 - 09 d4 e5 c0 ab 3d 25 fe - 04 8c 64 c9 da 1a 24 2c - 7f 19 41 7e 95 17 cd 26 - 69 50 d7 2c 75 57 13 58 - 5c 61 78 e9 7f e0 92 fc - 89 7c 9a 1f 17 20 d5 77 - 0a e8 ea ad 2f a8 fc bd - 08 e9 32 4a 5d de 18 57 - - Public key, 0x04 prefix, then 32 bytes X and 32 bytes Y. - - -.. list-table:: Initialization Vector IV: - :header-rows: 1 - :widths: auto - - * - Data - - Comments - * - - - :: - - bd db 7c 28 29 b0 80 38 - 75 30 84 a2 f3 99 16 81 - - 16 bytes generated with a secure random number generator. - -.. list-table:: Randomly generated key pair with private key r and public key R: - :header-rows: 1 - :widths: auto - - * - Data - - Comments - * - - - :: - - 5b e6 fa cd 94 1b 76 e9 - d3 ea d0 30 29 fb db 6b - 6e 08 09 29 3f 7f b1 97 - d0 c5 1f 84 e9 6b 8b a4 - - Private key r - * - - - :: - - 02 ca 00 20 - 02 93 21 3d cf 13 88 b6 - 1c 2a e5 cf 80 fe e6 ff - ff c0 49 a2 f9 fe 73 65 - fe 38 67 81 3c a8 12 92 - 00 20 - df 94 68 6c 6a fb 56 5a - c6 14 9b 15 3d 61 b3 b2 - 87 ee 2c 7f 99 7c 14 23 - 87 96 c1 2b 43 a3 86 5a - - Public key R - -.. list-table:: Derived public key P (point multiply r with K): - :header-rows: 1 - :widths: auto - - * - Data - - Comments - * - - - :: - - 04 - 0d b8 e3 ad 8c 0c d7 3f - a2 b3 46 71 b7 b2 47 72 - 9b 10 11 41 57 9d 19 9e - 0d c0 bd 02 4e ae fd 89 - ca c8 f5 28 dc 90 b6 68 - 11 ab ac 51 7d 74 97 be - 52 92 93 12 29 be 0b 74 - 3e 05 03 f4 43 c3 d2 96 - - Public key P - * - - - :: - - 0d b8 e3 ad 8c 0c d7 3f - a2 b3 46 71 b7 b2 47 72 - 9b 10 11 41 57 9d 19 9e - 0d c0 bd 02 4e ae fd 89 - - X component of public key P - -.. list-table:: SHA512 of public key P X component (H): - :header-rows: 1 - :widths: auto - - * - Data - - Comments - * - - - :: - - 17 05 43 82 82 67 86 71 - 05 26 3d 48 28 ef ff 82 - d9 d5 9c bf 08 74 3b 69 - 6b cc 5d 69 fa 18 97 b4 - - First 32 bytes of H called key_e - * - - - :: - - f8 3f 1e 9c c5 d6 b8 44 - 8d 39 dc 6a 9d 5f 5b 7f - 46 0e 4a 78 e9 28 6e e8 - d9 1c e1 66 0a 53 ea cd - - Last 32 bytes of H called key_m - -.. list-table:: Padded input: - :header-rows: 1 - :widths: auto - - * - Data - - Comments - * - - - :: - - 54 68 65 20 71 75 69 63 - 6b 20 62 72 6f 77 6e 20 - 66 6f 78 20 6a 75 6d 70 - 73 20 6f 76 65 72 20 74 - 68 65 20 6c 61 7a 79 20 - 64 6f 67 2e 04 04 04 04 - - The quick brown fox jumps over the lazy dog.0x04,0x04,0x04,0x04 - -.. list-table:: Cipher text: - :header-rows: 1 - :widths: auto - - * - Data - - Comments - * - - - :: - - 64 20 3d 5b 24 68 8e 25 - 47 bb a3 45 fa 13 9a 5a - 1d 96 22 20 d4 d4 8a 0c - f3 b1 57 2c 0d 95 b6 16 - 43 a6 f9 a0 d7 5a f7 ea - cc 1b d9 57 14 7b f7 23 - - 3 blocks of 16 bytes of encrypted data. - -.. list-table:: MAC: - :header-rows: 1 - :widths: auto - - * - Data - - Comments - * - - - :: - - f2 52 6d 61 b4 85 1f b2 - 34 09 86 38 26 fd 20 61 - 65 ed c0 21 36 8c 79 46 - 57 1c ea d6 90 46 e6 19 - - 32 bytes hash - - -.. rubric:: Footnotes - -.. [#f1] The pyelliptic implementation used in PyBitmessage takes unpadded data, - see :obj:`.pyelliptic.Cipher.ciphering`. -.. [#f2] The pyelliptic encodes the pubkey with curve and length, - see :obj:`.pyelliptic.ECC.get_pubkey` diff --git a/docs/extended_encoding.rst b/docs/extended_encoding.rst deleted file mode 100644 index 25539ad4..00000000 --- a/docs/extended_encoding.rst +++ /dev/null @@ -1,55 +0,0 @@ -Extended encoding -================= - -Extended encoding is an attempt to create a standard for transmitting structured -data. The goals are flexibility, wide platform support and extensibility. It is -currently available in the v0.6 branch and can be enabled by holding "Shift" -while clicking on Send. It is planned that v5 addresses will have to support -this. It's a work in progress, the basic plain text message works but don't -expect anthing else at this time. - -The data structure is in msgpack, then compressed with zlib. The top level is -a key/value store, and the "" key (empty string) contains the value of the type -of object, which can then have its individual format and standards. - -Text fields are encoded using UTF-8. - -Types ------ - -You can find the implementations in the ``src/messagetypes`` directory of -PyBitmessage. Each type has its own file which includes one class, and they are -dynamically loaded on startup. It's planned that this will also contain -initialisation, rendering and so on, so that developers can simply add a new -object type by adding a single file in the messagetypes directory and not have -to change any other part of the code. - -message -^^^^^^^ - -The replacement for the old messages. Mandatory keys are ``body`` and -``subject``, others are currently not implemented and not mandatory. Proposed -other keys: - -``parents``: - array of msgids referring to messages that logically precede it in a - conversation. Allows to create a threaded conversation view - -``files``: - array of files (which is a key/value pair): - - ``name``: - file name, mandatory - ``data``: - the binary data of the file - ``type``: - MIME content type - ``disposition``: - MIME content disposition, possible values are "inline" and "attachment" - -vote -^^^^ - -Dummy code available in the repository. Supposed to serve voting in a chan -(thumbs up/down) for decentralised moderation. Does not actually do anything at -the moment and specification can change. diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index 6edb0313..00000000 --- a/docs/index.rst +++ /dev/null @@ -1,30 +0,0 @@ -.. mdinclude:: ../README.md - :end-line: 20 - -Protocol documentation ----------------------- -.. toctree:: - :maxdepth: 2 - - protocol - address - encryption - pow - -Code documentation ------------------- -.. toctree:: - :maxdepth: 3 - - autodoc/pybitmessage - - -Indices and tables ------------------- - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - -.. mdinclude:: ../README.md - :start-line: 21 diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 2548d34e..00000000 --- a/docs/make.bat +++ /dev/null @@ -1,36 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=. -set BUILDDIR=_build -set SPHINXPROJ=PyBitmessage - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% - -:end -popd diff --git a/docs/pow.rst b/docs/pow.rst deleted file mode 100644 index 3786b075..00000000 --- a/docs/pow.rst +++ /dev/null @@ -1,77 +0,0 @@ -Proof of work -============= - -This page describes Bitmessage's Proof of work ("POW") mechanism as it exists in -Protocol Version 3. In this document, hash() means SHA512(). SHA512 was chosen -as it is widely supported and so that Bitcoin POW hardware cannot trivially be -used for Bitmessage POWs. The author acknowledges that they are essentially the -same algorithm with a different key size. - -Both ``averageProofOfWorkNonceTrialsPerByte`` and ``payloadLengthExtraBytes`` -are set by the owner of a Bitmessage address. The default and minimum for each -is 1000. (This is the same as difficulty 1. If the difficulty is 2, then this -value is 2000). The purpose of ``payloadLengthExtraBytes`` is to add some extra -weight to small messages. - -Do a POW --------- - -Let us use a ``msg`` message as an example:: - - payload = embeddedTime + encodedObjectVersion + encodedStreamNumber + encrypted - -``payloadLength`` - the length of payload, in bytes, + 8 - (to account for the nonce which we will append later) -``TTL`` - the number of seconds in between now and the object expiresTime. - -.. include:: pow_formula.rst - -:: - - initialHash = hash(payload) - -start with ``trialValue = 99999999999999999999`` - -also start with ``nonce = 0`` where nonce is 8 bytes in length and can be -hashed as if it is a string. - -:: - - while trialValue > target: - nonce = nonce + 1 - resultHash = hash(hash( nonce || initialHash )) - trialValue = the first 8 bytes of resultHash, converted to an integer - -When this loop finishes, you will have your 8 byte nonce value which you can -prepend onto the front of the payload. The message is then ready to send. - -Check a POW ------------ - -Let us assume that ``payload`` contains the payload for a msg message (the nonce -down through the encrypted message data). - -``nonce`` - the first 8 bytes of payload -``dataToCheck`` - the ninth byte of payload on down (thus it is everything except the nonce) - -:: - - initialHash = hash(dataToCheck) - - resultHash = hash(hash( nonce || initialHash )) - -``POWValue`` - the first eight bytes of resultHash converted to an integer -``TTL`` - the number of seconds in between now and the object ``expiresTime``. - -.. include:: pow_formula.rst - -If ``POWValue`` is less than or equal to ``target``, then the POW check passes. - - - diff --git a/docs/pow_formula.rst b/docs/pow_formula.rst deleted file mode 100644 index 16c3f174..00000000 --- a/docs/pow_formula.rst +++ /dev/null @@ -1,7 +0,0 @@ - -.. math:: - - target = \frac{2^{64}}{{\displaystyle - nonceTrialsPerByte (payloadLength + payloadLengthExtraBytes + \frac{ - TTL (payloadLength + payloadLengthExtraBytes)}{2^{16}}) - }} diff --git a/docs/protocol.rst b/docs/protocol.rst deleted file mode 100644 index 17a13dd9..00000000 --- a/docs/protocol.rst +++ /dev/null @@ -1,997 +0,0 @@ -Protocol specification -====================== - -.. warning:: All objects sent on the network should support protocol v3 - starting on Sun, 16 Nov 2014 22:00:00 GMT. - -.. toctree:: - :maxdepth: 2 - -Common standards ----------------- - -Hashes -^^^^^^ - -Most of the time `SHA-512 `_ hashes are -used, however `RIPEMD-160 `_ is also used -when creating an address. - -A double-round of SHA-512 is used for the Proof Of Work. Example of -double-SHA-512 encoding of string "hello": - -.. highlight:: nasm - -:: - - hello - 9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043(first round of sha-512) - 0592a10584ffabf96539f3d780d776828c67da1ab5b169e9e8aed838aaecc9ed36d49ff1423c55f019e050c66c6324f53588be88894fef4dcffdb74b98e2b200(second round of sha-512) - -For Bitmessage addresses (RIPEMD-160) this would give: - -:: - - hello - 9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043(first round is sha-512) - 79a324faeebcbf9849f310545ed531556882487e (with ripemd-160) - - -Common structures ------------------ - -All integers are encoded in big endian. (This is different from Bitcoin). - -.. list-table:: Message structure - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 4 - - magic - - uint32_t - - Magic value indicating message origin network, and used to seek to next - message when stream state is unknown - * - 12 - - command - - char[12] - - ASCII string identifying the packet content, NULL padded (non-NULL - padding results in packet rejected) - * - 4 - - length - - uint32_t - - Length of payload in number of bytes. Because of other restrictions, - there is no reason why this length would ever be larger than 1600003 - bytes. Some clients include a sanity-check to avoid processing messages - which are larger than this. - * - 4 - - checksum - - uint32_t - - First 4 bytes of sha512(payload) - * - ? - - message_payload - - uchar[] - - The actual data, a :ref:`message ` or an object_. - Not to be confused with objectPayload. - -Known magic values: - -+-------------+-------------------+ -| Magic value | Sent over wire as | -+=============+===================+ -| 0xE9BEB4D9 | E9 BE B4 D9 | -+-------------+-------------------+ - -.. _varint: - -Variable length integer -^^^^^^^^^^^^^^^^^^^^^^^ - -Integer can be encoded depending on the represented value to save space. -Variable length integers always precede an array/vector of a type of data that -may vary in length. Varints **must** use the minimum possible number of bytes to -encode a value. For example, the value 6 can be encoded with one byte therefore -a varint that uses three bytes to encode the value 6 is malformed and the -decoding task must be aborted. - -+---------------+----------------+------------------------------------------+ -| Value | Storage length | Format | -+===============+================+==========================================+ -| < 0xfd | 1 | uint8_t | -+---------------+----------------+------------------------------------------+ -| <= 0xffff | 3 | 0xfd followed by the integer as uint16_t | -+---------------+----------------+------------------------------------------+ -| <= 0xffffffff | 5 | 0xfe followed by the integer as uint32_t | -+---------------+----------------+------------------------------------------+ -| - | 9 | 0xff followed by the integer as uint64_t | -+---------------+----------------+------------------------------------------+ - -Variable length string -^^^^^^^^^^^^^^^^^^^^^^ - -Variable length string can be stored using a variable length integer followed by -the string itself. - -+------------+-------------+------------+----------------------------------+ -| Field Size | Description | Data type | Comments | -+============+=============+============+==================================+ -| 1+ | length | |var_int| | Length of the string | -+------------+-------------+------------+----------------------------------+ -| ? | string | char[] | The string itself (can be empty) | -+------------+-------------+------------+----------------------------------+ - -Variable length list of integers -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -n integers can be stored using n+1 :ref:`variable length integers ` -where the first var_int equals n. - -+------------+-------------+-----------+----------------------------+ -| Field Size | Description | Data type | Comments | -+============+=============+===========+============================+ -| 1+ | count | |var_int| | Number of var_ints below | -+------------+-------------+-----------+----------------------------+ -| 1+ | | var_int | The first value stored | -+------------+-------------+-----------+----------------------------+ -| 1+ | | var_int | The second value stored... | -+------------+-------------+-----------+----------------------------+ -| 1+ | | var_int | etc... | -+------------+-------------+-----------+----------------------------+ - -.. |var_int| replace:: :ref:`var_int ` - -Network address -^^^^^^^^^^^^^^^ - -When a network address is needed somewhere, this structure is used. Network -addresses are not prefixed with a timestamp or stream in the version_ message. - -.. list-table:: - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 8 - - time - - uint64 - - the Time. - * - 4 - - stream - - uint32 - - Stream number for this node - * - 8 - - services - - uint64_t - - same service(s) listed in version_ - * - 16 - - IPv6/4 - - char[16] - - IPv6 address. IPv4 addresses are written into the message as a 16 byte - `IPv4-mapped IPv6 address `_ - (12 bytes 00 00 00 00 00 00 00 00 00 00 FF FF, followed by the 4 bytes of - the IPv4 address). - * - 2 - - port - - uint16_t - - port number - -Inventory Vectors -^^^^^^^^^^^^^^^^^ - -Inventory vectors are used for notifying other nodes about objects they have or -data which is being requested. Two rounds of SHA-512 are used, resulting in a -64 byte hash. Only the first 32 bytes are used; the later 32 bytes are ignored. - -Inventory vectors consist of the following data format: - -+------------+-------------+-----------+--------------------+ -| Field Size | Description | Data type | Comments | -+============+=============+===========+====================+ -| 32 | hash | char[32] | Hash of the object | -+------------+-------------+-----------+--------------------+ - -Encrypted payload -^^^^^^^^^^^^^^^^^ - -Bitmessage uses `ECIES `_ to encrypt its messages. For more information see :doc:`encryption` - -.. include:: encrypted_payload.rst - -Unencrypted Message Data -^^^^^^^^^^^^^^^^^^^^^^^^ - -.. list-table:: - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 1+ - - msg_version - - var_int - - Message format version. **This field is not included after the - protocol v3 upgrade period**. - * - 1+ - - address_version - - var_int - - Sender's address version number. This is needed in order to calculate - the sender's address to show in the UI, and also to allow for forwards - compatible changes to the public-key data included below. - * - 1+ - - stream - - var_int - - Sender's stream number - * - 4 - - behavior bitfield - - uint32_t - - A bitfield of optional behaviors and features that can be expected from - the node with this pubkey included in this msg message (the sender's - pubkey). - * - 64 - - public signing key - - uchar[] - - The ECC public key used for signing (uncompressed format; - normally prepended with \x04) - * - 64 - - public encryption key - - uchar[] - - The ECC public key used for encryption (uncompressed format; - normally prepended with \x04) - * - 1+ - - nonce_trials_per_byte - - var_int - - Used to calculate the difficulty target of messages accepted by this - node. The higher this value, the more difficult the Proof of Work must - be before this individual will accept the message. This number is the - average number of nonce trials a node will have to perform to meet the - Proof of Work requirement. 1000 is the network minimum so any lower - values will be automatically raised to 1000. **This field is new and is - only included when the address_version >= 3**. - * - 1+ - - extra_bytes - - var_int - - Used to calculate the difficulty target of messages accepted by this - node. The higher this value, the more difficult the Proof of Work must - be before this individual will accept the message. This number is added - to the data length to make sending small messages more difficult. - 1000 is the network minimum so any lower values will be automatically - raised to 1000. **This field is new and is only included when the - address_version >= 3**. - * - 20 - - destination ripe - - uchar[] - - The ripe hash of the public key of the receiver of the message - * - 1+ - - encoding - - var_int - - :ref:`Message Encoding ` type - * - 1+ - - message_length - - var_int - - Message Length - * - message_length - - message - - uchar[] - - The message. - * - 1+ - - ack_length - - var_int - - Length of the acknowledgement data - * - ack_length - - ack_data - - uchar[] - - The acknowledgement data to be transmitted. This takes the form of a - Bitmessage protocol message, like another msg message. The POW therein - must already be completed. - * - 1+ - - sig_length - - var_int - - Length of the signature - * - sig_length - - signature - - uchar[] - - The ECDSA signature which covers the object header starting with the - time, appended with the data described in this table down to the - ack_data. - -.. _msg-encodings: - -Message Encodings -""""""""""""""""" - -.. list-table:: - :header-rows: 1 - :widths: auto - - * - Value - - Name - - Description - * - 0 - - IGNORE - - Any data with this number may be ignored. The sending node might simply - be sharing its public key with you. - * - 1 - - TRIVIAL - - UTF-8. No 'Subject' or 'Body' sections. Useful for simple strings - of data, like URIs or magnet links. - * - 2 - - SIMPLE - - UTF-8. Uses 'Subject' and 'Body' sections. No MIME is used. - :: - messageToTransmit = 'Subject:' + subject + '\n' + 'Body:' + message - * - 3 - - EXTENDED - - See :doc:`extended_encoding` - -Further values for the message encodings can be decided upon by the community. -Any MIME or MIME-like encoding format, should they be used, should make use of -Bitmessage's 8-bit bytes. - -.. _behavior-bitfield: - -Pubkey bitfield features -"""""""""""""""""""""""" - -.. list-table:: - :header-rows: 1 - :widths: auto - - * - Bit - - Name - - Description - * - 0 - - undefined - - The most significant bit at the beginning of the structure. Undefined - * - 1 - - undefined - - The next most significant bit. Undefined - * - ... - - ... - - ... - * - 27 - - onion_router - - (**Proposal**) Node can be used to onion-route messages. In theory any - node can onion route, but since it requires more resources, they may have - the functionality disabled. This field will be used to indicate that the - node is willing to do this. - * - 28 - - forward_secrecy - - (**Proposal**) Receiving node supports a forward secrecy encryption - extension. The exact design is pending. - * - 29 - - chat - - (**Proposal**) Address if for chatting rather than messaging. - * - 30 - - include_destination - - (**Proposal**) Receiving node expects that the RIPE hash encoded in their - address preceedes the encrypted message data of msg messages bound for - them. - - .. note:: since hardly anyone implements this, this will be redesigned as - `simple recipient verification `_ - * - 31 - - does_ack - - If true, the receiving node does send acknowledgements (rather than - dropping them). - -.. _msg-types: - -Message types -------------- - -Undefined messages received on the wire must be ignored. - -version -^^^^^^^ - -When a node creates an outgoing connection, it will immediately advertise its -version. The remote node will respond with its version. No futher communication -is possible until both peers have exchanged their version. - -.. list-table:: Payload - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 4 - - version - - int32_t - - Identifies protocol version being used by the node. Should equal 3. - Nodes should disconnect if the remote node's version is lower but - continue with the connection if it is higher. - * - 8 - - services - - uint64_t - - bitfield of features to be enabled for this connection - * - 8 - - timestamp - - int64_t - - standard UNIX timestamp in seconds - * - 26 - - addr_recv - - net_addr - - The network address of the node receiving this message (not including the - time or stream number) - * - 26 - - addr_from - - net_addr - - The network address of the node emitting this message (not including the - time or stream number and the ip itself is ignored by the receiver) - * - 8 - - nonce - - uint64_t - - Random nonce used to detect connections to self. - * - 1+ - - user_agent - - var_str - - :doc:`useragent` (0x00 if string is 0 bytes long). Sending nodes must not - include a user_agent longer than 5000 bytes. - * - 1+ - - stream_numbers - - var_int_list - - The stream numbers that the emitting node is interested in. Sending nodes - must not include more than 160000 stream numbers. - -A "verack" packet shall be sent if the version packet was accepted. Once you -have sent and received a verack messages with the remote node, send an addr -message advertising up to 1000 peers of which you are aware, and one or more -inv messages advertising all of the valid objects of which you are aware. - -.. list-table:: The following services are currently assigned - :header-rows: 1 - :widths: auto - - * - Value - - Name - - Description - * - 1 - - NODE_NETWORK - - This is a normal network node. - * - 2 - - NODE_SSL - - This node supports SSL/TLS in the current connect (python < 2.7.9 only - supports a SSL client, so in that case it would only have this on when - the connection is a client). - * - 3 - - NODE_POW - - (**Proposal**) This node may do PoW on behalf of some its peers (PoW - offloading/delegating), but it doesn't have to. Clients may have to meet - additional requirements (e.g. TLS authentication) - * - 4 - - NODE_DANDELION - - Node supports `dandelion `_ - -verack -^^^^^^ - -The *verack* message is sent in reply to *version*. This message consists of -only a :ref:`message header ` with the command string -"verack". The TCP timeout starts out at 20 seconds; after verack messages are -exchanged, the timeout is raised to 10 minutes. - -If both sides announce that they support SSL, they **must** perform an SSL -handshake immediately after they both send and receive verack. During this SSL -handshake, the TCP client acts as an SSL client, and the TCP server acts as an -SSL server. The current implementation (v0.5.4 or later) requires the -AECDH-AES256-SHA cipher over TLSv1 protocol, and prefers the secp256k1 curve -(but other curves may be accepted, depending on the version of python and -OpenSSL used). - -addr -^^^^ - -Provide information on known nodes of the network. Non-advertised nodes should -be forgotten after typically 3 hours - -Payload: - -+------------+-------------+-----------+---------------------------------------+ -| Field Size | Description | Data type | Comments | -+============+=============+===========+=======================================+ -| 1+ | count | |var_int| | Number of address entries (max: 1000) | -+------------+-------------+-----------+---------------------------------------+ -| 38 | addr_list | net_addr | Address of other nodes on the network.| -+------------+-------------+-----------+---------------------------------------+ - -inv -^^^ - -Allows a node to advertise its knowledge of one or more objects. Payload -(maximum payload length: 50000 items): - -+------------+-------------+------------+-----------------------------+ -| Field Size | Description | Data type | Comments | -+============+=============+============+=============================+ -| ? | count | |var_int| | Number of inventory entries | -+------------+-------------+------------+-----------------------------+ -| 32x? | inventory | inv_vect[] | Inventory vectors | -+------------+-------------+------------+-----------------------------+ - -getdata -^^^^^^^ - -getdata is used in response to an inv message to retrieve the content of a -specific object after filtering known elements. - -Payload (maximum payload length: 50000 entries): - -+------------+-------------+------------+-----------------------------+ -| Field Size | Description | Data type | Comments | -+============+=============+============+=============================+ -| ? | count | |var_int| | Number of inventory entries | -+------------+-------------+------------+-----------------------------+ -| 32x? | inventory | inv_vect[] | Inventory vectors | -+------------+-------------+------------+-----------------------------+ - -error -^^^^^ -.. note:: New in version 3 - -This message may be silently ignored (and therefor handled like any other -"unknown" message). - -The message is intended to inform the other node about protocol errors and -can be used for debugging and improving code. - -.. list-table:: - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 1+ - - fatal - - |var_int| - - This qualifies the error. If set to 0, than its just a "warning". - You can expect, everything still worked fine. If set to 1, than - it's an error, so you may expect, something was going wrong - (e.g. an object got lost). If set to 2, it's a fatal error. The node - will drop the line for that error and maybe ban you for some time. - * - 1+ - - ban time - - var_int - - If the error is fatal, you can specify the ban time in seconds, here. - You inform the other node, that you will not accept further connections - for this number of seconds. For non fatal errors this field has - no meaning and should be zero. - * - 1+ - - inventory vector - - var_str - - If the error is related to an object, this Variable length string - contains the inventory vector of that object. If the error is not - related to an object, this string is empty. - * - 1+ - - error text - - var_str - - A human readable string in English, which describes the error. - - -object -^^^^^^ - -An object is a message which is shared throughout a stream. It is the only -message which propagates; all others are only between two nodes. Objects have a -type, like 'msg', or 'broadcast'. To be a valid object, the -:doc:`pow` must be done. The maximum allowable length of an object -(not to be confused with the ``objectPayload``) is |2^18| bytes. - -.. |2^18| replace:: 2\ :sup:`18`\ - -.. list-table:: Message structure - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 8 - - nonce - - uint64_t - - Random nonce used for the :doc:`pow` - * - 8 - - expiresTime - - uint64_t - - The "end of life" time of this object (be aware, in version 2 of the - protocol this was the generation time). Objects shall be shared with - peers until its end-of-life time has been reached. The node should store - the inventory vector of that object for some extra period of time to - avoid reloading it from another node with a small time delay. The time - may be no further than 28 days + 3 hours in the future. - * - 4 - - objectType - - uint32_t - - Four values are currently defined: 0-"getpubkey", 1-"pubkey", 2-"msg", - 3-"broadcast". All other values are reserved. Nodes should relay objects - even if they use an undefined object type. - * - 1+ - - version - - var_int - - The object's version. Note that msg objects won't contain a version - until Sun, 16 Nov 2014 22:00:00 GMT. - * - 1+ - - stream number - - var_int - - The stream number in which this object may propagate - * - ? - - objectPayload - - uchar[] - - This field varies depending on the object type; see below. - - -Unsupported messages -^^^^^^^^^^^^^^^^^^^^ - -If a node receives an unknown message it **must** silently ignore it. This is -for further extensions of the protocol with other messages. Nodes that don't -understand such a new message type shall be able to work correct with the -message types they understand. - -Maybe some version 2 nodes did already implement it that way, but in version 3 -it is **part of the protocol specification**, that a node **must** -silently ignore unsupported messages. - - -Object types ------------- - -Here are the payloads for various object types. - -getpubkey -^^^^^^^^^ - -When a node has the hash of a public key (from an address) but not the public -key itself, it must send out a request for the public key. - -.. list-table:: - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 20 - - ripe - - uchar[] - - The ripemd hash of the public key. This field is only included when the - address version is <= 3. - * - 32 - - tag - - uchar[] - - The tag derived from the address version, stream number, and ripe. This - field is only included when the address version is >= 4. - -pubkey -^^^^^^ - -A version 2 pubkey. This is still in use and supported by current clients but -*new* v2 addresses are not generated by clients. - -.. list-table:: - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 4 - - |behavior_bitfield| - - uint32_t - - A bitfield of optional behaviors and features that can be expected from - the node receiving the message. - * - 64 - - public signing key - - uchar[] - - The ECC public key used for signing (uncompressed format; - normally prepended with \x04 ) - * - 64 - - public encryption key - - uchar[] - - The ECC public key used for encryption (uncompressed format; - normally prepended with \x04 ) - -.. list-table:: A version 3 pubkey - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 4 - - |behavior_bitfield| - - uint32_t - - A bitfield of optional behaviors and features that can be expected from - the node receiving the message. - * - 64 - - public signing key - - uchar[] - - The ECC public key used for signing (uncompressed format; - normally prepended with \x04 ) - * - 64 - - public encryption key - - uchar[] - - The ECC public key used for encryption (uncompressed format; - normally prepended with \x04 ) - * - 1+ - - nonce_trials_per_byte - - var_int - - Used to calculate the difficulty target of messages accepted by this - node. The higher this value, the more difficult the Proof of Work must - be before this individual will accept the message. This number is the - average number of nonce trials a node will have to perform to meet the - Proof of Work requirement. 1000 is the network minimum so any lower - values will be automatically raised to 1000. - * - 1+ - - extra_bytes - - var_int - - Used to calculate the difficulty target of messages accepted by this - node. The higher this value, the more difficult the Proof of Work must - be before this individual will accept the message. This number is added - to the data length to make sending small messages more difficult. - 1000 is the network minimum so any lower values will be automatically - raised to 1000. - * - 1+ - - sig_length - - var_int - - Length of the signature - * - sig_length - - signature - - uchar[] - - The ECDSA signature which, as of protocol v3, covers the object - header starting with the time, appended with the data described in this - table down to the extra_bytes. - -.. list-table:: A version 4 pubkey - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 32 - - tag - - uchar[] - - The tag, made up of bytes 32-64 of the double hash of the address data - (see example python code below) - * - ? - - encrypted - - uchar[] - - Encrypted pubkey data. - -When version 4 pubkeys are created, most of the data in the pubkey is encrypted. -This is done in such a way that only someone who has the Bitmessage address -which corresponds to a pubkey can decrypt and use that pubkey. This prevents -people from gathering pubkeys sent around the network and using the data from -them to create messages to be used in spam or in flooding attacks. - -In order to encrypt the pubkey data, a double SHA-512 hash is calculated from -the address version number, stream number, and ripe hash of the Bitmessage -address that the pubkey corresponds to. The first 32 bytes of this hash are used -to create a public and private key pair with which to encrypt and decrypt the -pubkey data, using the same algorithm as message encryption -(see :doc:`encryption`). The remaining 32 bytes of this hash are added to the -unencrypted part of the pubkey and used as a tag, as above. This allows nodes to -determine which pubkey to decrypt when they wish to send a message. - -In PyBitmessage, the double hash of the address data is calculated using the -python code below: - -.. code-block:: python - - doubleHashOfAddressData = hashlib.sha512(hashlib.sha512( - encodeVarint(addressVersionNumber) + encodeVarint(streamNumber) + hash - ).digest()).digest() - - -.. list-table:: Encrypted data in version 4 pubkeys: - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 4 - - |behavior_bitfield| - - uint32_t - - A bitfield of optional behaviors and features that can be expected from - the node receiving the message. - * - 64 - - public signing key - - uchar[] - - The ECC public key used for signing (uncompressed format; - normally prepended with \x04 ) - * - 64 - - public encryption key - - uchar[] - - The ECC public key used for encryption (uncompressed format; - normally prepended with \x04 ) - * - 1+ - - nonce_trials_per_byte - - var_int - - Used to calculate the difficulty target of messages accepted by this - node. The higher this value, the more difficult the Proof of Work must - be before this individual will accept the message. This number is the - average number of nonce trials a node will have to perform to meet the - Proof of Work requirement. 1000 is the network minimum so any lower - values will be automatically raised to 1000. - * - 1+ - - extra_bytes - - var_int - - Used to calculate the difficulty target of messages accepted by this - node. The higher this value, the more difficult the Proof of Work must - be before this individual will accept the message. This number is added - to the data length to make sending small messages more difficult. - 1000 is the network minimum so any lower values will be automatically - raised to 1000. - * - 1+ - - sig_length - - var_int - - Length of the signature - * - sig_length - - signature - - uchar[] - - The ECDSA signature which covers everything from the object header - starting with the time, then appended with the decrypted data down to - the extra_bytes. This was changed in protocol v3. - -msg -^^^ - -Used for person-to-person messages. Note that msg objects won't contain a -version in the object header until Sun, 16 Nov 2014 22:00:00 GMT. - -.. list-table:: - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - ? - - encrypted - - uchar[] - - Encrypted data. See `Encrypted payload`_. - See also `Unencrypted Message Data`_ - -broadcast -^^^^^^^^^ - -Users who are subscribed to the sending address will see the message appear in -their inbox. Broadcasts are version 4 or 5. - -Pubkey objects and v5 broadcast objects are encrypted the same way: The data -encoded in the sender's Bitmessage address is hashed twice. The first 32 bytes -of the resulting hash constitutes the "private" encryption key and the last -32 bytes constitute a **tag** so that anyone listening can easily decide if -this particular message is interesting. The sender calculates the public key -from the private key and then encrypts the object with this public key. Thus -anyone who knows the Bitmessage address of the sender of a broadcast or pubkey -object can decrypt it. - -The version of broadcast objects was previously 2 or 3 but was changed to 4 or -5 for protocol v3. Having a broadcast version of 5 indicates that a tag is used -which, in turn, is used when the sender's address version is >=4. - -.. list-table:: - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 32 - - tag - - uchar[] - - The tag. This field is new and only included when the broadcast version - is >= 5. Changed in protocol v3 - * - ? - - encrypted - - uchar[] - - Encrypted broadcast data. The keys are derived as described in the - paragraph above. See Encrypted payload for details about the encryption - algorithm itself. - -Unencrypted data format: - -.. list-table:: - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 1+ - - broadcast version - - var_int - - The version number of this broadcast protocol message which is equal - to 2 or 3. This is included here so that it can be signed. This is - no longer included in protocol v3 - * - 1+ - - address version - - var_int - - The sender's address version - * - 1+ - - stream number - - var_int - - The sender's stream number - * - 4 - - |behavior_bitfield| - - uint32_t - - A bitfield of optional behaviors and features that can be expected from - the owner of this pubkey. - * - 64 - - public signing key - - uchar[] - - The ECC public key used for signing (uncompressed format; - normally prepended with \x04) - * - 64 - - public encryption key - - uchar[] - - The ECC public key used for encryption (uncompressed format; - normally prepended with \x04) - * - 1+ - - nonce_trials_per_byte - - var_int - - Used to calculate the difficulty target of messages accepted by this - node. The higher this value, the more difficult the Proof of Work must - be before this individual will accept the message. This number is the - average number of nonce trials a node will have to perform to meet the - Proof of Work requirement. 1000 is the network minimum so any lower - values will be automatically raised to 1000. This field is new and is - only included when the address_version >= 3. - * - 1+ - - extra_bytes - - var_int - - Used to calculate the difficulty target of messages accepted by this - node. The higher this value, the more difficult the Proof of Work must - be before this individual will accept the message. This number is added - to the data length to make sending small messages more difficult. - 1000 is the network minimum so any lower values will be automatically - raised to 1000. This field is new and is only included when the - address_version >= 3. - * - 1+ - - encoding - - var_int - - The encoding type of the message - * - 1+ - - messageLength - - var_int - - The message length in bytes - * - messageLength - - message - - uchar[] - - The message - * - 1+ - - sig_length - - var_int - - Length of the signature - * - sig_length - - signature - - uchar[] - - The signature which did cover the unencrypted data from the broadcast - version down through the message. In protocol v3, it covers the - unencrypted object header starting with the time, all appended with - the decrypted data. - -.. |behavior_bitfield| replace:: :ref:`behavior bitfield ` diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index f8b4b17c..00000000 --- a/docs/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -mistune<=0.8.4 -m2r<=0.2.1 -sphinx_rtd_theme -sphinxcontrib-apidoc -docutils<=0.17.1 diff --git a/docs/useragent.rst b/docs/useragent.rst deleted file mode 100644 index 3523a274..00000000 --- a/docs/useragent.rst +++ /dev/null @@ -1,53 +0,0 @@ -User Agent -========== - -Bitmessage user agents are a modified browser user agent with more structure -to aid parsers and provide some coherence. The user agent strings are arranged -in a stack with the most underlying software listed first. - -Basic format:: - - /Name:Version/Name:Version/.../ - -Example:: - - /PyBitmessage:0.2.2/Corporate Mail System:0.8/ - /Surdo:5.64/surdo-qt:0.4/ - -The version numbers are not defined to any strict format, although this guide -recommends: - - * Version numbers in the form of Major.Minor.Revision (2.6.41) - * Repository builds using a date in the format of YYYYMMDD (20110128) - -For git repository builds, implementations are free to use the git commitish. -However the issue lies in that it is not immediately obvious without the -repository which version preceeds another. For this reason, we lightly -recommend dates in the format specified above, although this is by no means -a requirement. - -Optional ``-r1``, ``-r2``, ... can be appended to user agent version numbers. -This is another light recommendation, but not a requirement. Implementations -are free to specify version numbers in whatever format needed insofar as it -does not include ``(``, ``)``, ``:`` or ``/`` to interfere with the user agent -syntax. - -An optional comments field after the version number is also allowed. Comments -should be delimited by parenthesis ``(...)``. The contents of comments is -entirely implementation defined although this document recommends the use of -semi-colons ``;`` as a delimiter between pieces of information. - -Example:: - - /cBitmessage:0.2(iPad; U; CPU OS 3_2_1)/AndroidBuild:0.8/ - -Reserved symbols are therefore: ``/ : ( )`` - -They should not be misused beyond what is specified in this section. - -``/`` - separates the code-stack -``:`` - specifies the implementation version of the particular stack -``( and )`` - delimits a comment which optionally separates data using ``;`` diff --git a/kivy-requirements.txt b/kivy-requirements.txt deleted file mode 100644 index 185a3ae7..00000000 --- a/kivy-requirements.txt +++ /dev/null @@ -1,11 +0,0 @@ -kivy-garden.qrcode -kivymd==1.0.2 -kivy==2.1.0 -opencv-python -pyzbar -git+https://github.com/tito/telenium@9b54ff1#egg=telenium -Pillow==9.4.0 -jaraco.collections==3.8.0 -jaraco.classes==3.2.3 -pytz==2022.7.1 -pydantic==1.10.6 diff --git a/man/pybitmessage.1.gz b/man/pybitmessage.1.gz deleted file mode 100644 index efa686a8..00000000 Binary files a/man/pybitmessage.1.gz and /dev/null differ 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 c98717b4..00000000 --- a/packages/android/buildozer.spec +++ /dev/null @@ -1,357 +0,0 @@ -[app] - -# (str) Title of your application -title = mockone - -# (str) Package name -package.name = mock - -# (str) Package domain (needed for android/ios packaging) -package.domain = org.mock - -# (str) Source code where the main.py live -source.dir = ../../src - -# (list) Source files to include (let empty to include all the files) -source.include_exts = py,png,jpg,kv,atlas,tflite,sql - -# (list) List of inclusions using pattern matching -#source.include_patterns = assets/*,images/*.png - -# (list) Source files to exclude (let empty to not exclude anything) -#source.exclude_exts = spec - -# (list) List of directory to exclude (let empty to not exclude anything) -#source.exclude_dirs = tests, bin, venv - -# (list) List of exclusions using pattern matching -#source.exclude_patterns = license,images/*/*.jpg - -# (str) Application versioning (method 1) -version = 0.1 - -# (str) Application versioning (method 2) -# version.regex = __version__ = ['"](.*)['"] -# version.filename = %(source.dir)s/main.py - -# (list) Application requirements -# comma separated e.g. requirements = sqlite3,kivy -requirements = python3,kivy - -# (str) Custom source folders for requirements -# Sets custom source for any requirements with recipes -# requirements.source.kivy = ../../kivy - -# (str) Presplash of the application -#presplash.filename = %(source.dir)s/data/presplash.png - -# (str) Icon of the application -#icon.filename = %(source.dir)s/data/icon.png - -# (str) Supported orientation (one of landscape, sensorLandscape, portrait or all) -orientation = portrait - -# (list) List of service to declare -#services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY - -# -# OSX Specific -# - -# -# author = © Copyright Info - -# change the major version of python used by the app -osx.python_version = 3 - -# Kivy version to use -osx.kivy_version = 1.9.1 - -# -# Android specific -# - -# (bool) Indicate if the application should be fullscreen or not -fullscreen = 0 - -# (string) Presplash background color (for android toolchain) -# Supported formats are: #RRGGBB #AARRGGBB or one of the following names: -# red, blue, green, black, white, gray, cyan, magenta, yellow, lightgray, -# darkgray, grey, lightgrey, darkgrey, aqua, fuchsia, lime, maroon, navy, -# olive, purple, silver, teal. -#android.presplash_color = #FFFFFF - -# (string) Presplash animation using Lottie format. -# see https://lottiefiles.com/ for examples and https://airbnb.design/lottie/ -# for general documentation. -# Lottie files can be created using various tools, like Adobe After Effect or Synfig. -#android.presplash_lottie = "path/to/lottie/file.json" - -# (list) Permissions -#android.permissions = INTERNET - -# (int) Android API to use (targetSdkVersion AND compileSdkVersion) -# note: when changing, Dockerfile also needs to be changed to install corresponding build tools -android.api = 28 - -# (int) Minimum API required. You will need to set the android.ndk_api to be as low as this value. -android.minapi = 21 - -# (str) Android NDK version to use -android.ndk = 25b - -# (int) Android NDK API to use (optional). This is the minimum API your app will support. -android.ndk_api = 21 - -# (bool) Use --private data storage (True) or --dir public storage (False) -android.private_storage = True - -# (str) Android NDK directory (if empty, it will be automatically downloaded.) -android.ndk_path = /opt/android/android-ndk - -# (str) Android SDK directory (if empty, it will be automatically downloaded.) -android.sdk_path = /opt/android/android-sdk - -# (str) ANT directory (if empty, it will be automatically downloaded.) -android.ant_path = /opt/android/apache-ant - -# (bool) If True, then skip trying to update the Android sdk -# This can be useful to avoid excess Internet downloads or save time -# when an update is due and you just want to test/build your package -# android.skip_update = False - -# (bool) If True, then automatically accept SDK license -# agreements. This is intended for automation only. If set to False, -# the default, you will be shown the license when first running -# buildozer. -# android.accept_sdk_license = False - -# (str) Android entry point, default is ok for Kivy-based app -#android.entrypoint = org.renpy.android.PythonActivity - -# (str) Android app theme, default is ok for Kivy-based app -# android.apptheme = "@android:style/Theme.NoTitleBar" - -# (list) Pattern to whitelist for the whole project -#android.whitelist = - -# (str) Path to a custom whitelist file -#android.whitelist_src = - -# (str) Path to a custom blacklist file -#android.blacklist_src = - -# (list) List of Java .jar files to add to the libs so that pyjnius can access -# their classes. Don't add jars that you do not need, since extra jars can slow -# down the build process. Allows.build wildcards matching, for example: -# OUYA-ODK/libs/*.jar -#android.add_jars = foo.jar,bar.jar,path/to/more/*.jar - -# (list) List of Java files to add to the android project (can be java or a -# directory containing the files) -#android.add_src = - -# (list) Android AAR archives to add -#android.add_aars = - -# (list) Gradle dependencies to add -#android.gradle_dependencies = - -# (list) add java compile options -# this can for example be necessary when importing certain java libraries using the 'android.gradle_dependencies' option -# see https://developer.android.com/studio/write/java8-support for further information -#android.add_compile_options = "sourceCompatibility = 1.8", "targetCompatibility = 1.8" - -# (list) Gradle repositories to add {can be necessary for some android.gradle_dependencies} -# please enclose in double quotes -# e.g. android.gradle_repositories = "maven { url 'https://kotlin.bintray.com/ktor' }" -#android.add_gradle_repositories = -#android.gradle_dependencies = "org.tensorflow:tensorflow-lite:+","org.tensorflow:tensorflow-lite-support:0.0.0-nightly" - -# (list) packaging options to add -# see https://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.PackagingOptions.html -# can be necessary to solve conflicts in gradle_dependencies -# please enclose in double quotes -# e.g. android.add_packaging_options = "exclude 'META-INF/common.kotlin_module'", "exclude 'META-INF/*.kotlin_module'" -#android.add_packaging_options = - -# (list) Java classes to add as activities to the manifest. -#android.add_activities = com.example.ExampleActivity - -# (str) OUYA Console category. Should be one of GAME or APP -# If you leave this blank, OUYA support will not be enabled -#android.ouya.category = GAME - -# (str) Filename of OUYA Console icon. It must be a 732x412 png image. -#android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png - -# (str) XML file to include as an intent filters in tag -#android.manifest.intent_filters = - -# (str) launchMode to set for the main activity -#android.manifest.launch_mode = standard - -# (list) Android additional libraries to copy into libs/armeabi -#android.add_libs_armeabi = libs/android/*.so -#android.add_libs_armeabi_v7a = libs/android-v7/*.so -#android.add_libs_arm64_v8a = libs/android-v8/*.so -#android.add_libs_x86 = libs/android-x86/*.so -#android.add_libs_mips = libs/android-mips/*.so - -# (bool) Indicate whether the screen should stay on -# Don't forget to add the WAKE_LOCK permission if you set this to True -#android.wakelock = False - -# (list) Android application meta-data to set (key=value format) -#android.meta_data = - -# (list) Android library project to add (will be added in the -# project.properties automatically.) -#android.library_references = - -# (list) Android shared libraries which will be added to AndroidManifest.xml using tag -#android.uses_library = - -# (str) Android logcat filters to use -#android.logcat_filters = *:S python:D - -# (bool) Android logcat only display log for activity's pid -#android.logcat_pid_only = False - -# (str) Android additional adb arguments -#android.adb_args = -H host.docker.internal - -# (bool) Copy library instead of making a libpymodules.so -#android.copy_libs = 1 - -# (str) The Android arch to build for, choices: armeabi-v7a, arm64-v8a, x86, x86_64 -android.archs = armeabi-v7a, arm64-v8a, x86_64 - -# (int) overrides automatic versionCode computation (used in build.gradle) -# this is not the same as app version and should only be edited if you know what you're doing -# android.numeric_version = 1 - -# (bool) enables Android auto backup feature (Android API >=23) -android.allow_backup = True - -# (str) XML file for custom backup rules (see official auto backup documentation) -# android.backup_rules = - -# (str) If you need to insert variables into your AndroidManifest.xml file, -# you can do so with the manifestPlaceholders property. -# This property takes a map of key-value pairs. (via a string) -# Usage example : android.manifest_placeholders = [myCustomUrl:\"org.kivy.customurl\"] -# android.manifest_placeholders = [:] - -# -# Python for android (p4a) specific -# - -# (str) python-for-android fork to use, defaults to upstream (kivy) -#p4a.fork = kivy - -# (str) python-for-android branch to use, defaults to master -#p4a.branch = master - -# (str) python-for-android git clone directory (if empty, it will be automatically cloned from github) -#p4a.source_dir = - -# (str) The directory in which python-for-android should look for your own build recipes (if any) -#p4a.local_recipes = - -# (str) Filename to the hook for p4a -#p4a.hook = - -# (str) Bootstrap to use for android builds -# p4a.bootstrap = sdl2 - -# (int) port number to specify an explicit --port= p4a argument (eg for bootstrap flask) -#p4a.port = - -# Control passing the --use-setup-py vs --ignore-setup-py to p4a -# "in the future" --use-setup-py is going to be the default behaviour in p4a, right now it is not -# Setting this to false will pass --ignore-setup-py, true will pass --use-setup-py -# NOTE: this is general setuptools integration, having pyproject.toml is enough, no need to generate -# setup.py if you're using Poetry, but you need to add "toml" to source.include_exts. -#p4a.setup_py = false - - -# -# iOS specific -# - -# (str) Path to a custom kivy-ios folder -#ios.kivy_ios_dir = ../kivy-ios -# Alternately, specify the URL and branch of a git checkout: -ios.kivy_ios_url = https://github.com/kivy/kivy-ios -ios.kivy_ios_branch = master - -# Another platform dependency: ios-deploy -# Uncomment to use a custom checkout -#ios.ios_deploy_dir = ../ios_deploy -# Or specify URL and branch -ios.ios_deploy_url = https://github.com/phonegap/ios-deploy -ios.ios_deploy_branch = 1.10.0 - -# (bool) Whether or not to sign the code -ios.codesign.allowed = false - -# (str) Name of the certificate to use for signing the debug version -# Get a list of available identities: buildozer ios list_identities -#ios.codesign.debug = "iPhone Developer: ()" - -# (str) Name of the certificate to use for signing the release version -#ios.codesign.release = %(ios.codesign.debug)s - - -[buildozer] - -# (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) -log_level = 2 - -# (int) Display warning if buildozer is run as root (0 = False, 1 = True) -warn_on_root = 1 - -# (str) Path to build artifact storage, absolute or relative to spec file -# build_dir = ./.buildozer - -# (str) Path to build output (i.e. .apk, .ipa) storage -# bin_dir = ./bin - -# ----------------------------------------------------------------------------- -# List as sections -# -# You can define all the "list" as [section:key]. -# Each line will be considered as a option to the list. -# Let's take [app] / source.exclude_patterns. -# Instead of doing: -# -#[app] -#source.exclude_patterns = license,data/audio/*.wav,data/images/original/* -# -# This can be translated into: -# -#[app:source.exclude_patterns] -#license -#data/audio/*.wav -#data/images/original/* -# - - -# ----------------------------------------------------------------------------- -# Profiles -# -# You can extend section / key with a profile -# For example, you want to deploy a demo version of your application without -# HD content. You could first change the title to add "(demo)" in the name -# and extend the excluded directories to remove the HD content. -# -#[app@demo] -#title = My Application (demo) -# -#[app:source.exclude_patterns@demo] -#images/hd/* -# -# Then, invoke the command line with the "demo" profile: -# -#buildozer --profile demo android debug diff --git a/packages/apparmor/pybitmessage b/packages/apparmor/pybitmessage deleted file mode 100644 index 3ec3d237..00000000 --- a/packages/apparmor/pybitmessage +++ /dev/null @@ -1,19 +0,0 @@ -# Last Modified: Wed Apr 29 21:04:08 2020 -#include - -/usr/bin/pybitmessage { - #include - #include - #include - #include - #include - - owner /home/*/.ICEauthority r, - owner /home/*/.Xauthority r, - owner /home/*/.config/PyBitmessage/ rw, - owner /home/*/.config/PyBitmessage/* rwk, - owner /home/*/.config/Trolltech.conf rwk, - owner /home/*/.config/Trolltech.conf.* rw, - owner /proc/*/mounts r, - -} diff --git a/packages/collectd/pybitmessagestatus.py b/packages/collectd/pybitmessagestatus.py 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 cc3e54fc..00000000 --- a/packages/snap/snapcraft.yaml +++ /dev/null @@ -1,76 +0,0 @@ -name: pybitmessage -base: core18 -grade: devel -confinement: strict -summary: Reference client for Bitmessage, a P2P communications protocol -description: | - Bitmessage is a P2P communication protocol used to send encrypted messages to - another person or to many subscribers. It is decentralized and trustless, - meaning that you need-not inherently trust any entities like root certificate - authorities. It uses strong authentication, which means that the sender of a - message cannot be spoofed. BM aims to hide metadata from passive - eavesdroppers like those ongoing warrantless wiretapping programs. Hence - the sender and receiver of Bitmessages stay anonymous. -adopt-info: pybitmessage - -apps: - pybitmessage: - command: desktop-launch pybitmessage - plugs: [desktop, home, network-bind, unity7] - desktop: share/applications/pybitmessage.desktop - passthrough: - autostart: pybitmessage.desktop - -parts: - pybitmessage: - # https://wiki.ubuntu.com/snapcraft/parts - after: [qt4conf, desktop-qt4, indicator-qt4, tor] - source: https://github.com/Bitmessage/PyBitmessage.git - override-pull: | - snapcraftctl pull - snapcraftctl set-version $(git describe --tags --abbrev=0 | tr -d v) - plugin: python - python-version: python2 - build-packages: - - libssl-dev - - python-all-dev - python-packages: - - jsonrpclib - - qrcode - - pyxdg - - stem - stage-packages: - - python-qt4 - - python-sip - # parse-info: [setup.py] - tor: - source: https://dist.torproject.org/tor-0.4.6.9.tar.gz - source-checksum: sha256/c7e93380988ce20b82aa19c06cdb2f10302b72cfebec7c15b5b96bcfc94ca9a9 - source-type: tar - plugin: autotools - build-packages: - - libssl-dev - - zlib1g-dev - after: [libevent] - libevent: - source: https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gz - source-checksum: sha256/92e6de1be9ec176428fd2367677e61ceffc2ee1cb119035037a27d346b0403bb - source-type: tar - plugin: autotools - cleanup: - after: [pybitmessage] - plugin: nil - override-prime: | - set -eux - sed -ie \ - 's|.*Icon=.*|Icon=${SNAP}/share/icons/hicolor/scalable/apps/pybitmessage.svg|g' \ - $SNAPCRAFT_PRIME/share/applications/pybitmessage.desktop - rm -rf $SNAPCRAFT_PRIME/lib/python2.7/site-packages/pip - for DIR in doc man icons themes fonts mime; do - rm -rf $SNAPCRAFT_PRIME/usr/share/$DIR/* - done - LIBS="libQtDeclarative libQtDesigner libQtHelp libQtScript libQtSql \ - libQtXmlPatterns libdb-5 libicu libgdk libgio libglib libcairo" - for LIBGLOB in $LIBS; do - rm $SNAPCRAFT_PRIME/usr/lib/$SNAPCRAFT_ARCH_TRIPLET/${LIBGLOB}* - done diff --git a/packages/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/unmaintained/arch.sh b/packages/unmaintained/arch.sh deleted file mode 100755 index 5f7982bb..00000000 --- a/packages/unmaintained/arch.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/bash - -APP=pybitmessage -PREV_VERSION=0.4.4 -VERSION=0.6.0 -RELEASE=1 -ARCH_TYPE=any -CURRDIR=`pwd` -SOURCE=archpackage/${APP}-${VERSION}.tar.gz - -# Update version numbers automatically - so you don't have to -sed -i 's/VERSION='${PREV_VERSION}'/VERSION='${VERSION}'/g' Makefile debian.sh rpm.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 - - -# Create the source code -make clean -rm -f archpackage/*.gz - -# having the root directory called name-version seems essential -mv ../${APP} ../${APP}-${VERSION} -tar -cvzf ${SOURCE} ../${APP}-${VERSION} --exclude-vcs - -# rename the root directory without the version number -mv ../${APP}-${VERSION} ../${APP} - -# calculate the MD5 checksum -CHECKSM=$(md5sum ${SOURCE}) -sed -i "s/md5sums[^)]*)/md5sums=(${CHECKSM%% *})/g" archpackage/PKGBUILD - -cd archpackage - -# Create the package -tar -c -f ${APP}-${VERSION}.pkg.tar . -sync -xz ${APP}-${VERSION}.pkg.tar -sync - -# Move back to the original directory -cd ${CURRDIR} - diff --git a/packages/unmaintained/archpackage/PKGBUILD b/packages/unmaintained/archpackage/PKGBUILD deleted file mode 100644 index 9fd8eadd..00000000 --- a/packages/unmaintained/archpackage/PKGBUILD +++ /dev/null @@ -1,31 +0,0 @@ -# Maintainer: Bob Mottram (4096 bits) -pkgname=pybitmessage -pkgver=0.6.0 -pkgrel=1 -pkgdesc="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." -arch=('any') -url="https://github.com/Bitmessage/PyBitmessage" -license=('MIT') -groups=() -depends=('python2' 'qt4' 'python2-pyqt4' 'sqlite' 'openssl' 'mpg123') -makedepends=() -optdepends=('python2-gevent: Python network library that uses greenlet and libevent for easy and scalable concurrency') -provides=() -conflicts=() -replaces=() -backup=() -options=() -install= -changelog= -source=($pkgname-$pkgver.tar.gz) -noextract=() -md5sums=() -build() { - cd "$srcdir/$pkgname-$pkgver" - ./configure --prefix=/usr - make -} -package() { - cd "$srcdir/$pkgname-$pkgver" - make DESTDIR="$pkgdir/" PREFIX="/usr" install -} diff --git a/packages/unmaintained/ebuild.sh b/packages/unmaintained/ebuild.sh deleted file mode 100755 index f26fe58e..00000000 --- a/packages/unmaintained/ebuild.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash - -APP=pybitmessage -PREV_VERSION=0.4.4 -VERSION=0.6.0 -RELEASE=1 -SOURCEDIR=. -ARCH_TYPE=$(uname -m) -CURRDIR=$(pwd) -SOURCE=~/ebuild/${APP}-${VERSION}.tar.gz - - -# Update version numbers automatically - so you don't have to -sed -i 's/VERSION='${PREV_VERSION}'/VERSION='${VERSION}'/g' Makefile debian.sh rpm.sh arch.sh puppy.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 - -# create the source code in the SOURCES directory -make clean -mkdir -p ~/ebuild -rm -f ${SOURCE} -mv ../${APP} ../${APP}-${VERSION} -tar -cvzf ${SOURCE} ../${APP}-${VERSION} --exclude-vcs - -# rename the root directory without the version number -mv ../${APP}-${VERSION} ../${APP} diff --git a/packages/unmaintained/generate.sh b/packages/unmaintained/generate.sh deleted file mode 100755 index c8f0e393..00000000 --- a/packages/unmaintained/generate.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash - -# Generates packaging - -rm -f Makefile rpmpackage/*.spec - -packagemonkey -n "PyBitmessage" --version "0.4.4" --dir "." -l "mit" \ - -e "Bob Mottram (4096 bits) " \ - --brief "Send encrypted messages" \ - --desc "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." \ - --homepage "https://github.com/Bitmessage/PyBitmessage" --section "mail" \ - --categories "Office/Email" \ - --dependsdeb "python (>= 2.7), openssl, python-qt4, libqt4-dev (>= 4.8.0), python-qt4-dev, sqlite3, libsqlite3-dev, gst123" \ - --dependsrpm "python, PyQt4, openssl-compat-bitcoin-libs, gst123" \ - --mainscript "bitmessagemain.py" \ - --librarypath "/opt/openssl-compat-bitcoin/lib/" \ - --suggestsdeb "libmessaging-menu-dev" \ - --dependspuppy "openssl, python-qt4, sqlite3, sqlite3-dev, python-openssl, python-sip, gst123" \ - --dependsarch "python2, qt4, python2-pyqt4, sqlite, openssl, mpg123" \ - --suggestsarch "python2-gevent: Python network library that uses greenlet and libevent for easy and scalable concurrency" --pythonversion 2 \ - --dependsebuild "dev-libs/openssl, dev-python/PyQt4[${PYTHON_USEDEP}]" \ - --buildebuild "\${PYTHON_DEPS}" --pythonreq "sqlite" \ - --repository "https://github.com/Bitmessage/PyBitmessage.git" diff --git a/packages/unmaintained/puppy.sh b/packages/unmaintained/puppy.sh deleted file mode 100755 index 1d3bdd31..00000000 --- a/packages/unmaintained/puppy.sh +++ /dev/null @@ -1,66 +0,0 @@ -#!/bin/bash - -APP=pybitmessage -PREV_VERSION=0.4.4 -VERSION=0.6.0 -RELEASE=1 -BUILDDIR=~/petbuild -CURRDIR=$(pwd) -PROJECTDIR=${BUILDDIR}/${APP}-${VERSION}-${RELEASE} - -# Update version numbers automatically - so you don't have to -sed -i 's/VERSION='${PREV_VERSION}'/VERSION='${VERSION}'/g' Makefile debian.sh rpm.sh arch.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 directories within which the project will be built -mkdir -p ${BUILDDIR} -mkdir -p ${PROJECTDIR} - -# Build the project -make clean -make -make install -B DESTDIR=${PROJECTDIR} PREFIX=/usr - -# Alter the desktop file categories -sed -i "s/Categories=Office;Email;/Categories=Internet;mailnews;/g" ${PROJECTDIR}/usr/share/applications/${APP}.desktop - -# Create directories specific to puppy -mkdir ${PROJECTDIR}/usr -mkdir ${PROJECTDIR}/usr/local -mkdir ${PROJECTDIR}/usr/local/bin - -# Copy anything in /usr/bin into /usr/local/bin -cp ${PROJECTDIR}/usr/bin/* ${PROJECTDIR}/usr/local/bin/ - -# Copy the spec file into the build directory -cp ${CURRDIR}/puppypackage/${APP}-${VERSION}.pet.specs ${PROJECTDIR} - -# Copy the XPM mini icon into the build directory -cp ${CURRDIR}/desktop/icon14.xpm ${PROJECTDIR}/${APP}.xpm - -# Compress the build directory -cd ${BUILDDIR} -tar -c -f ${APP}-${VERSION}-${RELEASE}.tar . -sync -gzip ${APP}-${VERSION}-${RELEASE}.tar -mv ${APP}-${VERSION}-${RELEASE}.tar.gz ${CURRDIR}/puppypackage -cd ${CURRDIR}/puppypackage - -# Create the PET package -MD5SUM="`md5sum ${APP}-${VERSION}-${RELEASE}.tar.gz | cut -f 1 -d ' '`" -echo -n "$MD5SUM" >> ${APP}-${VERSION}-${RELEASE}.tar.gz -sync -mv -f ${APP}-${VERSION}-${RELEASE}.tar.gz ${APP}-${VERSION}-${RELEASE}.pet -sync -cd ${CURRDIR} - -# Remove the temporary build directory -rm -fr ${BUILDDIR} diff --git a/packages/unmaintained/puppypackage/icon14.xpm b/packages/unmaintained/puppypackage/icon14.xpm deleted file mode 100644 index 6d50c8c7..00000000 --- a/packages/unmaintained/puppypackage/icon14.xpm +++ /dev/null @@ -1,111 +0,0 @@ -/* XPM */ -static char * icon14_xpm[] = { -"14 14 94 2", -" c None", -". c #B9BABC", -"+ c #D2D3D4", -"@ c #BEBFC1", -"# c #CBCCCF", -"$ c #E0E3E1", -"% c #F6F8F8", -"& c #F3F3F3", -"* c #B9BABD", -"= c #C8C9CB", -"- c #DADCDB", -"; c #E6E8E7", -"> c #F7F7F7", -", c #FCFCFC", -"' c #F5F5F5", -") c #BCBDBF", -"! c #D3D5D5", -"~ c #E3E5E4", -"{ c #F1F2F2", -"] c #FDFDFD", -"^ c #F8F8F8", -"/ c #CBCCCC", -"( c #B2B3B6", -"_ c #B0B1B3", -": c #D3D4D6", -"< c #DFE0E0", -"[ c #EAEDEB", -"} c #FAF9F9", -"| c #DFE0DF", -"1 c #B9BBBD", -"2 c #C2C3C5", -"3 c #B7B8BC", -"4 c #CDCED0", -"5 c #DCDDDE", -"6 c #E7E9E7", -"7 c #F6F6F6", -"8 c #C0C1C2", -"9 c #DDDFDF", -"0 c #BCBCBF", -"a c #D7D9DA", -"b c #E2E4E3", -"c c #F0F2F1", -"d c #FAFAFA", -"e c #F9F9F9", -"f c #CCCDCD", -"g c #B6B7B9", -"h c #C7C8CA", -"i c #A6A7A9", -"j c #D3D4D5", -"k c #F2F5F3", -"l c #F1F2F1", -"m c #F6F8F7", -"n c #FCFBFC", -"o c #E8EAE9", -"p c #B6B7B8", -"q c #BFC0C2", -"r c #323138", -"s c #1D1D22", -"t c #111117", -"u c #4C4C51", -"v c #ECECED", -"w c #FFFFFF", -"x c #BBBDBD", -"y c #C9CACB", -"z c #333238", -"A c #313036", -"B c #27272C", -"C c #1E1F24", -"D c #16171D", -"E c #919193", -"F c #F2F3F3", -"G c #B4B5B7", -"H c #CDCFCF", -"I c #67666B", -"J c #37363C", -"K c #2C2B31", -"L c #2A292F", -"M c #16171C", -"N c #68696B", -"O c #C7C8C9", -"P c #CBCDCC", -"Q c #49474E", -"R c #39383E", -"S c #36353B", -"T c #333138", -"U c #28272D", -"V c #CED0D0", -"W c #67676C", -"X c #414046", -"Y c #424147", -"Z c #39383F", -"` c #8C8D8F", -" . c #6B6C70", -".. c #75757A", -" . + ", -" @ # $ % & ", -" * = - ; > , ' ", -" ) ! ~ { ] ^ / ( ", -" _ : < [ } , | 1 2 ", -" 3 4 5 6 7 , { 8 . 9 ", -" 2 0 a b c d e f g h ", -" i j k l m n o p q h ", -" r s t u v w ' x g y ", -" z A B C D E F G H ", -" I J A K L M N O P ", -" Q R R S T U V ", -" W X Y X Z ` ", -" ... "}; diff --git a/packages/unmaintained/puppypackage/pybitmessage-0.3.5.pet.specs b/packages/unmaintained/puppypackage/pybitmessage-0.3.5.pet.specs deleted file mode 100644 index 717cd281..00000000 --- a/packages/unmaintained/puppypackage/pybitmessage-0.3.5.pet.specs +++ /dev/null @@ -1 +0,0 @@ -pybitmessage-0.3.5-1|PyBitmessage|0.3.5|1|Internet;mailnews;|3.8M||pybitmessage-0.3.5-1.pet|+openssl,+python-qt4,+sqlite3,+sqlite3-dev,+python-openssl,+python-sip,+gst123|Send encrypted messages|ubuntu|precise|5| diff --git a/packages/unmaintained/rpm.sh b/packages/unmaintained/rpm.sh deleted file mode 100755 index 52fb0465..00000000 --- a/packages/unmaintained/rpm.sh +++ /dev/null @@ -1,53 +0,0 @@ -#!/bin/bash - -APP=pybitmessage -PREV_VERSION=0.4.4 -VERSION=0.4.4 -RELEASE=1 -SOURCEDIR=. -ARCH_TYPE=`uname -m` -CURRDIR=`pwd` -SOURCE=~/rpmbuild/SOURCES/${APP}_${VERSION}.orig.tar.gz - - -# Update version numbers automatically - so you don't have to -sed -i 's/VERSION='${PREV_VERSION}'/VERSION='${VERSION}'/g' Makefile debian.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 - -sudo yum groupinstall "Development Tools" -sudo yum install rpmdevtools - -# setup the rpmbuild directory tree -rpmdev-setuptree - -# create the source code in the SOURCES directory -make clean -mkdir -p ~/rpmbuild/SOURCES -rm -f ${SOURCE} - -# having the root directory called name-version seems essential -mv ../${APP} ../${APP}-${VERSION} -tar -cvzf ${SOURCE} ../${APP}-${VERSION} --exclude-vcs - -# rename the root directory without the version number -mv ../${APP}-${VERSION} ../${APP} - -# copy the spec file into the SPECS directory -cp -f rpmpackage/${APP}.spec ~/rpmbuild/SPECS - -# build -cd ~/rpmbuild/SPECS -rpmbuild -ba ${APP}.spec -cd ${CURRDIR} - -# Copy the results into the rpmpackage directory -mkdir -p rpmpackage/${ARCH_TYPE} -cp -r ~/rpmbuild/RPMS/${ARCH_TYPE}/${APP}* rpmpackage/${ARCH_TYPE} -cp -r ~/rpmbuild/SRPMS/${APP}* rpmpackage diff --git a/packages/unmaintained/rpmpackage/pybitmessage.spec b/packages/unmaintained/rpmpackage/pybitmessage.spec deleted file mode 100644 index 1235251a..00000000 --- a/packages/unmaintained/rpmpackage/pybitmessage.spec +++ /dev/null @@ -1,357 +0,0 @@ -Name: pybitmessage -Version: 0.6.0 -Release: 1%{?dist} -Summary: Send encrypted messages -License: MIT -URL: https://github.com/Bitmessage/PyBitmessage -Packager: Bob Mottram (4096 bits) -Source0: http://yourdomainname.com/src/%{name}_%{version}.orig.tar.gz -BuildArch: noarch -Group: Office/Email - -Requires: python, PyQt4, openssl-compat-bitcoin-libs, gst123 - - -%description -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. - -%prep -%setup -q - -%build -%configure -make %{?_smp_mflags} - -%install -rm -rf %{buildroot} -mkdir -p %{buildroot} -mkdir -p %{buildroot}/etc -mkdir -p %{buildroot}/etc/%{name} -mkdir -p %{buildroot}/usr -mkdir -p %{buildroot}/usr/bin -mkdir -p %{buildroot}/usr/share -mkdir -p %{buildroot}/usr/share/man -mkdir -p %{buildroot}/usr/share/man/man1 -mkdir -p %{buildroot}/usr/share/%{name} -mkdir -p %{buildroot}/usr/share/applications -mkdir -p %{buildroot}/usr/share/icons -mkdir -p %{buildroot}/usr/share/icons/hicolor -mkdir -p %{buildroot}/usr/share/icons/hicolor/24x24 -mkdir -p %{buildroot}/usr/share/icons/hicolor/24x24/apps - -mkdir -p %{buildroot}/usr/share/pixmaps -mkdir -p %{buildroot}/usr/share/icons/hicolor/scalable -mkdir -p %{buildroot}/usr/share/icons/hicolor/scalable/apps -# Make install but to the RPM BUILDROOT directory -make install -B DESTDIR=%{buildroot} PREFIX=/usr - -%files -%doc README.md LICENSE -%defattr(-,root,root,-) -%dir /usr/share/%{name} -%dir /usr/share/applications -%dir /usr/share/icons/hicolor -%dir /usr/share/icons/hicolor/24x24 -%dir /usr/share/icons/hicolor/24x24/apps -%dir /usr/share/pixmaps -%dir /usr/share/icons/hicolor/scalable -%dir /usr/share/icons/hicolor/scalable/apps -/usr/share/%{name}/* -%{_bindir}/* -%{_mandir}/man1/* -%attr(644,root,root) /usr/share/applications/%{name}.desktop -%attr(644,root,root) /usr/share/icons/hicolor/24x24/apps/%{name}.png - -%changelog -* Sun Nov 2 2014 Bob Mottram (4096 bits) - 0.4.4-1 -- 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 - -* Thu Mar 6 2014 Bob Mottram (4096 bits) - 0.4.3-1 -- 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 - -* Thu Mar 6 2014 Bob Mottram (4096 bits) - 0.4.2-1 -- 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 - -* Sun Sep 29 2013 Bob Mottram (4096 bits) - 0.4.1-1 -- 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 - -* Sat Sep 28 2013 Bob Mottram (4096 bits) - 0.4.0-1 -- 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 - -* Mon Jul 29 2013 Bob Mottram (4096 bits) - 0.3.5-1 -- 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 - -* Sun Jun 30 2013 Bob Mottram (4096 bits) - 0.3.4-1 -- 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 - -* Sat Jun 29 2013 Bob Mottram (4096 bits) - 0.3.3-1 -- 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 - -* Fri Jun 28 2013 Bob Mottram (4096 bits) - 0.3.211-1 -- Removed multi-core proof of work - as the multiprocessing module does not work well with - pyinstaller's --onefile option. - -* Mon Jun 03 2013 Bob Mottram (4096 bits) - 0.3.2-1 -- 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 - -* Sat May 25 2013 Jonathan Warren (4096 bits) - 0.3.1-1 -- 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 - -* Mon May 6 2013 Bob Mottram (4096 bits) - 0.3.0-1 -- 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 - -* Tue Apr 9 2013 Bob Mottram (4096 bits) - 0.2.8-1 -- 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. - -* Mon Apr 1 2013 Bob Mottram (4096 bits) - 0.2.7-1 -- 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 diff --git a/packages/unmaintained/slack.sh b/packages/unmaintained/slack.sh deleted file mode 100755 index f7495e46..00000000 --- a/packages/unmaintained/slack.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/bash - -APP=pybitmessage -PREV_VERSION=0.4.4 -VERSION=0.6.0 -RELEASE=1 -ARCH_TYPE=`uname -m` -BUILDDIR=~/slackbuild -CURRDIR=`pwd` -PROJECTDIR=${BUILDDIR}/${APP}-${VERSION}-${RELEASE} - -# Update version numbers automatically - so you don't have to -sed -i 's/VERSION='${PREV_VERSION}'/VERSION='${VERSION}'/g' Makefile debian.sh rpm.sh arch.sh puppy.sh ebuild.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 directories within which the project will be built -mkdir -p ${BUILDDIR} -mkdir -p ${PROJECTDIR} - -# Build the project -make clean -make -make install -B DESTDIR=${PROJECTDIR} PREFIX=/usr - -# Copy the slack-desc and doinst.sh files into the build install directory -mkdir ${PROJECTDIR}/install -cp ${CURRDIR}/slackpackage/slack-desc ${PROJECTDIR}/install -cp ${CURRDIR}/slackpackage/doinst.sh ${PROJECTDIR}/install - -# Compress the build directory -cd ${BUILDDIR} -tar -c -f ${APP}-${VERSION}-${RELEASE}.tar . -sync -xz ${APP}-${VERSION}-${RELEASE}.tar -sync -mv ${APP}-${VERSION}-${RELEASE}.tar.xz ${CURRDIR}/slackpackage/${APP}-${VERSION}-${ARCH_TYPE}-${RELEASE}.txz -cd ${CURRDIR} - -# Remove the temporary build directory -rm -fr ${BUILDDIR} diff --git a/packages/unmaintained/slackpackage/doinst.sh b/packages/unmaintained/slackpackage/doinst.sh deleted file mode 100755 index 2d703395..00000000 --- a/packages/unmaintained/slackpackage/doinst.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -e - -# This script is run after installation. -# Any additional configuration goes here. diff --git a/packages/unmaintained/slackpackage/slack-desc b/packages/unmaintained/slackpackage/slack-desc deleted file mode 100644 index 8705a13b..00000000 --- a/packages/unmaintained/slackpackage/slack-desc +++ /dev/null @@ -1,19 +0,0 @@ -# HOW TO EDIT THIS FILE: -# The "handy ruler" below makes it easier to edit a package description. Line -# up the first '|' above the ':' following the base package name, and the '|' on -# the right side marks the last column you can put a character in. You must make -# exactly 11 lines for the formatting to be correct. It's also customary to -# leave one space after the ':'. - - |-----handy-ruler--------------------------------------------------| -pybitmessage: pybitmessage (Send encrypted messages) -pybitmessage: -pybitmessage: Bitmessage is a P2P communications protocol used to send -pybitmessage: encrypted messages to another person or to many subscribers. It -pybitmessage: is decentralized and trustless, meaning that you need-not -pybitmessage: inherently trust any entities like root certificate authorities. -pybitmessage: It uses strong authentication which means that the sender of a -pybitmessage: message cannot be spoofed, and it aims to hide "non-content" -pybitmessage: data, like the sender and receiver of messages, from passive -pybitmessage: eavesdroppers like those running warrantless wiretapping -pybitmessage: programs. 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/pybitmessage b/pybitmessage deleted file mode 120000 index e8310385..00000000 --- a/pybitmessage +++ /dev/null @@ -1 +0,0 @@ -src \ No newline at end of file diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 6f4a22fb..00000000 --- a/requirements.txt +++ /dev/null @@ -1,8 +0,0 @@ -coverage -psutil -pycryptodome -PyQt5;python_version>="3.7" -mock;python_version<="2.7" -python_prctl;platform_system=="Linux" -six -xvfbwrapper;platform_system=="Linux" diff --git a/run-kivy-tests-in-docker.sh b/run-kivy-tests-in-docker.sh deleted file mode 100755 index f34bbd19..00000000 --- a/run-kivy-tests-in-docker.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -docker build -t pybm-kivy-travis-bionic -f packages/docker/Dockerfile.kivy-travis . -docker run pybm-kivy-travis-bionic diff --git a/run-tests-in-docker.sh b/run-tests-in-docker.sh deleted file mode 100755 index 174cb754..00000000 --- a/run-tests-in-docker.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh - -DOCKERFILE=packages/docker/Dockerfile.bionic - -# explicitly mark appimage stage because it builds in any case -docker build --target appimage -t pybm/appimage -f $DOCKERFILE . - -if [ $? -gt 0 ]; then - docker build --no-cache --target appimage -t pybm/appimage -f $DOCKERFILE . -fi - -docker build --target tox -t pybm/tox -f $DOCKERFILE . -docker run --rm -t pybm/tox diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 28ceaede..00000000 --- a/setup.cfg +++ /dev/null @@ -1,25 +0,0 @@ -# Since there is overlap in the violations that the different tools check for, it makes sense to quiesce some warnings -# in some tools if those warnings in other tools are preferred. This avoids the need to add duplicate lint warnings. - -# max-line-length should be removed ASAP! - -[pycodestyle] -max-line-length = 119 - -[flake8] -max-line-length = 119 -exclude = bitmessagecli.py,bitmessagecurses,bitmessageqt,plugins,tests,umsgpack -ignore = E722,F841,W503 -# E722: pylint is preferred for bare-except -# F841: pylint is preferred for unused-variable -# W503: deprecated: https://bugs.python.org/issue26763 - https://www.python.org/dev/peps/pep-0008/#should-a-line-break-before-or-after-a-binary-operator - -# pylint honours the [MESSAGES CONTROL] section -# as well as [MASTER] section -[MESSAGES CONTROL] -disable=invalid-name,bare-except,broad-except -# invalid-name: needs fixing during a large, project-wide refactor -# bare-except,broad-except: Need fixing once thorough testing is easier - -[MASTER] -init-hook = import sys;sys.path.append('src') diff --git a/setup.py b/setup.py 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/src/__init__.py b/src/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/about.py b/src/about.py new file mode 100644 index 00000000..3ede6a15 --- /dev/null +++ b/src/about.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'about.ui' +# +# Created: Mon Mar 11 11:19:35 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_aboutDialog(object): + def setupUi(self, aboutDialog): + aboutDialog.setObjectName(_fromUtf8("aboutDialog")) + aboutDialog.resize(360, 315) + self.buttonBox = QtGui.QDialogButtonBox(aboutDialog) + self.buttonBox.setGeometry(QtCore.QRect(20, 280, 311, 32)) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Ok) + self.buttonBox.setObjectName(_fromUtf8("buttonBox")) + self.label = QtGui.QLabel(aboutDialog) + self.label.setGeometry(QtCore.QRect(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, 20)) + 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, 180, 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(QtGui.QApplication.translate("aboutDialog", "About", None, QtGui.QApplication.UnicodeUTF8)) + self.label.setText(QtGui.QApplication.translate("aboutDialog", "PyBitmessage", None, QtGui.QApplication.UnicodeUTF8)) + self.labelVersion.setText(QtGui.QApplication.translate("aboutDialog", "version ?", None, QtGui.QApplication.UnicodeUTF8)) + self.label_2.setText(QtGui.QApplication.translate("aboutDialog", "Copyright © 2013 Jonathan Warren", None, QtGui.QApplication.UnicodeUTF8)) + self.label_3.setText(QtGui.QApplication.translate("aboutDialog", "

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

", None, QtGui.QApplication.UnicodeUTF8)) + self.label_5.setText(QtGui.QApplication.translate("aboutDialog", "This is Beta software.", None, QtGui.QApplication.UnicodeUTF8)) + diff --git a/src/about.ui b/src/about.ui new file mode 100644 index 00000000..58a0bc36 --- /dev/null +++ b/src/about.ui @@ -0,0 +1,154 @@ + + + aboutDialog + + + + 0 + 0 + 360 + 315 + + + + About + + + + + 20 + 280 + 311 + 32 + + + + Qt::Horizontal + + + QDialogButtonBox::Ok + + + + + + 70 + 126 + 111 + 20 + + + + + 75 + true + + + + PyBitmessage + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 190 + 126 + 161 + 20 + + + + version ? + + + + + + 10 + 150 + 341 + 20 + + + + Copyright © 2013 Jonathan Warren + + + 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 + 180 + 341 + 20 + + + + This is Beta software. + + + Qt::AlignCenter + + + + + + + buttonBox + accepted() + aboutDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + aboutDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/addresses.py b/src/addresses.py index e48873a1..a6a571f6 100644 --- a/src/addresses.py +++ b/src/addresses.py @@ -1,192 +1,130 @@ -""" -Operations with addresses -""" -# pylint: disable=inconsistent-return-statements import hashlib -import logging -from binascii import hexlify, unhexlify -from struct import pack, unpack +from struct import * +from pyelliptic import arithmetic -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 a[2:].decode('hex') + else: + return ('0'+a[2:]).decode('hex') 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) + strlen = len(string) num = 0 try: + power = strlen - 1 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) * (base ** power) + power -= 1 + 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') + print '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') - + print 'varint cannot be >= 18446744073709551616' + raise SystemExit 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. - 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]) - if encodedValue < 253: - raise varintDecodeError( - 'This varint does not encode the value with the lowest' - ' possible number of bytes.') - return (encodedValue, 3) + a, = unpack('>H',data[1:3]) + return (a,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]) - if encodedValue < 65536: - raise varintDecodeError( - 'This varint does not encode the value with the lowest' - ' possible number of bytes.') - return (encodedValue, 5) + a, = unpack('>I',data[1:5]) + return (a,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]) - if encodedValue < 4294967296: - raise varintDecodeError( - 'This varint does not encode the value with the lowest' - ' possible number of bytes.') - return (encodedValue, 9) + a, = unpack('>Q',data[1:9]) + return (a,9) + def calculateInventoryHash(data): - """Calculate inventory hash from object data""" sha = hashlib.new('sha512') sha2 = hashlib.new('sha512') sha.update(data) sha2.update(sha.digest()) return sha2.digest()[0:32] - -def encodeAddress(version, stream, ripe): - """Convert ripe to address""" - if version >= 2 and version < 4: +def encodeAddress(version,stream,ripe): + if version >= 2: 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') - - storedBinaryData = encodeVarint(version) + encodeVarint(stream) + ripe - - # Generate the checksum + a = encodeVarint(version) + encodeVarint(stream) + ripe sha = hashlib.new('sha512') - sha.update(storedBinaryData) + sha.update(a) currentHash = sha.digest() + #print 'sha after first hashing: ', sha.hexdigest() sha = hashlib.new('sha512') sha.update(currentHash) + #print 'sha after second hashing: ', sha.hexdigest() + checksum = sha.digest()[0:4] + #print 'len(a) = ', len(a) + #print 'checksum = ', checksum.encode('hex') + #print 'len(checksum) = ', len(checksum) - # FIXME: encodeBase58 should take binary data, to reduce conversions - # encodeBase58(storedBinaryData + checksum) - asInt = int(hexlify(storedBinaryData) + hexlify(checksum), 16) - # should it be str? If yes, it should be everywhere in the code - return 'BM-' + encodeBase58(asInt) - + asInt = int(a.encode('hex') + checksum.encode('hex'),16) + #asInt = int(checksum.encode('hex') + a.encode('hex'),16) + # print asInt + return 'BM-'+ encodeBase58(asInt) def decodeAddress(address): - """ - returns (status, address version number, stream number, - data (almost certainly a ripe hash)) - """ - # pylint: disable=too-many-return-statements,too-many-statements - # pylint: disable=too-many-branches + #returns (status, address version number, stream number, data (almost certainly a ripe hash)) address = str(address).strip() @@ -196,88 +134,153 @@ 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,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 - data = unhexlify(hexdata) + #print 'hexdata', hexdata + + data = hexdata.decode('hex') checksum = data[-4:] sha = hashlib.new('sha512') sha.update(data[:-4]) currentHash = sha.digest() + #print 'sha after first hashing: ', sha.hexdigest() sha = hashlib.new('sha512') sha.update(currentHash) + #print 'sha after second hashing: ', sha.hexdigest() if checksum != sha.digest()[0:4]: status = 'checksumfailed' - return status, 0, 0, '' + return status,0,0,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, '' + addressVersionNumber, bytesUsedByVersionNumber = decodeVarint(data[:9]) + #print 'addressVersionNumber', addressVersionNumber + #print 'bytesUsedByVersionNumber', bytesUsedByVersionNumber - if addressVersionNumber > 4: - logger.error('cannot decode address version numbers this high') + if addressVersionNumber > 3: + print 'cannot decode address version numbers this high' status = 'versiontoohigh' - return status, 0, 0, '' + return status,0,0,0 elif addressVersionNumber == 0: - logger.error('cannot decode address version numbers of zero.') + print 'cannot decode address version numbers of zero.' status = 'versiontoohigh' - return status, 0, 0, '' - - try: - streamNumber, bytesUsedByStreamNumber = \ - decodeVarint(data[bytesUsedByVersionNumber:]) - except varintDecodeError as e: - logger.error(str(e)) - status = 'varintmalformed' - return status, 0, 0, '' + return status,0,0,0 + streamNumber, bytesUsedByStreamNumber = decodeVarint(data[bytesUsedByVersionNumber:]) + #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] - if len(embeddedRipeData) == 19: - return status, addressVersionNumber, streamNumber, \ - b'\x00' + embeddedRipeData - elif len(embeddedRipeData) == 20: - return status, addressVersionNumber, streamNumber, \ - embeddedRipeData - elif len(embeddedRipeData) == 18: - return status, addressVersionNumber, streamNumber, \ - b'\x00\x00' + embeddedRipeData - elif len(embeddedRipeData) < 18: - return 'ripetooshort', 0, 0, '' - elif len(embeddedRipeData) > 20: - return 'ripetoolong', 0, 0, '' - 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, '' - elif len(embeddedRipeData) > 20: - return 'ripetoolong', 0, 0, '' - elif len(embeddedRipeData) < 4: - return 'ripetooshort', 0, 0, '' - x00string = b'\x00' * (20 - len(embeddedRipeData)) - return status, addressVersionNumber, streamNumber, \ - x00string + embeddedRipeData - + if len(data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4]) == 19: + return status,addressVersionNumber,streamNumber,'\x00'+data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4] + elif len(data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4]) == 20: + return status,addressVersionNumber,streamNumber,data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4] + elif len(data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4]) == 18: + return status,addressVersionNumber,streamNumber,'\x00\x00'+data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4] + elif len(data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4]) < 18: + return 'ripetooshort',0,0,0 + elif len(data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4]) > 20: + return 'ripetoolong',0,0,0 + else: + return 'otherproblem',0,0,0 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 + +def addressStream(address): + #returns the stream number of an address or False if there is a problem with the address. + + #check for the BM- at the front of the address. If it isn't there, this address might be for a different version of Bitmessage + if address[:3] != 'BM-': + status = 'missingbm' + return False + #here we take off the BM- + integer = decodeBase58(address[3:]) + #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 = hexdata.decode('hex') + checksum = data[-4:] + + sha = hashlib.new('sha512') + sha.update(data[:-4]) + currentHash = sha.digest() + #print 'sha after first hashing: ', sha.hexdigest() + sha = hashlib.new('sha512') + sha.update(currentHash) + #print 'sha after second hashing: ', sha.hexdigest() + + if checksum != sha.digest()[0:4]: + print 'checksum failed' + status = 'checksumfailed' + return False + #else: + # print 'checksum PASSED' + + addressVersionNumber, bytesUsedByVersionNumber = decodeVarint(data[:9]) + #print 'addressVersionNumber', addressVersionNumber + #print 'bytesUsedByVersionNumber', bytesUsedByVersionNumber + + if addressVersionNumber < 1: + print 'cannot decode version address version numbers this high' + status = 'versiontoohigh' + return False + + streamNumber, bytesUsedByStreamNumber = decodeVarint(data[bytesUsedByVersionNumber:9+bytesUsedByVersionNumber]) + #print streamNumber + status = 'success' + return streamNumber + + +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:', ripe.digest().encode('hex') + 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:', data.encode('hex') + diff --git a/src/api client.py b/src/api client.py new file mode 100644 index 00000000..e07258d5 --- /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, 3, 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'),3,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/api.py b/src/api.py deleted file mode 100644 index c27a24e7..00000000 --- a/src/api.py +++ /dev/null @@ -1,1587 +0,0 @@ -# Copyright (c) 2012-2016 Jonathan Warren -# Copyright (c) 2012-2023 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`. -""" - -import base64 -import errno -import hashlib -import json -import random -import socket -import subprocess # nosec B404 -import time -from binascii import hexlify, unhexlify -from struct import pack - -import six -from six.moves import configparser, http_client, xmlrpc_server - -import defaults -import helper_inbox -import helper_sent -import protocol -import proofofwork -import queues -import shared - -import shutdown -import state -from addresses import ( - addBMIfNotPresent, - calculateInventoryHash, - decodeAddress, - decodeVarint, - varintDecodeError -) -from bmconfigparser import config -from debug import logger -from helper_sql import ( - SqlBulkExecute, sqlExecute, sqlQuery, sqlStoredProcedure, sql_ready) -from inventory import Inventory - -try: - from network import BMConnectionPool -except ImportError: - BMConnectionPool = None - -from network import stats, StoppableThread -from version import softwareVersion - -try: # TODO: write tests for XML vulnerabilities - from defusedxml.xmlrpc import monkey_patch -except ImportError: - logger.warning( - 'defusedxml not available, only use API on a secure, closed network.') -else: - monkey_patch() - - -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 - - def __str__(self): - return "API Error %04i: %s" % (self.faultCode, self.faultString) - - -# 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 - - -# 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""" - - # pylint: disable=protected-access - def do_POST(self): - """ - 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. - - .. note:: this method is the same as in - `SimpleXMLRPCServer.SimpleXMLRPCRequestHandler`, - just hacked to handle cookies - """ - - # Check that the path is legal - if not self.is_rpc_path_valid(): - self.report_404() - return - - try: - # Get arguments by reading body of request. - # We read this in chunks to avoid straining - # socket.read(); around the 10 or 15Mb mark, some platforms - # begin to have problems (bug #792570). - max_chunk_size = 10 * 1024 * 1024 - size_remaining = int(self.headers["content-length"]) - 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) - size_remaining -= len(L[-1]) - data = b''.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 - # internal error, report as HTTP server error - self.send_response(http_client.INTERNAL_SERVER_ERROR) - 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_header("Content-length", str(len(response))) - - # HACK :start -> sends cookies here - if self.cookies: - for cookie in self.cookies: - self.send_header('Set-Cookie', cookie.output(header='')) - # HACK :end - - self.end_headers() - self.wfile.write(response) - - # shut down the connection - 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')) - else: - logger.warning( - 'Authentication failed because header lacks' - ' Authentication field') - time.sleep(2) - - 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): - try: - if decode_type == 'hex': - return unhexlify(text) - elif decode_type == 'base64': - return base64.b64decode(text) - except Exception as e: - raise APIError( - 22, 'Decode error - %s. Had trouble while decoding string: %r' - % (e, text) - ) - - def _verifyAddress(self, address): - status, addressVersionNumber, streamNumber, ripe = \ - decodeAddress(address) - if status != 'success': - 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) - if status == 'varintmalformed': - raise APIError(26, 'Malformed varint in address: ' + address) - raise APIError( - 7, 'Could not decode address: %s : %s' % (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.' - ) - if streamNumber != 1: - 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) - - @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 - - @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: - label = shared.fixPotentiallyInvalidUTF8Data(label) - data.append({ - 'label': base64.b64encode(label), - 'address': address - }) - return {'addresses': 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.""" - 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) - if queryreturn != []: - 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', '')) - 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.""" - 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', '')) - return "Deleted address book entry for %s if it existed" % address - - @command('createRandomAddress') - def HandleCreateRandomAddress( - self, label, eighteenByteRipe=False, totalDifficulty=0, - smallMessageDifficulty=0 - ): - """ - Create one address using the random number generator. - - :param str label: base64 encoded label for the address - :param bool eighteenByteRipe: is telling Bitmessage whether to - generate an address with an 18 byte RIPE hash - (as opposed to a 19 byte hash). - """ - - nonceTrialsPerByte = self.config.get( - 'bitmessagesettings', 'defaultnoncetrialsperbyte' - ) if not totalDifficulty else int( - defaults.networkDefaultProofOfWorkNonceTrialsPerByte - * totalDifficulty) - payloadLengthExtraBytes = self.config.get( - 'bitmessagesettings', 'defaultpayloadlengthextrabytes' - ) if not smallMessageDifficulty else int( - defaults.networkDefaultPayloadLengthExtraBytes - * smallMessageDifficulty) - - if not isinstance(eighteenByteRipe, bool): - raise APIError( - 23, 'Bool expected in eighteenByteRipe, saw %s instead' - % type(eighteenByteRipe)) - label = self._decode(label, "base64") - try: - label.decode('utf-8') - except UnicodeDecodeError: - raise APIError(17, 'Label is not valid UTF-8 data.') - queues.apiAddressGeneratorReturnQueue.queue.clear() - # FIXME hard coded stream no - streamNumberForAddress = 1 - queues.addressGeneratorQueue.put(( - 'createRandomAddress', 4, streamNumberForAddress, label, 1, "", - eighteenByteRipe, nonceTrialsPerByte, payloadLengthExtraBytes - )) - return queues.apiAddressGeneratorReturnQueue.get() - - # pylint: disable=too-many-arguments - @command('createDeterministicAddresses') - def HandleCreateDeterministicAddresses( - self, passphrase, numberOfAddresses=1, addressVersionNumber=0, - streamNumber=0, eighteenByteRipe=False, totalDifficulty=0, - smallMessageDifficulty=0 - ): - """ - Create many addresses deterministically using the passphrase. - - :param str passphrase: base64 encoded passphrase - :param int numberOfAddresses: number of addresses to create, - up to 999 - - *addressVersionNumber* and *streamNumber* may be set to 0 - which will tell Bitmessage to use the most up-to-date - address version and the most available stream. - """ - - nonceTrialsPerByte = self.config.get( - 'bitmessagesettings', 'defaultnoncetrialsperbyte' - ) if not totalDifficulty else int( - defaults.networkDefaultProofOfWorkNonceTrialsPerByte - * totalDifficulty) - payloadLengthExtraBytes = self.config.get( - 'bitmessagesettings', 'defaultpayloadlengthextrabytes' - ) if not smallMessageDifficulty else int( - defaults.networkDefaultPayloadLengthExtraBytes - * smallMessageDifficulty) - - if not passphrase: - 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)) - passphrase = self._decode(passphrase, "base64") - # 0 means "just use the proper addressVersionNumber" - if addressVersionNumber == 0: - 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 streamNumber == 0: # 0 means "just use the most available stream" - streamNumber = 1 # FIXME hard coded stream no - if streamNumber != 1: - 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?') - 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. - """ - - numberOfAddresses = 1 - eighteenByteRipe = False - if not passphrase: - 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 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. - """ - - passphrase = self._decode(passphrase, "base64") - if not passphrase: - 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') - label = str_chan + ' ' + passphrase - except UnicodeDecodeError: - 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: - raise APIError(24, 'Chan address is already present.') - - @command('joinChan') - def HandleJoinChan(self, passphrase, suppliedAddress): - """ - Join a chan. passphrase must be base64 encoded. Returns 'success'. - """ - - passphrase = self._decode(passphrase, "base64") - if not passphrase: - 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') - label = str_chan + ' ' + passphrase - except UnicodeDecodeError: - label = str_chan + ' ' + repr(passphrase) - - 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: - raise APIError(24, 'Chan address is already present.') - - 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) - 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" - - @command('deleteAddress') - def HandleDeleteAddress(self, address): - """ - Permanently delete the address from keys.dat file. Returns 'success'. - """ - 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', ('', '', ''))) - 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. - """ - - 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*. - """ - - queryreturn = sqlQuery( - "SELECT msgid FROM inbox where folder='inbox' ORDER BY received") - - 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: - 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) - # 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 - - @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. - """ - - 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 - ]} - - @command('getAllSentMessageIds', 'getAllSentMessageIDs') - def HandleGetAllSentMessageIds(self): - """ - The same as *getAllInboxMessageIds* but for sent, - result key - *sentMessageIds*. - """ - - queryreturn = sqlQuery( - "SELECT msgid FROM sent WHERE folder='sent'" - " ORDER BY lastactiontime" - ) - return {"sentMessageIds": [ - {'msgid': hexlify(msgid)} for msgid, in queryreturn - ]} - - # 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. - """ - - 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 - ]} - - @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* - """ - - 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) - 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") - 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") - 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.') - 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 - toAddress = addBMIfNotPresent(toAddress) - 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.') - - ackdata = helper_sent.insert( - toAddress=toAddress, fromAddress=fromAddress, - subject=subject, message=message, encoding=encodingType, ttl=TTL) - - toLabel = '' - queryreturn = sqlQuery( - "SELECT label FROM addressbook WHERE address=?", toAddress) - try: - toLabel = queryreturn[0][0] - except IndexError: - pass - - queues.UISignalQueue.put(('displayNewSentMessage', ( - toAddress, toLabel, fromAddress, subject, message, ackdata))) - queues.workerQueue.put(('sendmessage', toAddress)) - - return hexlify(ackdata) - - @command('sendBroadcast') - def HandleSendBroadcast( - self, fromAddress, subject, message, encodingType=2, - TTL=4 * 24 * 60 * 60): - """Send the broadcast message. Similiar to *sendMessage*.""" - - if encodingType not in (2, 3): - raise APIError(6, 'The encoding type must be 2 or 3.') - - subject = self._decode(subject, "base64") - message = self._decode(message, "base64") - if len(subject + message) > (2 ** 18 - 500): - raise APIError(27, 'Message is too long.') - if TTL < 60 * 60: - TTL = 60 * 60 - if TTL > 28 * 24 * 60 * 60: - TTL = 28 * 24 * 60 * 60 - 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.') - - toAddress = str_broadcast_subscribers - - ackdata = helper_sent.insert( - fromAddress=fromAddress, subject=subject, - message=message, status='broadcastqueued', - encoding=encodingType) - - toLabel = str_broadcast_subscribers - queues.UISignalQueue.put(('displayNewSentMessage', ( - toAddress, toLabel, fromAddress, subject, message, ackdata))) - queues.workerQueue.put(('sendbroadcast', '')) - - return hexlify(ackdata) - - @command('getStatus') - def HandleGetStatus(self, ackdata): - """ - Get the status of sent message by its ackdata (hex encoded). - Returns one of these strings: notfound, msgqueued, - broadcastqueued, broadcastsent, doingpubkeypow, awaitingpubkey, - doingmsgpow, forcepow, msgsent, msgsentnoackexpected or ackreceived. - """ - - if len(ackdata) < 76: - # The length of ackData should be at least 38 bytes (76 hex digits) - raise APIError(15, 'Invalid ackData object size.') - ackdata = self._decode(ackdata, "hex") - queryreturn = sqlQuery( - "SELECT status FROM sent where ackdata=?", ackdata) - try: - return queryreturn[0][0] - except IndexError: - return 'notfound' - - @command('addSubscription') - def HandleAddSubscription(self, address, label=''): - """Subscribe to the address. label must be base64 encoded.""" - - if label: - label = self._decode(label, "base64") - try: - label.decode('utf-8') - except UnicodeDecodeError: - raise APIError(17, 'Label is not valid UTF-8 data.') - self._verifyAddress(address) - address = addBMIfNotPresent(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: - raise APIError(16, 'You are already subscribed to that address.') - sqlExecute( - "INSERT INTO subscriptions VALUES (?,?,?)", label, address, True) - shared.reloadBroadcastSendersForWhichImWatching() - queues.UISignalQueue.put(('rerenderMessagelistFromLabels', '')) - queues.UISignalQueue.put(('rerenderSubscriptions', '')) - return 'Added subscription.' - - @command('deleteSubscription') - def HandleDeleteSubscription(self, address): - """ - Unsubscribe from the address. The program does not check whether - you were subscribed in the first place. - """ - - address = addBMIfNotPresent(address) - sqlExecute("DELETE FROM subscriptions WHERE address=?", address) - shared.reloadBroadcastSendersForWhichImWatching() - queues.UISignalQueue.put(('rerenderMessagelistFromLabels', '')) - queues.UISignalQueue.put(('rerenderSubscriptions', '')) - return 'Deleted subscription if it existed.' - - @command('listSubscriptions') - def ListSubscriptions(self): - """ - Returns dict with a list of all subscriptions - in the *subscriptions* key. - """ - - queryreturn = sqlQuery( - "SELECT label, address, enabled FROM subscriptions") - data = [] - for label, address, enabled in queryreturn: - label = shared.fixPotentiallyInvalidUTF8Data(label) - data.append({ - 'label': base64.b64encode(label), - 'address': address, - 'enabled': enabled == 1 - }) - return {'subscriptions': data} - - @command('disseminatePreEncryptedMsg') - def HandleDisseminatePreEncryptedMsg( - self, encryptedPayload, requiredAverageProofOfWorkNonceTrialsPerByte, - requiredPayloadLengthExtraBytes): - """Handle a request to disseminate an encrypted message""" - - # The device issuing this command to PyBitmessage supplies a msg - # object that has already been encrypted but which still needs the POW - # to be done. PyBitmessage accepts this msg object and sends it out - # to the rest of the Bitmessage network as if it had generated - # the message itself. Please do not yet add this to the api doc. - encryptedPayload = b'\x00' * 8 + self._decode(encryptedPayload, "hex") - # compatibility stub ^, since disseminatePreEncryptedMsg - # still expects the encryptedPayload without a nonce - objectType, toStreamNumber, expiresTime = \ - protocol.decodeObjectParameters(encryptedPayload) - encryptedPayload = encryptedPayload[8:] - TTL = expiresTime - time.time() + 300 # a bit of extra padding - # Let us do the POW and attach it to the front - target = 2**64 / ( - requiredAverageProofOfWorkNonceTrialsPerByte * ( - len(encryptedPayload) + 8 - + requiredPayloadLengthExtraBytes + (( - TTL * ( - len(encryptedPayload) + 8 - + requiredPayloadLengthExtraBytes - )) / (2 ** 16)) - )) - logger.debug("expiresTime: %s", expiresTime) - logger.debug("TTL: %s", TTL) - logger.debug("objectType: %s", objectType) - logger.info( - '(For msg message via API) Doing proof of work. Total required' - ' difficulty: %s\nRequired small message difficulty: %s', - float(requiredAverageProofOfWorkNonceTrialsPerByte) - / defaults.networkDefaultProofOfWorkNonceTrialsPerByte, - float(requiredPayloadLengthExtraBytes) - / defaults.networkDefaultPayloadLengthExtraBytes, - ) - powStartTime = time.time() - initialHash = hashlib.sha512(encryptedPayload).digest() - trialValue, nonce = proofofwork.run(target, initialHash) - logger.info( - '(For msg message via API) Found proof of work %s\nNonce: %s\n' - 'POW took %s seconds. %s nonce trials per second.', - trialValue, nonce, int(time.time() - powStartTime), - nonce / (time.time() - powStartTime) - ) - encryptedPayload = pack('>Q', nonce) + encryptedPayload - inventoryHash = calculateInventoryHash(encryptedPayload) - Inventory()[inventoryHash] = ( - objectType, toStreamNumber, encryptedPayload, - expiresTime, b'' - ) - logger.info( - 'Broadcasting inv for msg(API disseminatePreEncryptedMsg' - ' command): %s', hexlify(inventoryHash)) - queues.invQueue.put((toStreamNumber, inventoryHash)) - return hexlify(inventoryHash).decode() - - @command('trashSentMessageByAckData') - def HandleTrashSentMessageByAckDAta(self, ackdata): - """Trash a sent message by ackdata (hex encoded)""" - # 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) - 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. - payload = self._decode(payload, "hex") - - # Let us do the POW - target = 2 ** 64 / (( - len(payload) + defaults.networkDefaultPayloadLengthExtraBytes + 8 - ) * defaults.networkDefaultProofOfWorkNonceTrialsPerByte) - logger.info('(For pubkey message via API) Doing proof of work...') - 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 - ) - 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 - else: - pubkeyReadPosition += 4 - addressVersionLength = decodeVarint( - payload[pubkeyReadPosition:pubkeyReadPosition + 10])[1] - pubkeyReadPosition += addressVersionLength - pubkeyStreamNumber = decodeVarint( - payload[pubkeyReadPosition:pubkeyReadPosition + 10])[0] - inventoryHash = calculateInventoryHash(payload) - objectType = 1 # .. todo::: support v4 pubkeys - TTL = 28 * 24 * 60 * 60 - Inventory()[inventoryHash] = ( - objectType, pubkeyStreamNumber, payload, int(time.time()) + TTL, '' - ) - logger.info( - 'broadcasting inv within API command disseminatePubkey with' - ' hash: %s', hexlify(inventoryHash)) - queues.invQueue.put((pubkeyStreamNumber, inventoryHash)) - - @command( - 'getMessageDataByDestinationHash', 'getMessageDataByDestinationTag') - def HandleGetMessageDataByDestinationHash(self, requestedHash): - """Handle a request to get message data by destination hash""" - - # 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(requestedHash) != 32: - 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") - 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) - - queryreturn = sqlQuery( - "SELECT payload FROM inventory WHERE tag = ?", requestedHash) - return {"receivedMessageDatas": [ - {'data': hexlify(payload)} for payload, in queryreturn - ]} - - @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: - networkStatus = 'notConnected' - elif state.clientHasReceivedIncomingConnections: - networkStatus = 'connectedAndReceivingIncomingConnections' - else: - networkStatus = 'connectedButHaveNotReceivedIncomingConnections' - return { - 'networkConnections': connections_num, - 'numberOfMessagesProcessed': state.numberOfMessagesProcessed, - 'numberOfBroadcastsProcessed': state.numberOfBroadcastsProcessed, - 'numberOfPubkeysProcessed': state.numberOfPubkeysProcessed, - 'pendingDownload': stats.pendingDownload(), - 'networkStatus': networkStatus, - 'softwareName': 'PyBitmessage', - 'softwareVersion': softwareVersion - } - - @command('listConnections') - def HandleListConnections(self): - """ - Returns bitmessage connection information as dict with keys *inbound*, - *outbound*. - """ - if BMConnectionPool is None: - raise APIError(21, 'Could not import BMConnectionPool.') - inboundConnections = [] - outboundConnections = [] - for i in BMConnectionPool().inboundConnections.values(): - inboundConnections.append({ - 'host': i.destination.host, - 'port': i.destination.port, - 'fullyEstablished': i.fullyEstablished, - 'userAgent': str(i.userAgent) - }) - for i in BMConnectionPool().outboundConnections.values(): - outboundConnections.append({ - 'host': i.destination.host, - 'port': i.destination.port, - 'fullyEstablished': i.fullyEstablished, - 'userAgent': str(i.userAgent) - }) - return { - 'inbound': inboundConnections, - 'outbound': outboundConnections - } - - @command('helloWorld') - def HandleHelloWorld(self, a, b): - """Test two string params""" - return a + '-' + b - - @command('add') - def HandleAdd(self, a, b): - """Test two numeric params""" - return a + b - - @command('statusBar') - def HandleStatusBar(self, message): - """Update GUI statusbar message""" - queues.UISignalQueue.put(('updateStatusBar', message)) - return "success" - - @testmode('undeleteMessage') - def HandleUndeleteMessage(self, msgid): - """Undelete message""" - msgid = self._decode(msgid, "hex") - helper_inbox.undeleteMessage(msgid) - return "Undeleted message" - - @command('deleteAndVacuum') - def HandleDeleteAndVacuum(self): - """Cleanup trashes and vacuum messages database""" - 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' - - 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: - 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 - - try: - return self._handle_request(method, params) - except APIError as e: - _fault = e - except varintDecodeError as e: - logger.error(e) - _fault = APIError( - 26, '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__ 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/bitmessage_icons.qrc b/src/bitmessage_icons.qrc new file mode 100644 index 00000000..a186b01b --- /dev/null +++ b/src/bitmessage_icons.qrc @@ -0,0 +1,20 @@ + + + images/can-icon-24px-yellow.png + images/can-icon-24px-red.png + images/can-icon-24px-green.png + images/can-icon-24px.png + images/can-icon-16px.png + images/greenicon.png + images/redicon.png + images/yellowicon.png + images/addressbook.png + images/blacklist.png + images/identities.png + images/networkstatus.png + images/sent.png + images/subscriptions.png + images/send.png + images/inbox.png + + diff --git a/src/bitmessage_icons_rc.py b/src/bitmessage_icons_rc.py new file mode 100644 index 00000000..6e0f0391 --- /dev/null +++ b/src/bitmessage_icons_rc.py @@ -0,0 +1,2440 @@ +# -*- coding: utf-8 -*- + +# Resource object code +# +# Created: Sat May 25 13:22:59 2013 +# by: The Resource Compiler for PyQt (Qt v4.8.3) +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore + +qt_resource_data = "\ +\x00\x00\x10\xdb\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\ +\x01\x00\x9a\x9c\x18\x00\x00\x0a\x4d\x69\x43\x43\x50\x50\x68\x6f\ +\x74\x6f\x73\x68\x6f\x70\x20\x49\x43\x43\x20\x70\x72\x6f\x66\x69\ +\x6c\x65\x00\x00\x78\xda\x9d\x53\x77\x58\x93\xf7\x16\x3e\xdf\xf7\ +\x65\x0f\x56\x42\xd8\xf0\xb1\x97\x6c\x81\x00\x22\x23\xac\x08\xc8\ +\x10\x59\xa2\x10\x92\x00\x61\x84\x10\x12\x40\xc5\x85\x88\x0a\x56\ +\x14\x15\x11\x9c\x48\x55\xc4\x82\xd5\x0a\x48\x9d\x88\xe2\xa0\x28\ +\xb8\x67\x41\x8a\x88\x5a\x8b\x55\x5c\x38\xee\x1f\xdc\xa7\xb5\x7d\ +\x7a\xef\xed\xed\xfb\xd7\xfb\xbc\xe7\x9c\xe7\xfc\xce\x79\xcf\x0f\ +\x80\x11\x12\x26\x91\xe6\xa2\x6a\x00\x39\x52\x85\x3c\x3a\xd8\x1f\ +\x8f\x4f\x48\xc4\xc9\xbd\x80\x02\x15\x48\xe0\x04\x20\x10\xe6\xcb\ +\xc2\x67\x05\xc5\x00\x00\xf0\x03\x79\x78\x7e\x74\xb0\x3f\xfc\x01\ +\xaf\x6f\x00\x02\x00\x70\xd5\x2e\x24\x12\xc7\xe1\xff\x83\xba\x50\ +\x26\x57\x00\x20\x91\x00\xe0\x22\x12\xe7\x0b\x01\x90\x52\x00\xc8\ +\x2e\x54\xc8\x14\x00\xc8\x18\x00\xb0\x53\xb3\x64\x0a\x00\x94\x00\ +\x00\x6c\x79\x7c\x42\x22\x00\xaa\x0d\x00\xec\xf4\x49\x3e\x05\x00\ +\xd8\xa9\x93\xdc\x17\x00\xd8\xa2\x1c\xa9\x08\x00\x8d\x01\x00\x99\ +\x28\x47\x24\x02\x40\xbb\x00\x60\x55\x81\x52\x2c\x02\xc0\xc2\x00\ +\xa0\xac\x40\x22\x2e\x04\xc0\xae\x01\x80\x59\xb6\x32\x47\x02\x80\ +\xbd\x05\x00\x76\x8e\x58\x90\x0f\x40\x60\x00\x80\x99\x42\x2c\xcc\ +\x00\x20\x38\x02\x00\x43\x1e\x13\xcd\x03\x20\x4c\x03\xa0\x30\xd2\ +\xbf\xe0\xa9\x5f\x70\x85\xb8\x48\x01\x00\xc0\xcb\x95\xcd\x97\x4b\ +\xd2\x33\x14\xb8\x95\xd0\x1a\x77\xf2\xf0\xe0\xe2\x21\xe2\xc2\x6c\ +\xb1\x42\x61\x17\x29\x10\x66\x09\xe4\x22\x9c\x97\x9b\x23\x13\x48\ +\xe7\x03\x4c\xce\x0c\x00\x00\x1a\xf9\xd1\xc1\xfe\x38\x3f\x90\xe7\ +\xe6\xe4\xe1\xe6\x66\xe7\x6c\xef\xf4\xc5\xa2\xfe\x6b\xf0\x6f\x22\ +\x3e\x21\xf1\xdf\xfe\xbc\x8c\x02\x04\x00\x10\x4e\xcf\xef\xda\x5f\ +\xe5\xe5\xd6\x03\x70\xc7\x01\xb0\x75\xbf\x6b\xa9\x5b\x00\xda\x56\ +\x00\x68\xdf\xf9\x5d\x33\xdb\x09\xa0\x5a\x0a\xd0\x7a\xf9\x8b\x79\ +\x38\xfc\x40\x1e\x9e\xa1\x50\xc8\x3c\x1d\x1c\x0a\x0b\x0b\xed\x25\ +\x62\xa1\xbd\x30\xe3\x8b\x3e\xff\x33\xe1\x6f\xe0\x8b\x7e\xf6\xfc\ +\x40\x1e\xfe\xdb\x7a\xf0\x00\x71\x9a\x40\x99\xad\xc0\xa3\x83\xfd\ +\x71\x61\x6e\x76\xae\x52\x8e\xe7\xcb\x04\x42\x31\x6e\xf7\xe7\x23\ +\xfe\xc7\x85\x7f\xfd\x8e\x29\xd1\xe2\x34\xb1\x5c\x2c\x15\x8a\xf1\ +\x58\x89\xb8\x50\x22\x4d\xc7\x79\xb9\x52\x91\x44\x21\xc9\x95\xe2\ +\x12\xe9\x7f\x32\xf1\x1f\x96\xfd\x09\x93\x77\x0d\x00\xac\x86\x4f\ +\xc0\x4e\xb6\x07\xb5\xcb\x6c\xc0\x7e\xee\x01\x02\x8b\x0e\x58\xd2\ +\x76\x00\x40\x7e\xf3\x2d\x8c\x1a\x0b\x91\x00\x10\x67\x34\x32\x79\ +\xf7\x00\x00\x93\xbf\xf9\x8f\x40\x2b\x01\x00\xcd\x97\xa4\xe3\x00\ +\x00\xbc\xe8\x18\x5c\xa8\x94\x17\x4c\xc6\x08\x00\x00\x44\xa0\x81\ +\x2a\xb0\x41\x07\x0c\xc1\x14\xac\xc0\x0e\x9c\xc1\x1d\xbc\xc0\x17\ +\x02\x61\x06\x44\x40\x0c\x24\xc0\x3c\x10\x42\x06\xe4\x80\x1c\x0a\ +\xa1\x18\x96\x41\x19\x54\xc0\x3a\xd8\x04\xb5\xb0\x03\x1a\xa0\x11\ +\x9a\xe1\x10\xb4\xc1\x31\x38\x0d\xe7\xe0\x12\x5c\x81\xeb\x70\x17\ +\x06\x60\x18\x9e\xc2\x18\xbc\x86\x09\x04\x41\xc8\x08\x13\x61\x21\ +\x3a\x88\x11\x62\x8e\xd8\x22\xce\x08\x17\x99\x8e\x04\x22\x61\x48\ +\x34\x92\x80\xa4\x20\xe9\x88\x14\x51\x22\xc5\xc8\x72\xa4\x02\xa9\ +\x42\x6a\x91\x5d\x48\x23\xf2\x2d\x72\x14\x39\x8d\x5c\x40\xfa\x90\ +\xdb\xc8\x20\x32\x8a\xfc\x8a\xbc\x47\x31\x94\x81\xb2\x51\x03\xd4\ +\x02\x75\x40\xb9\xa8\x1f\x1a\x8a\xc6\xa0\x73\xd1\x74\x34\x0f\x5d\ +\x80\x96\xa2\x6b\xd1\x1a\xb4\x1e\x3d\x80\xb6\xa2\xa7\xd1\x4b\xe8\ +\x75\x74\x00\x7d\x8a\x8e\x63\x80\xd1\x31\x0e\x66\x8c\xd9\x61\x5c\ +\x8c\x87\x45\x60\x89\x58\x1a\x26\xc7\x16\x63\xe5\x58\x35\x56\x8f\ +\x35\x63\x1d\x58\x37\x76\x15\x1b\xc0\x9e\x61\xef\x08\x24\x02\x8b\ +\x80\x13\xec\x08\x5e\x84\x10\xc2\x6c\x82\x90\x90\x47\x58\x4c\x58\ +\x43\xa8\x25\xec\x23\xb4\x12\xba\x08\x57\x09\x83\x84\x31\xc2\x27\ +\x22\x93\xa8\x4f\xb4\x25\x7a\x12\xf9\xc4\x78\x62\x3a\xb1\x90\x58\ +\x46\xac\x26\xee\x21\x1e\x21\x9e\x25\x5e\x27\x0e\x13\x5f\x93\x48\ +\x24\x0e\xc9\x92\xe4\x4e\x0a\x21\x25\x90\x32\x49\x0b\x49\x6b\x48\ +\xdb\x48\x2d\xa4\x53\xa4\x3e\xd2\x10\x69\x9c\x4c\x26\xeb\x90\x6d\ +\xc9\xde\xe4\x08\xb2\x80\xac\x20\x97\x91\xb7\x90\x0f\x90\x4f\x92\ +\xfb\xc9\xc3\xe4\xb7\x14\x3a\xc5\x88\xe2\x4c\x09\xa2\x24\x52\xa4\ +\x94\x12\x4a\x35\x65\x3f\xe5\x04\xa5\x9f\x32\x42\x99\xa0\xaa\x51\ +\xcd\xa9\x9e\xd4\x08\xaa\x88\x3a\x9f\x5a\x49\x6d\xa0\x76\x50\x2f\ +\x53\x87\xa9\x13\x34\x75\x9a\x25\xcd\x9b\x16\x43\xcb\xa4\x2d\xa3\ +\xd5\xd0\x9a\x69\x67\x69\xf7\x68\x2f\xe9\x74\xba\x09\xdd\x83\x1e\ +\x45\x97\xd0\x97\xd2\x6b\xe8\x07\xe9\xe7\xe9\x83\xf4\x77\x0c\x0d\ +\x86\x0d\x83\xc7\x48\x62\x28\x19\x6b\x19\x7b\x19\xa7\x18\xb7\x19\ +\x2f\x99\x4c\xa6\x05\xd3\x97\x99\xc8\x54\x30\xd7\x32\x1b\x99\x67\ +\x98\x0f\x98\x6f\x55\x58\x2a\xf6\x2a\x7c\x15\x91\xca\x12\x95\x3a\ +\x95\x56\x95\x7e\x95\xe7\xaa\x54\x55\x73\x55\x3f\xd5\x79\xaa\x0b\ +\x54\xab\x55\x0f\xab\x5e\x56\x7d\xa6\x46\x55\xb3\x50\xe3\xa9\x09\ +\xd4\x16\xab\xd5\xa9\x1d\x55\xbb\xa9\x36\xae\xce\x52\x77\x52\x8f\ +\x50\xcf\x51\x5f\xa3\xbe\x5f\xfd\x82\xfa\x63\x0d\xb2\x86\x85\x46\ +\xa0\x86\x48\xa3\x54\x63\xb7\xc6\x19\x8d\x21\x16\xc6\x32\x65\xf1\ +\x58\x42\xd6\x72\x56\x03\xeb\x2c\x6b\x98\x4d\x62\x5b\xb2\xf9\xec\ +\x4c\x76\x05\xfb\x1b\x76\x2f\x7b\x4c\x53\x43\x73\xaa\x66\xac\x66\ +\x91\x66\x9d\xe6\x71\xcd\x01\x0e\xc6\xb1\xe0\xf0\x39\xd9\x9c\x4a\ +\xce\x21\xce\x0d\xce\x7b\x2d\x03\x2d\x3f\x2d\xb1\xd6\x6a\xad\x66\ +\xad\x7e\xad\x37\xda\x7a\xda\xbe\xda\x62\xed\x72\xed\x16\xed\xeb\ +\xda\xef\x75\x70\x9d\x40\x9d\x2c\x9d\xf5\x3a\x6d\x3a\xf7\x75\x09\ +\xba\x36\xba\x51\xba\x85\xba\xdb\x75\xcf\xea\x3e\xd3\x63\xeb\x79\ +\xe9\x09\xf5\xca\xf5\x0e\xe9\xdd\xd1\x47\xf5\x6d\xf4\xa3\xf5\x17\ +\xea\xef\xd6\xef\xd1\x1f\x37\x30\x34\x08\x36\x90\x19\x6c\x31\x38\ +\x63\xf0\xcc\x90\x63\xe8\x6b\x98\x69\xb8\xd1\xf0\x84\xe1\xa8\x11\ +\xcb\x68\xba\x91\xc4\x68\xa3\xd1\x49\xa3\x27\xb8\x26\xee\x87\x67\ +\xe3\x35\x78\x17\x3e\x66\xac\x6f\x1c\x62\xac\x34\xde\x65\xdc\x6b\ +\x3c\x61\x62\x69\x32\xdb\xa4\xc4\xa4\xc5\xe4\xbe\x29\xcd\x94\x6b\ +\x9a\x66\xba\xd1\xb4\xd3\x74\xcc\xcc\xc8\x2c\xdc\xac\xd8\xac\xc9\ +\xec\x8e\x39\xd5\x9c\x6b\x9e\x61\xbe\xd9\xbc\xdb\xfc\x8d\x85\xa5\ +\x45\x9c\xc5\x4a\x8b\x36\x8b\xc7\x96\xda\x96\x7c\xcb\x05\x96\x4d\ +\x96\xf7\xac\x98\x56\x3e\x56\x79\x56\xf5\x56\xd7\xac\x49\xd6\x5c\ +\xeb\x2c\xeb\x6d\xd6\x57\x6c\x50\x1b\x57\x9b\x0c\x9b\x3a\x9b\xcb\ +\xb6\xa8\xad\x9b\xad\xc4\x76\x9b\x6d\xdf\x14\xe2\x14\x8f\x29\xd2\ +\x29\xf5\x53\x6e\xda\x31\xec\xfc\xec\x0a\xec\x9a\xec\x06\xed\x39\ +\xf6\x61\xf6\x25\xf6\x6d\xf6\xcf\x1d\xcc\x1c\x12\x1d\xd6\x3b\x74\ +\x3b\x7c\x72\x74\x75\xcc\x76\x6c\x70\xbc\xeb\xa4\xe1\x34\xc3\xa9\ +\xc4\xa9\xc3\xe9\x57\x67\x1b\x67\xa1\x73\x9d\xf3\x35\x17\xa6\x4b\ +\x90\xcb\x12\x97\x76\x97\x17\x53\x6d\xa7\x8a\xa7\x6e\x9f\x7a\xcb\ +\x95\xe5\x1a\xee\xba\xd2\xb5\xd3\xf5\xa3\x9b\xbb\x9b\xdc\xad\xd9\ +\x6d\xd4\xdd\xcc\x3d\xc5\x7d\xab\xfb\x4d\x2e\x9b\x1b\xc9\x5d\xc3\ +\x3d\xef\x41\xf4\xf0\xf7\x58\xe2\x71\xcc\xe3\x9d\xa7\x9b\xa7\xc2\ +\xf3\x90\xe7\x2f\x5e\x76\x5e\x59\x5e\xfb\xbd\x1e\x4f\xb3\x9c\x26\ +\x9e\xd6\x30\x6d\xc8\xdb\xc4\x5b\xe0\xbd\xcb\x7b\x60\x3a\x3e\x3d\ +\x65\xfa\xce\xe9\x03\x3e\xc6\x3e\x02\x9f\x7a\x9f\x87\xbe\xa6\xbe\ +\x22\xdf\x3d\xbe\x23\x7e\xd6\x7e\x99\x7e\x07\xfc\x9e\xfb\x3b\xfa\ +\xcb\xfd\x8f\xf8\xbf\xe1\x79\xf2\x16\xf1\x4e\x05\x60\x01\xc1\x01\ +\xe5\x01\xbd\x81\x1a\x81\xb3\x03\x6b\x03\x1f\x04\x99\x04\xa5\x07\ +\x35\x05\x8d\x05\xbb\x06\x2f\x0c\x3e\x15\x42\x0c\x09\x0d\x59\x1f\ +\x72\x93\x6f\xc0\x17\xf2\x1b\xf9\x63\x33\xdc\x67\x2c\x9a\xd1\x15\ +\xca\x08\x9d\x15\x5a\x1b\xfa\x30\xcc\x26\x4c\x1e\xd6\x11\x8e\x86\ +\xcf\x08\xdf\x10\x7e\x6f\xa6\xf9\x4c\xe9\xcc\xb6\x08\x88\xe0\x47\ +\x6c\x88\xb8\x1f\x69\x19\x99\x17\xf9\x7d\x14\x29\x2a\x32\xaa\x2e\ +\xea\x51\xb4\x53\x74\x71\x74\xf7\x2c\xd6\xac\xe4\x59\xfb\x67\xbd\ +\x8e\xf1\x8f\xa9\x8c\xb9\x3b\xdb\x6a\xb6\x72\x76\x67\xac\x6a\x6c\ +\x52\x6c\x63\xec\x9b\xb8\x80\xb8\xaa\xb8\x81\x78\x87\xf8\x45\xf1\ +\x97\x12\x74\x13\x24\x09\xed\x89\xe4\xc4\xd8\xc4\x3d\x89\xe3\x73\ +\x02\xe7\x6c\x9a\x33\x9c\xe4\x9a\x54\x96\x74\x63\xae\xe5\xdc\xa2\ +\xb9\x17\xe6\xe9\xce\xcb\x9e\x77\x3c\x59\x35\x59\x90\x7c\x38\x85\ +\x98\x12\x97\xb2\x3f\xe5\x83\x20\x42\x50\x2f\x18\x4f\xe5\xa7\x6e\ +\x4d\x1d\x13\xf2\x84\x9b\x85\x4f\x45\xbe\xa2\x8d\xa2\x51\xb1\xb7\ +\xb8\x4a\x3c\x92\xe6\x9d\x56\x95\xf6\x38\xdd\x3b\x7d\x43\xfa\x68\ +\x86\x4f\x46\x75\xc6\x33\x09\x4f\x52\x2b\x79\x91\x19\x92\xb9\x23\ +\xf3\x4d\x56\x44\xd6\xde\xac\xcf\xd9\x71\xd9\x2d\x39\x94\x9c\x94\ +\x9c\xa3\x52\x0d\x69\x96\xb4\x2b\xd7\x30\xb7\x28\xb7\x4f\x66\x2b\ +\x2b\x93\x0d\xe4\x79\xe6\x6d\xca\x1b\x93\x87\xca\xf7\xe4\x23\xf9\ +\x73\xf3\xdb\x15\x6c\x85\x4c\xd1\xa3\xb4\x52\xae\x50\x0e\x16\x4c\ +\x2f\xa8\x2b\x78\x5b\x18\x5b\x78\xb8\x48\xbd\x48\x5a\xd4\x33\xdf\ +\x66\xfe\xea\xf9\x23\x0b\x82\x16\x7c\xbd\x90\xb0\x50\xb8\xb0\xb3\ +\xd8\xb8\x78\x59\xf1\xe0\x22\xbf\x45\xbb\x16\x23\x8b\x53\x17\x77\ +\x2e\x31\x5d\x52\xba\x64\x78\x69\xf0\xd2\x7d\xcb\x68\xcb\xb2\x96\ +\xfd\x50\xe2\x58\x52\x55\xf2\x6a\x79\xdc\xf2\x8e\x52\x83\xd2\xa5\ +\xa5\x43\x2b\x82\x57\x34\x95\xa9\x94\xc9\xcb\x6e\xae\xf4\x5a\xb9\ +\x63\x15\x61\x95\x64\x55\xef\x6a\x97\xd5\x5b\x56\x7f\x2a\x17\x95\ +\x5f\xac\x70\xac\xa8\xae\xf8\xb0\x46\xb8\xe6\xe2\x57\x4e\x5f\xd5\ +\x7c\xf5\x79\x6d\xda\xda\xde\x4a\xb7\xca\xed\xeb\x48\xeb\xa4\xeb\ +\x6e\xac\xf7\x59\xbf\xaf\x4a\xbd\x6a\x41\xd5\xd0\x86\xf0\x0d\xad\ +\x1b\xf1\x8d\xe5\x1b\x5f\x6d\x4a\xde\x74\xa1\x7a\x6a\xf5\x8e\xcd\ +\xb4\xcd\xca\xcd\x03\x35\x61\x35\xed\x5b\xcc\xb6\xac\xdb\xf2\xa1\ +\x36\xa3\xf6\x7a\x9d\x7f\x5d\xcb\x56\xfd\xad\xab\xb7\xbe\xd9\x26\ +\xda\xd6\xbf\xdd\x77\x7b\xf3\x0e\x83\x1d\x15\x3b\xde\xef\x94\xec\ +\xbc\xb5\x2b\x78\x57\x6b\xbd\x45\x7d\xf5\x6e\xd2\xee\x82\xdd\x8f\ +\x1a\x62\x1b\xba\xbf\xe6\x7e\xdd\xb8\x47\x77\x4f\xc5\x9e\x8f\x7b\ +\xa5\x7b\x07\xf6\x45\xef\xeb\x6a\x74\x6f\x6c\xdc\xaf\xbf\xbf\xb2\ +\x09\x6d\x52\x36\x8d\x1e\x48\x3a\x70\xe5\x9b\x80\x6f\xda\x9b\xed\ +\x9a\x77\xb5\x70\x5a\x2a\x0e\xc2\x41\xe5\xc1\x27\xdf\xa6\x7c\x7b\ +\xe3\x50\xe8\xa1\xce\xc3\xdc\xc3\xcd\xdf\x99\x7f\xb7\xf5\x08\xeb\ +\x48\x79\x2b\xd2\x3a\xbf\x75\xac\x2d\xa3\x6d\xa0\x3d\xa1\xbd\xef\ +\xe8\x8c\xa3\x9d\x1d\x5e\x1d\x47\xbe\xb7\xff\x7e\xef\x31\xe3\x63\ +\x75\xc7\x35\x8f\x57\x9e\xa0\x9d\x28\x3d\xf1\xf9\xe4\x82\x93\xe3\ +\xa7\x64\xa7\x9e\x9d\x4e\x3f\x3d\xd4\x99\xdc\x79\xf7\x4c\xfc\x99\ +\x6b\x5d\x51\x5d\xbd\x67\x43\xcf\x9e\x3f\x17\x74\xee\x4c\xb7\x5f\ +\xf7\xc9\xf3\xde\xe7\x8f\x5d\xf0\xbc\x70\xf4\x22\xf7\x62\xdb\x25\ +\xb7\x4b\xad\x3d\xae\x3d\x47\x7e\x70\xfd\xe1\x48\xaf\x5b\x6f\xeb\ +\x65\xf7\xcb\xed\x57\x3c\xae\x74\xf4\x4d\xeb\x3b\xd1\xef\xd3\x7f\ +\xfa\x6a\xc0\xd5\x73\xd7\xf8\xd7\x2e\x5d\x9f\x79\xbd\xef\xc6\xec\ +\x1b\xb7\x6e\x26\xdd\x1c\xb8\x25\xba\xf5\xf8\x76\xf6\xed\x17\x77\ +\x0a\xee\x4c\xdc\x5d\x7a\x8f\x78\xaf\xfc\xbe\xda\xfd\xea\x07\xfa\ +\x0f\xea\x7f\xb4\xfe\xb1\x65\xc0\x6d\xe0\xf8\x60\xc0\x60\xcf\xc3\ +\x59\x0f\xef\x0e\x09\x87\x9e\xfe\x94\xff\xd3\x87\xe1\xd2\x47\xcc\ +\x47\xd5\x23\x46\x23\x8d\x8f\x9d\x1f\x1f\x1b\x0d\x1a\xbd\xf2\x64\ +\xce\x93\xe1\xa7\xb2\xa7\x13\xcf\xca\x7e\x56\xff\x79\xeb\x73\xab\ +\xe7\xdf\xfd\xe2\xfb\x4b\xcf\x58\xfc\xd8\xf0\x0b\xf9\x8b\xcf\xbf\ +\xae\x79\xa9\xf3\x72\xef\xab\xa9\xaf\x3a\xc7\x23\xc7\x1f\xbc\xce\ +\x79\x3d\xf1\xa6\xfc\xad\xce\xdb\x7d\xef\xb8\xef\xba\xdf\xc7\xbd\ +\x1f\x99\x28\xfc\x40\xfe\x50\xf3\xd1\xfa\x63\xc7\xa7\xd0\x4f\xf7\ +\x3e\xe7\x7c\xfe\xfc\x2f\xf7\x84\xf3\xfb\x25\xd2\x9f\x33\x00\x00\ +\x00\x20\x63\x48\x52\x4d\x00\x00\x7a\x25\x00\x00\x80\x83\x00\x00\ +\xf9\xff\x00\x00\x80\xe9\x00\x00\x75\x30\x00\x00\xea\x60\x00\x00\ +\x3a\x98\x00\x00\x17\x6f\x92\x5f\xc5\x46\x00\x00\x06\x08\x49\x44\ +\x41\x54\x78\xda\x7c\x95\x4b\x6c\x5c\x57\x1d\xc6\x7f\xe7\x75\x9f\ +\xf3\xf2\x78\xe2\x44\x4d\x62\xd7\x89\x84\x1a\xd2\xa4\x45\xb5\x54\ +\x09\x81\xe8\x43\x08\xca\x82\xd2\xa4\xca\x02\x29\xb0\xa4\x42\xa4\ +\x22\x20\x9e\xad\x40\x88\x45\xc5\xa3\x2d\xed\x02\x16\x20\x0a\x62\ +\xd1\x96\xd7\xa2\x20\x55\x2a\x05\x91\x8a\xaa\x09\x11\x28\x71\x5c\ +\xda\x38\x49\x15\xc7\xf6\x78\xc6\xf6\x4c\x6c\x8f\x67\xe6\xce\xe3\ +\xdc\xc3\x62\xec\x49\x4a\x24\x8e\x74\x74\xcf\xe2\x1e\x7d\xff\xff\ +\xf7\x9d\xef\xfb\x0b\xe7\x1c\xd6\x5a\xb6\x97\x73\x0e\x21\xc4\xf0\ +\xbc\xbd\xa4\x94\xd4\x6a\xb5\x7b\x2f\x5e\xbc\x74\xa4\xdf\xb7\xf7\ +\x16\x0a\xf9\x95\xc2\x48\xfe\x4c\x69\x47\xe9\x2f\x71\x14\x9d\x4f\ +\x6d\x7a\xf3\xff\x02\x70\x5a\x6b\xc4\xff\x01\x10\x6e\xeb\x86\x52\ +\x8a\xd5\xd5\xda\x43\xd3\xd3\x17\xfe\xb0\xd9\x6c\x86\xa3\xa3\x45\ +\x46\x4b\xa3\x64\x32\x31\x4a\xca\xb6\x56\xfa\xe7\x61\x18\x3e\x1b\ +\x45\xd1\x55\x80\x34\x75\x80\xc3\x18\x83\xe4\xd6\x25\x00\x0d\x48\ +\x21\x04\x42\x08\x9c\x73\x66\xa9\x52\x79\xb2\x6f\xd3\x70\x6c\x6c\ +\x27\x85\x42\x81\x30\x0a\x08\xc2\x90\x52\x69\x47\x58\x2c\x16\x1f\ +\xef\x74\x3a\x67\x6b\xb5\xda\x67\x06\x77\x90\x5b\x24\xbc\x1f\xc0\ +\x39\xa7\x81\xdd\x80\x03\xec\x76\xf5\x57\xe7\xae\x7d\x77\x7e\xa1\ +\xfc\xe1\x4c\x26\x26\x8a\x42\x82\x20\x40\x0a\x09\xce\x61\xfb\x7d\ +\xa4\x94\x94\x4a\xa5\x52\x10\x04\x7f\x5c\x5a\x5a\xfa\xb1\x94\x32\ +\x55\x52\xde\x02\x20\x84\x10\xbe\x10\xa2\x01\x58\xe7\x1c\x4a\x29\ +\x56\x56\x6b\x1f\x7b\xf7\xdd\xd9\xaf\x47\x61\x40\x2e\x97\x23\x8c\ +\x7c\x8c\xa7\xf1\x3c\x0f\xdf\xf7\xd1\x5a\x03\xd0\xef\xf7\xc9\x66\ +\xb3\xe2\xda\xc2\xc2\x57\x67\x2f\x5d\xfe\xb2\x90\xea\xd6\x0e\x80\ +\x16\xb0\xbe\xad\x43\x92\x24\xd9\x99\x0b\x33\xcf\x4b\x29\x4c\x26\ +\x93\x41\x48\x30\xc6\x60\x8c\x19\x00\x78\x1e\x4a\x29\x9c\x73\xf4\ +\x7a\x3d\xae\xce\xcd\x91\x24\x09\x57\xae\xbc\xf7\x44\xbb\x9d\x8c\ +\xff\x2f\x80\xdb\xda\x6c\x53\x73\xf9\xca\x7b\xdf\xdb\x68\x34\x0e\ +\xe7\xf3\x79\x7c\xdf\xc7\xf7\xbd\x21\x80\xef\xf9\x48\xa9\x10\x42\ +\x60\xad\xa5\xd9\x6a\x52\xa9\x56\xe8\xf7\x2d\x42\xc8\x52\xb9\x5c\ +\x7e\x90\x2d\x31\xdf\xaf\xb0\x10\x03\x6a\x56\x56\x3f\x79\xf9\xf2\ +\x95\x93\xc5\x62\x91\x6c\x36\x8b\xef\xfb\x78\x9e\x46\x69\x85\xe7\ +\x79\x18\xad\x50\x42\x90\xa6\x29\x9d\x4e\x87\xea\xf2\x32\xad\x56\ +\x82\x40\x90\xc9\xc4\x34\x1a\x9b\x13\x43\x80\xed\x77\xbf\xcd\x65\ +\xa7\xdb\xfb\xe8\xcc\xcc\xdb\xbf\x31\xc6\x90\xcf\xe7\x51\x5a\xa1\ +\x8d\x1c\x56\xee\x1b\x83\x94\x0a\x84\xa0\xdb\xeb\x52\xab\xd7\x58\ +\x5e\x5e\x01\x37\xe8\x3c\x8a\x22\x4a\xa5\xd2\xfc\x10\xa0\x52\xad\ +\xde\xc4\x93\xf3\xae\xcd\x2d\x3c\xb1\xbe\xb1\x59\xda\xb3\xe7\x36\ +\x3c\x33\xe0\xda\x78\x06\xcf\x18\x7c\xe3\xa1\x94\x46\x0a\x81\x4d\ +\x53\x5a\xcd\x16\x95\xca\x32\xa9\x1d\xf8\x49\x6b\xc5\x8e\xb1\xd2\ +\xc2\xae\x5d\x63\xaf\x0d\x01\xea\xf5\xf5\x21\x3d\xbd\x7e\xf7\x47\ +\xd5\x6a\xf5\x13\xc5\x62\x91\x38\x8a\x30\x9e\x46\x1b\x85\xd1\x06\ +\x63\x3c\xb4\xd2\x03\x6f\x00\xbd\x6e\x97\x6a\xb5\xca\x46\x63\x03\ +\x81\x44\x6b\xcd\x68\x69\x94\x42\x21\xff\x45\x07\xf3\x43\x91\x53\ +\xdb\x27\xb5\x7d\x04\x6e\xaa\xb6\x5a\x7b\x2c\x08\x02\xf2\xb9\x3c\ +\x42\x49\xb4\x51\x68\xad\xd1\xc6\x60\x8c\x46\x2a\x39\x14\x76\x6d\ +\x7d\x8d\x6a\x75\x19\xdc\x40\x8b\x28\x8a\x30\xda\xbc\xd1\x6e\xb7\ +\xff\x54\xaf\xd7\x19\x76\xe0\x84\x23\x0a\x43\xba\x9d\xee\xb7\x84\ +\xc0\x1b\x1f\xdf\xcb\xc8\x48\x01\x21\x05\xce\x0d\x2c\xef\x69\x83\ +\x54\x6a\xa8\x57\xd2\x49\x58\xaa\x54\x48\x92\x36\x42\x48\xc2\x30\ +\x42\x4a\xd9\x69\x34\x36\xbe\xed\x48\xb1\xd6\xb2\x73\x6c\x6c\x00\ +\x10\xfa\x21\xef\xfc\x67\xf6\x9b\xa7\x4e\x9d\x3a\x62\xd3\x94\x89\ +\xf1\xbd\x1c\xba\xeb\x4e\xa6\xa6\xee\x21\x97\xcd\xe1\xd2\x41\x90\ +\x39\xe7\x10\x08\x7a\xbd\x1e\x2b\xb5\x1a\x2b\xcb\xab\x80\xc4\xf7\ +\x43\xf2\xf9\x3c\x1b\x8d\xf5\x9f\xa4\xa9\x7d\xb3\xb1\xd9\x20\x0c\ +\xc3\x1b\x1d\x9c\x3b\x77\xfe\xc4\x33\x4f\x3f\xf7\xd4\xda\xda\x75\ +\xda\x49\x9b\x24\x49\x30\xc6\xe3\x8e\x03\x77\xf0\xf9\xcf\x1d\xe7\ +\xd8\xb1\xa3\xf8\xbe\x07\x40\xaf\xd7\x63\xa3\xb1\xc1\xfc\xb5\x79\ +\xba\xdd\x1e\x41\x10\x10\xc7\x11\x9d\x4e\xfb\x4a\xbb\xdd\x7a\x36\ +\x4d\x53\xb2\xd9\x2c\x93\x93\x93\x37\x9c\x7c\x69\xf6\xd2\xc7\xfb\ +\xfd\x1e\xd9\x6c\x96\x5c\x36\x47\xa1\x50\xc0\x78\x86\xe9\xe9\x69\ +\xbe\x74\xe2\x71\x8e\x1c\x3d\xc6\xf9\xe9\x0b\x83\x8a\xb4\xa6\x5a\ +\x5d\x66\xed\xfa\x1a\x9e\xe7\x91\xcb\xe5\xf0\x03\x8f\xcd\x66\xf3\ +\x07\x40\x35\x08\x02\x26\x26\x26\x50\xea\xa6\xa8\xc8\xe5\xf2\x67\ +\xb4\xd6\x43\x97\x9a\xad\xe7\x98\xcb\xe5\x18\x2d\x16\x39\x73\xe6\ +\x2c\x8f\x3c\xf2\x28\x2f\xbe\xf8\x32\xed\x24\x61\xb3\xb9\x49\x10\ +\x84\x64\xb2\x59\xa2\x38\xa0\xb9\xd9\x7c\xc3\xda\xf4\x17\x42\x28\ +\x76\xef\xde\x43\x1c\x47\xa4\x69\x7a\x83\xa2\xf1\x89\xf1\x5f\xef\ +\xde\x7d\xdb\x89\xc5\xc5\xf2\x4e\xdf\xf7\x71\xce\x21\x85\x44\x59\ +\x4b\xaa\x52\x4a\xa3\x1e\x49\xb7\xcb\xc9\xaf\x7c\x8d\x73\xd3\xe7\ +\x79\xf8\xe1\x4f\x6f\x51\x13\x62\xad\x65\xd7\xae\xb7\x7e\xb7\x6f\ +\x72\xfa\xdf\xb0\x7e\x37\xd4\x81\xeb\xab\x50\xfe\x8e\x73\x9b\x3f\ +\x93\x00\x71\x36\x5e\xb8\xef\xfe\xfb\x9e\xda\x6e\x6b\x10\x0b\xde\ +\xf0\x6b\xb4\x47\x26\x8a\xc9\xe7\x72\xfc\xf6\xa5\xdf\xf3\xcf\xd3\ +\x67\xc9\xe6\x32\x84\x61\x40\xbb\xdd\x7e\x6d\xdf\xe4\x9b\x27\x61\ +\xee\x6e\x38\x0f\xbc\x03\xcc\x97\xac\x5d\xff\xe9\xda\x5a\xf2\x90\ +\xdc\x1a\x04\x1c\x38\x78\xe0\xb9\x07\x1e\xb8\xff\x19\xb5\x15\x60\ +\x41\x10\xdc\xb4\x7d\x7c\xcf\x23\x0e\x63\x02\x3f\xe0\x95\x57\xfe\ +\x8c\xd6\x9a\x34\x4d\xbb\xfd\x7e\xff\xfb\xb0\xb8\x0f\x2e\x02\x57\ +\x81\x65\xd2\xf4\x3a\xab\xab\x3d\x16\x17\xf9\xac\x1e\x8e\x49\xe0\ +\x9e\xa9\xa9\x27\x41\xc4\xa7\x4f\x9f\xfe\x42\xab\xd5\x1a\x68\xe1\ +\xfb\x58\x6b\x49\xad\xc5\xa6\x29\xc6\x33\xac\xae\xd4\x98\x99\x9e\ +\xe1\xe0\xa1\x0f\xfe\xca\x18\xf3\x0f\x98\x03\xae\x03\x03\xde\x93\ +\x04\x96\x96\x60\x61\x81\xcc\x20\x4d\x9d\xc0\x39\x88\xc2\xb0\x7d\ +\xe8\xd0\x9d\x8f\xc5\x99\xf8\xef\x33\x33\x33\x3f\xac\x56\xaa\x7b\ +\xfb\xfd\x3e\x9e\xe7\x0d\xdd\x3b\x88\x14\xc9\x62\xb9\xdc\xb8\xeb\ +\x43\x87\x9f\x8f\xe3\x98\x4e\xa7\xd2\xee\x76\x5d\x28\x04\x58\x0b\ +\x0b\x0b\x30\x3b\x0b\xcd\x26\x7f\xd5\xc3\x49\x00\x28\x25\x65\x9c\ +\x89\xdd\xed\x93\xb7\xbf\x34\x52\x1c\x79\x75\x71\x71\xf1\x68\xb9\ +\x5c\x3e\x5e\xab\xd5\x3f\xd2\x6a\xb5\x34\x42\x80\x73\x4c\xee\x9f\ +\x6c\x1d\x3e\x7c\xe8\xa4\x10\xe2\x6d\x80\x4a\xc5\x7d\xa3\x56\xe3\ +\xe9\x24\xc1\x34\x1a\x03\x80\xf5\x75\x5e\x8e\x22\x5e\x10\xce\x39\ +\x5e\x7f\xfd\x6f\xc3\x39\xe3\x9c\x63\x7d\x63\xd3\x5a\x6b\x49\x92\ +\x84\x7a\xbd\x4e\xa7\xd3\x9d\x52\x4a\x7e\x4a\x48\x3e\x50\xc8\x17\ +\xd6\xf6\xef\xdf\xf7\x42\x61\x24\xff\x2f\xa9\x04\x5a\x69\x7c\x73\ +\x90\xb9\x39\x1e\x2c\x97\x39\x5e\xaf\x93\x6f\xb7\x79\xd5\x18\x7e\ +\x19\x45\xf4\xff\x3b\x00\x34\xc0\xa8\x5e\x05\x6f\x81\xc4\x00\x00\ +\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x02\x24\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ +\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ +\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ +\x79\x71\xc9\x65\x3c\x00\x00\x01\xc6\x49\x44\x41\x54\x78\xda\x8c\ +\x53\x3d\x4b\x03\x41\x10\x7d\xd9\x3b\xf5\x44\x45\x8b\x44\x34\xa2\ +\xc4\x42\x49\x2b\xe8\xcf\xb0\xf3\x07\x28\xd6\x82\x9d\x20\x58\x88\ +\x0a\x36\x82\x8d\x95\xa0\x58\x0b\x16\xda\x8b\xd8\x08\x09\x44\x0b\ +\x8b\x54\x22\x7e\x44\x10\x41\x42\xce\xe4\x2e\xb7\x1f\xce\x9c\xe6\ +\xcb\x5c\xc0\xe5\x06\x6e\xdf\xbe\xf7\x66\x76\x76\x37\x96\xdd\x05\ +\x62\xc0\x12\x80\x14\xfe\x3f\x1e\x0d\x70\x3c\xbb\x66\x60\x1b\xfa\ +\xa3\x6f\x72\x6e\xf5\x62\x03\x5a\x03\x4a\x75\x96\x59\x16\x20\x04\ +\xb2\xfb\xf3\x5b\x35\x48\x84\x06\x06\xe2\x25\x93\x01\x1b\x18\x29\ +\x61\xaa\x7e\x7b\x10\xce\xeb\xcc\x63\x3e\xeb\x42\x03\xc5\x49\x35\ +\x44\x6f\x3c\x8e\xfb\xcb\x4b\xca\x22\x60\x44\x7b\x30\xce\xeb\xcc\ +\x63\x3e\xeb\x78\xd8\xfa\xc7\xc9\x1a\x1a\x4e\xa0\xe2\x96\x70\x73\ +\x7e\x51\xaf\xd8\xf3\x3c\x38\x8e\x53\x9f\x4f\x4c\x4f\x81\x79\xa4\ +\xb1\x6a\x98\xfd\xeb\x24\x0c\xed\x7d\x38\x39\x1a\x46\x08\x74\x75\ +\xe3\x29\x9f\xc7\x44\x3a\x0d\x1d\x54\xeb\x26\xcc\xe3\x0a\xfe\x1a\ +\x58\x5a\x05\x50\x32\x68\x34\x4c\xc4\x30\xd0\xd7\x87\x28\x9c\x34\ +\x56\xbb\x81\x54\xd0\xdc\xa8\xdf\x11\x13\x16\x1d\x08\x63\x11\x78\ +\x94\x81\x51\x92\xb2\x35\x88\x42\x59\x90\x94\x39\x0a\xef\x50\x41\ +\x00\xdd\x54\xaa\x1f\x28\x2c\xf6\x6c\xa2\xfa\xa6\xa8\x99\x92\x22\ +\x80\xef\x2b\x64\xa6\x8f\x5a\x0d\xa4\xaa\x19\x48\xda\x6b\x23\x53\ +\xd9\xf5\x70\x32\x53\x6e\xba\x45\x22\x0c\xf7\xae\x04\xd2\x44\x54\ +\x10\x96\xda\xa8\xc0\xfd\x2c\xc2\xae\x54\x90\xcb\xe5\x90\x48\x24\ +\xc2\x7e\xa4\x52\x29\xe8\x62\xa9\x53\x0f\xa8\x59\x4d\xd7\xd8\x25\ +\x62\x77\xb9\x8c\x34\x1d\x63\xbd\x2a\x9a\xeb\xd2\x57\xab\xc1\xdd\ +\x23\x90\x4e\xc2\x79\x79\x7a\xa5\x9b\xaa\x9a\x7a\xe0\xe3\xe3\x74\ +\xa5\xed\x39\x0c\xc6\x87\xe0\x55\xe1\xe4\x0b\xc0\x02\x1b\xec\x9c\ +\x61\xf0\x60\x19\xfd\xe3\xe3\xc9\xd6\xf3\x1e\x1b\x89\x7e\x4f\x76\ +\x17\x6e\xaf\xd1\xcf\xba\x6d\xa0\x68\xb3\xe9\xfd\x33\x0a\x87\x7b\ +\xeb\x57\xff\x7d\xcb\x0f\xef\x28\xb0\x8e\xa2\xf8\x2d\xc0\x00\x14\ +\x2c\x1a\x71\xde\xeb\xd2\x54\x00\x00\x00\x00\x49\x45\x4e\x44\xae\ +\x42\x60\x82\ +\x00\x00\x0c\xdd\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\ +\x01\x00\x9a\x9c\x18\x00\x00\x0a\x4f\x69\x43\x43\x50\x50\x68\x6f\ +\x74\x6f\x73\x68\x6f\x70\x20\x49\x43\x43\x20\x70\x72\x6f\x66\x69\ +\x6c\x65\x00\x00\x78\xda\x9d\x53\x67\x54\x53\xe9\x16\x3d\xf7\xde\ +\xf4\x42\x4b\x88\x80\x94\x4b\x6f\x52\x15\x08\x20\x52\x42\x8b\x80\ +\x14\x91\x26\x2a\x21\x09\x10\x4a\x88\x21\xa1\xd9\x15\x51\xc1\x11\ +\x45\x45\x04\x1b\xc8\xa0\x88\x03\x8e\x8e\x80\x8c\x15\x51\x2c\x0c\ +\x8a\x0a\xd8\x07\xe4\x21\xa2\x8e\x83\xa3\x88\x8a\xca\xfb\xe1\x7b\ +\xa3\x6b\xd6\xbc\xf7\xe6\xcd\xfe\xb5\xd7\x3e\xe7\xac\xf3\x9d\xb3\ +\xcf\x07\xc0\x08\x0c\x96\x48\x33\x51\x35\x80\x0c\xa9\x42\x1e\x11\ +\xe0\x83\xc7\xc4\xc6\xe1\xe4\x2e\x40\x81\x0a\x24\x70\x00\x10\x08\ +\xb3\x64\x21\x73\xfd\x23\x01\x00\xf8\x7e\x3c\x3c\x2b\x22\xc0\x07\ +\xbe\x00\x01\x78\xd3\x0b\x08\x00\xc0\x4d\x9b\xc0\x30\x1c\x87\xff\ +\x0f\xea\x42\x99\x5c\x01\x80\x84\x01\xc0\x74\x91\x38\x4b\x08\x80\ +\x14\x00\x40\x7a\x8e\x42\xa6\x00\x40\x46\x01\x80\x9d\x98\x26\x53\ +\x00\xa0\x04\x00\x60\xcb\x63\x62\xe3\x00\x50\x2d\x00\x60\x27\x7f\ +\xe6\xd3\x00\x80\x9d\xf8\x99\x7b\x01\x00\x5b\x94\x21\x15\x01\xa0\ +\x91\x00\x20\x13\x65\x88\x44\x00\x68\x3b\x00\xac\xcf\x56\x8a\x45\ +\x00\x58\x30\x00\x14\x66\x4b\xc4\x39\x00\xd8\x2d\x00\x30\x49\x57\ +\x66\x48\x00\xb0\xb7\x00\xc0\xce\x10\x0b\xb2\x00\x08\x0c\x00\x30\ +\x51\x88\x85\x29\x00\x04\x7b\x00\x60\xc8\x23\x23\x78\x00\x84\x99\ +\x00\x14\x46\xf2\x57\x3c\xf1\x2b\xae\x10\xe7\x2a\x00\x00\x78\x99\ +\xb2\x3c\xb9\x24\x39\x45\x81\x5b\x08\x2d\x71\x07\x57\x57\x2e\x1e\ +\x28\xce\x49\x17\x2b\x14\x36\x61\x02\x61\x9a\x40\x2e\xc2\x79\x99\ +\x19\x32\x81\x34\x0f\xe0\xf3\xcc\x00\x00\xa0\x91\x15\x11\xe0\x83\ +\xf3\xfd\x78\xce\x0e\xae\xce\xce\x36\x8e\xb6\x0e\x5f\x2d\xea\xbf\ +\x06\xff\x22\x62\x62\xe3\xfe\xe5\xcf\xab\x70\x40\x00\x00\xe1\x74\ +\x7e\xd1\xfe\x2c\x2f\xb3\x1a\x80\x3b\x06\x80\x6d\xfe\xa2\x25\xee\ +\x04\x68\x5e\x0b\xa0\x75\xf7\x8b\x66\xb2\x0f\x40\xb5\x00\xa0\xe9\ +\xda\x57\xf3\x70\xf8\x7e\x3c\x3c\x45\xa1\x90\xb9\xd9\xd9\xe5\xe4\ +\xe4\xd8\x4a\xc4\x42\x5b\x61\xca\x57\x7d\xfe\x67\xc2\x5f\xc0\x57\ +\xfd\x6c\xf9\x7e\x3c\xfc\xf7\xf5\xe0\xbe\xe2\x24\x81\x32\x5d\x81\ +\x47\x04\xf8\xe0\xc2\xcc\xf4\x4c\xa5\x1c\xcf\x92\x09\x84\x62\xdc\ +\xe6\x8f\x47\xfc\xb7\x0b\xff\xfc\x1d\xd3\x22\xc4\x49\x62\xb9\x58\ +\x2a\x14\xe3\x51\x12\x71\x8e\x44\x9a\x8c\xf3\x32\xa5\x22\x89\x42\ +\x92\x29\xc5\x25\xd2\xff\x64\xe2\xdf\x2c\xfb\x03\x3e\xdf\x35\x00\ +\xb0\x6a\x3e\x01\x7b\x91\x2d\xa8\x5d\x63\x03\xf6\x4b\x27\x10\x58\ +\x74\xc0\xe2\xf7\x00\x00\xf2\xbb\x6f\xc1\xd4\x28\x08\x03\x80\x68\ +\x83\xe1\xcf\x77\xff\xef\x3f\xfd\x47\xa0\x25\x00\x80\x66\x49\x92\ +\x71\x00\x00\x5e\x44\x24\x2e\x54\xca\xb3\x3f\xc7\x08\x00\x00\x44\ +\xa0\x81\x2a\xb0\x41\x1b\xf4\xc1\x18\x2c\xc0\x06\x1c\xc1\x05\xdc\ +\xc1\x0b\xfc\x60\x36\x84\x42\x24\xc4\xc2\x42\x10\x42\x0a\x64\x80\ +\x1c\x72\x60\x29\xac\x82\x42\x28\x86\xcd\xb0\x1d\x2a\x60\x2f\xd4\ +\x40\x1d\x34\xc0\x51\x68\x86\x93\x70\x0e\x2e\xc2\x55\xb8\x0e\x3d\ +\x70\x0f\xfa\x61\x08\x9e\xc1\x28\xbc\x81\x09\x04\x41\xc8\x08\x13\ +\x61\x21\xda\x88\x01\x62\x8a\x58\x23\x8e\x08\x17\x99\x85\xf8\x21\ +\xc1\x48\x04\x12\x8b\x24\x20\xc9\x88\x14\x51\x22\x4b\x91\x35\x48\ +\x31\x52\x8a\x54\x20\x55\x48\x1d\xf2\x3d\x72\x02\x39\x87\x5c\x46\ +\xba\x91\x3b\xc8\x00\x32\x82\xfc\x86\xbc\x47\x31\x94\x81\xb2\x51\ +\x3d\xd4\x0c\xb5\x43\xb9\xa8\x37\x1a\x84\x46\xa2\x0b\xd0\x64\x74\ +\x31\x9a\x8f\x16\xa0\x9b\xd0\x72\xb4\x1a\x3d\x8c\x36\xa1\xe7\xd0\ +\xab\x68\x0f\xda\x8f\x3e\x43\xc7\x30\xc0\xe8\x18\x07\x33\xc4\x6c\ +\x30\x2e\xc6\xc3\x42\xb1\x38\x2c\x09\x93\x63\xcb\xb1\x22\xac\x0c\ +\xab\xc6\x1a\xb0\x56\xac\x03\xbb\x89\xf5\x63\xcf\xb1\x77\x04\x12\ +\x81\x45\xc0\x09\x36\x04\x77\x42\x20\x61\x1e\x41\x48\x58\x4c\x58\ +\x4e\xd8\x48\xa8\x20\x1c\x24\x34\x11\xda\x09\x37\x09\x03\x84\x51\ +\xc2\x27\x22\x93\xa8\x4b\xb4\x26\xba\x11\xf9\xc4\x18\x62\x32\x31\ +\x87\x58\x48\x2c\x23\xd6\x12\x8f\x13\x2f\x10\x7b\x88\x43\xc4\x37\ +\x24\x12\x89\x43\x32\x27\xb9\x90\x02\x49\xb1\xa4\x54\xd2\x12\xd2\ +\x46\xd2\x6e\x52\x23\xe9\x2c\xa9\x9b\x34\x48\x1a\x23\x93\xc9\xda\ +\x64\x6b\xb2\x07\x39\x94\x2c\x20\x2b\xc8\x85\xe4\x9d\xe4\xc3\xe4\ +\x33\xe4\x1b\xe4\x21\xf2\x5b\x0a\x9d\x62\x40\x71\xa4\xf8\x53\xe2\ +\x28\x52\xca\x6a\x4a\x19\xe5\x10\xe5\x34\xe5\x06\x65\x98\x32\x41\ +\x55\xa3\x9a\x52\xdd\xa8\xa1\x54\x11\x35\x8f\x5a\x42\xad\xa1\xb6\ +\x52\xaf\x51\x87\xa8\x13\x34\x75\x9a\x39\xcd\x83\x16\x49\x4b\xa5\ +\xad\xa2\x95\xd3\x1a\x68\x17\x68\xf7\x69\xaf\xe8\x74\xba\x11\xdd\ +\x95\x1e\x4e\x97\xd0\x57\xd2\xcb\xe9\x47\xe8\x97\xe8\x03\xf4\x77\ +\x0c\x0d\x86\x15\x83\xc7\x88\x67\x28\x19\x9b\x18\x07\x18\x67\x19\ +\x77\x18\xaf\x98\x4c\xa6\x19\xd3\x8b\x19\xc7\x54\x30\x37\x31\xeb\ +\x98\xe7\x99\x0f\x99\x6f\x55\x58\x2a\xb6\x2a\x7c\x15\x91\xca\x0a\ +\x95\x4a\x95\x26\x95\x1b\x2a\x2f\x54\xa9\xaa\xa6\xaa\xde\xaa\x0b\ +\x55\xf3\x55\xcb\x54\x8f\xa9\x5e\x53\x7d\xae\x46\x55\x33\x53\xe3\ +\xa9\x09\xd4\x96\xab\x55\xaa\x9d\x50\xeb\x53\x1b\x53\x67\xa9\x3b\ +\xa8\x87\xaa\x67\xa8\x6f\x54\x3f\xa4\x7e\x59\xfd\x89\x06\x59\xc3\ +\x4c\xc3\x4f\x43\xa4\x51\xa0\xb1\x5f\xe3\xbc\xc6\x20\x0b\x63\x19\ +\xb3\x78\x2c\x21\x6b\x0d\xab\x86\x75\x81\x35\xc4\x26\xb1\xcd\xd9\ +\x7c\x76\x2a\xbb\x98\xfd\x1d\xbb\x8b\x3d\xaa\xa9\xa1\x39\x43\x33\ +\x4a\x33\x57\xb3\x52\xf3\x94\x66\x3f\x07\xe3\x98\x71\xf8\x9c\x74\ +\x4e\x09\xe7\x28\xa7\x97\xf3\x7e\x8a\xde\x14\xef\x29\xe2\x29\x1b\ +\xa6\x34\x4c\xb9\x31\x65\x5c\x6b\xaa\x96\x97\x96\x58\xab\x48\xab\ +\x51\xab\x47\xeb\xbd\x36\xae\xed\xa7\x9d\xa6\xbd\x45\xbb\x59\xfb\ +\x81\x0e\x41\xc7\x4a\x27\x5c\x27\x47\x67\x8f\xce\x05\x9d\xe7\x53\ +\xd9\x53\xdd\xa7\x0a\xa7\x16\x4d\x3d\x3a\xf5\xae\x2e\xaa\x6b\xa5\ +\x1b\xa1\xbb\x44\x77\xbf\x6e\xa7\xee\x98\x9e\xbe\x5e\x80\x9e\x4c\ +\x6f\xa7\xde\x79\xbd\xe7\xfa\x1c\x7d\x2f\xfd\x54\xfd\x6d\xfa\xa7\ +\xf5\x47\x0c\x58\x06\xb3\x0c\x24\x06\xdb\x0c\xce\x18\x3c\xc5\x35\ +\x71\x6f\x3c\x1d\x2f\xc7\xdb\xf1\x51\x43\x5d\xc3\x40\x43\xa5\x61\ +\x95\x61\x97\xe1\x84\x91\xb9\xd1\x3c\xa3\xd5\x46\x8d\x46\x0f\x8c\ +\x69\xc6\x5c\xe3\x24\xe3\x6d\xc6\x6d\xc6\xa3\x26\x06\x26\x21\x26\ +\x4b\x4d\xea\x4d\xee\x9a\x52\x4d\xb9\xa6\x29\xa6\x3b\x4c\x3b\x4c\ +\xc7\xcd\xcc\xcd\xa2\xcd\xd6\x99\x35\x9b\x3d\x31\xd7\x32\xe7\x9b\ +\xe7\x9b\xd7\x9b\xdf\xb7\x60\x5a\x78\x5a\x2c\xb6\xa8\xb6\xb8\x65\ +\x49\xb2\xe4\x5a\xa6\x59\xee\xb6\xbc\x6e\x85\x5a\x39\x59\xa5\x58\ +\x55\x5a\x5d\xb3\x46\xad\x9d\xad\x25\xd6\xbb\xad\xbb\xa7\x11\xa7\ +\xb9\x4e\x93\x4e\xab\x9e\xd6\x67\xc3\xb0\xf1\xb6\xc9\xb6\xa9\xb7\ +\x19\xb0\xe5\xd8\x06\xdb\xae\xb6\x6d\xb6\x7d\x61\x67\x62\x17\x67\ +\xb7\xc5\xae\xc3\xee\x93\xbd\x93\x7d\xba\x7d\x8d\xfd\x3d\x07\x0d\ +\x87\xd9\x0e\xab\x1d\x5a\x1d\x7e\x73\xb4\x72\x14\x3a\x56\x3a\xde\ +\x9a\xce\x9c\xee\x3f\x7d\xc5\xf4\x96\xe9\x2f\x67\x58\xcf\x10\xcf\ +\xd8\x33\xe3\xb6\x13\xcb\x29\xc4\x69\x9d\x53\x9b\xd3\x47\x67\x17\ +\x67\xb9\x73\x83\xf3\x88\x8b\x89\x4b\x82\xcb\x2e\x97\x3e\x2e\x9b\ +\x1b\xc6\xdd\xc8\xbd\xe4\x4a\x74\xf5\x71\x5d\xe1\x7a\xd2\xf5\x9d\ +\x9b\xb3\x9b\xc2\xed\xa8\xdb\xaf\xee\x36\xee\x69\xee\x87\xdc\x9f\ +\xcc\x34\x9f\x29\x9e\x59\x33\x73\xd0\xc3\xc8\x43\xe0\x51\xe5\xd1\ +\x3f\x0b\x9f\x95\x30\x6b\xdf\xac\x7e\x4f\x43\x4f\x81\x67\xb5\xe7\ +\x23\x2f\x63\x2f\x91\x57\xad\xd7\xb0\xb7\xa5\x77\xaa\xf7\x61\xef\ +\x17\x3e\xf6\x3e\x72\x9f\xe3\x3e\xe3\x3c\x37\xde\x32\xde\x59\x5f\ +\xcc\x37\xc0\xb7\xc8\xb7\xcb\x4f\xc3\x6f\x9e\x5f\x85\xdf\x43\x7f\ +\x23\xff\x64\xff\x7a\xff\xd1\x00\xa7\x80\x25\x01\x67\x03\x89\x81\ +\x41\x81\x5b\x02\xfb\xf8\x7a\x7c\x21\xbf\x8e\x3f\x3a\xdb\x65\xf6\ +\xb2\xd9\xed\x41\x8c\xa0\xb9\x41\x15\x41\x8f\x82\xad\x82\xe5\xc1\ +\xad\x21\x68\xc8\xec\x90\xad\x21\xf7\xe7\x98\xce\x91\xce\x69\x0e\ +\x85\x50\x7e\xe8\xd6\xd0\x07\x61\xe6\x61\x8b\xc3\x7e\x0c\x27\x85\ +\x87\x85\x57\x86\x3f\x8e\x70\x88\x58\x1a\xd1\x31\x97\x35\x77\xd1\ +\xdc\x43\x73\xdf\x44\xfa\x44\x96\x44\xde\x9b\x67\x31\x4f\x39\xaf\ +\x2d\x4a\x35\x2a\x3e\xaa\x2e\x6a\x3c\xda\x37\xba\x34\xba\x3f\xc6\ +\x2e\x66\x59\xcc\xd5\x58\x9d\x58\x49\x6c\x4b\x1c\x39\x2e\x2a\xae\ +\x36\x6e\x6c\xbe\xdf\xfc\xed\xf3\x87\xe2\x9d\xe2\x0b\xe3\x7b\x17\ +\x98\x2f\xc8\x5d\x70\x79\xa1\xce\xc2\xf4\x85\xa7\x16\xa9\x2e\x12\ +\x2c\x3a\x96\x40\x4c\x88\x4e\x38\x94\xf0\x41\x10\x2a\xa8\x16\x8c\ +\x25\xf2\x13\x77\x25\x8e\x0a\x79\xc2\x1d\xc2\x67\x22\x2f\xd1\x36\ +\xd1\x88\xd8\x43\x5c\x2a\x1e\x4e\xf2\x48\x2a\x4d\x7a\x92\xec\x91\ +\xbc\x35\x79\x24\xc5\x33\xa5\x2c\xe5\xb9\x84\x27\xa9\x90\xbc\x4c\ +\x0d\x4c\xdd\x9b\x3a\x9e\x16\x9a\x76\x20\x6d\x32\x3d\x3a\xbd\x31\ +\x83\x92\x91\x90\x71\x42\xaa\x21\x4d\x93\xb6\x67\xea\x67\xe6\x66\ +\x76\xcb\xac\x65\x85\xb2\xfe\xc5\x6e\x8b\xb7\x2f\x1e\x95\x07\xc9\ +\x6b\xb3\x90\xac\x05\x59\x2d\x0a\xb6\x42\xa6\xe8\x54\x5a\x28\xd7\ +\x2a\x07\xb2\x67\x65\x57\x66\xbf\xcd\x89\xca\x39\x96\xab\x9e\x2b\ +\xcd\xed\xcc\xb3\xca\xdb\x90\x37\x9c\xef\x9f\xff\xed\x12\xc2\x12\ +\xe1\x92\xb6\xa5\x86\x4b\x57\x2d\x1d\x58\xe6\xbd\xac\x6a\x39\xb2\ +\x3c\x71\x79\xdb\x0a\xe3\x15\x05\x2b\x86\x56\x06\xac\x3c\xb8\x8a\ +\xb6\x2a\x6d\xd5\x4f\xab\xed\x57\x97\xae\x7e\xbd\x26\x7a\x4d\x6b\ +\x81\x5e\xc1\xca\x82\xc1\xb5\x01\x6b\xeb\x0b\x55\x0a\xe5\x85\x7d\ +\xeb\xdc\xd7\xed\x5d\x4f\x58\x2f\x59\xdf\xb5\x61\xfa\x86\x9d\x1b\ +\x3e\x15\x89\x8a\xae\x14\xdb\x17\x97\x15\x7f\xd8\x28\xdc\x78\xe5\ +\x1b\x87\x6f\xca\xbf\x99\xdc\x94\xb4\xa9\xab\xc4\xb9\x64\xcf\x66\ +\xd2\x66\xe9\xe6\xde\x2d\x9e\x5b\x0e\x96\xaa\x97\xe6\x97\x0e\x6e\ +\x0d\xd9\xda\xb4\x0d\xdf\x56\xb4\xed\xf5\xf6\x45\xdb\x2f\x97\xcd\ +\x28\xdb\xbb\x83\xb6\x43\xb9\xa3\xbf\x3c\xb8\xbc\x65\xa7\xc9\xce\ +\xcd\x3b\x3f\x54\xa4\x54\xf4\x54\xfa\x54\x36\xee\xd2\xdd\xb5\x61\ +\xd7\xf8\x6e\xd1\xee\x1b\x7b\xbc\xf6\x34\xec\xd5\xdb\x5b\xbc\xf7\ +\xfd\x3e\xc9\xbe\xdb\x55\x01\x55\x4d\xd5\x66\xd5\x65\xfb\x49\xfb\ +\xb3\xf7\x3f\xae\x89\xaa\xe9\xf8\x96\xfb\x6d\x5d\xad\x4e\x6d\x71\ +\xed\xc7\x03\xd2\x03\xfd\x07\x23\x0e\xb6\xd7\xb9\xd4\xd5\x1d\xd2\ +\x3d\x54\x52\x8f\xd6\x2b\xeb\x47\x0e\xc7\x1f\xbe\xfe\x9d\xef\x77\ +\x2d\x0d\x36\x0d\x55\x8d\x9c\xc6\xe2\x23\x70\x44\x79\xe4\xe9\xf7\ +\x09\xdf\xf7\x1e\x0d\x3a\xda\x76\x8c\x7b\xac\xe1\x07\xd3\x1f\x76\ +\x1d\x67\x1d\x2f\x6a\x42\x9a\xf2\x9a\x46\x9b\x53\x9a\xfb\x5b\x62\ +\x5b\xba\x4f\xcc\x3e\xd1\xd6\xea\xde\x7a\xfc\x47\xdb\x1f\x0f\x9c\ +\x34\x3c\x59\x79\x4a\xf3\x54\xc9\x69\xda\xe9\x82\xd3\x93\x67\xf2\ +\xcf\x8c\x9d\x95\x9d\x7d\x7e\x2e\xf9\xdc\x60\xdb\xa2\xb6\x7b\xe7\ +\x63\xce\xdf\x6a\x0f\x6f\xef\xba\x10\x74\xe1\xd2\x45\xff\x8b\xe7\ +\x3b\xbc\x3b\xce\x5c\xf2\xb8\x74\xf2\xb2\xdb\xe5\x13\x57\xb8\x57\ +\x9a\xaf\x3a\x5f\x6d\xea\x74\xea\x3c\xfe\x93\xd3\x4f\xc7\xbb\x9c\ +\xbb\x9a\xae\xb9\x5c\x6b\xb9\xee\x7a\xbd\xb5\x7b\x66\xf7\xe9\x1b\ +\x9e\x37\xce\xdd\xf4\xbd\x79\xf1\x16\xff\xd6\xd5\x9e\x39\x3d\xdd\ +\xbd\xf3\x7a\x6f\xf7\xc5\xf7\xf5\xdf\x16\xdd\x7e\x72\x27\xfd\xce\ +\xcb\xbb\xd9\x77\x27\xee\xad\xbc\x4f\xbc\x5f\xf4\x40\xed\x41\xd9\ +\x43\xdd\x87\xd5\x3f\x5b\xfe\xdc\xd8\xef\xdc\x7f\x6a\xc0\x77\xa0\ +\xf3\xd1\xdc\x47\xf7\x06\x85\x83\xcf\xfe\x91\xf5\x8f\x0f\x43\x05\ +\x8f\x99\x8f\xcb\x86\x0d\x86\xeb\x9e\x38\x3e\x39\x39\xe2\x3f\x72\ +\xfd\xe9\xfc\xa7\x43\xcf\x64\xcf\x26\x9e\x17\xfe\xa2\xfe\xcb\xae\ +\x17\x16\x2f\x7e\xf8\xd5\xeb\xd7\xce\xd1\x98\xd1\xa1\x97\xf2\x97\ +\x93\xbf\x6d\x7c\xa5\xfd\xea\xc0\xeb\x19\xaf\xdb\xc6\xc2\xc6\x1e\ +\xbe\xc9\x78\x33\x31\x5e\xf4\x56\xfb\xed\xc1\x77\xdc\x77\x1d\xef\ +\xa3\xdf\x0f\x4f\xe4\x7c\x20\x7f\x28\xff\x68\xf9\xb1\xf5\x53\xd0\ +\xa7\xfb\x93\x19\x93\x93\xff\x04\x03\x98\xf3\xfc\x63\x33\x2d\xdb\ +\x00\x00\x00\x20\x63\x48\x52\x4d\x00\x00\x7a\x25\x00\x00\x80\x83\ +\x00\x00\xf9\xff\x00\x00\x80\xe9\x00\x00\x75\x30\x00\x00\xea\x60\ +\x00\x00\x3a\x98\x00\x00\x17\x6f\x92\x5f\xc5\x46\x00\x00\x02\x08\ +\x49\x44\x41\x54\x78\xda\x64\x91\x3d\x6b\x14\x41\x18\xc7\x7f\x73\ +\xb7\x2c\x6c\x0c\xb9\x20\xa6\x0d\x58\x45\x90\x88\x1f\x40\xb1\x12\ +\x2e\x8d\x82\x5f\x41\x08\x58\x05\xb4\xb5\x0c\xd8\x09\xa2\x20\x08\ +\x7e\x88\x58\x05\xac\x04\xfb\x08\x56\x42\x40\x48\x9d\xdc\xe5\xf6\ +\x66\xe7\x75\x67\xc7\xe2\x19\x72\x07\x0e\xfc\x18\x06\xe6\xff\x32\ +\xcf\xa8\x9c\x1f\x00\xb7\x81\x09\x70\x0b\xa8\xf7\x40\x1d\x42\x7a\ +\x02\xe1\x21\x78\xc0\xfe\x82\xee\x07\x74\x9f\x41\x9f\x83\x41\xf0\ +\xa8\x9c\x1f\x17\x83\x4d\xa0\x7e\x0d\xea\x18\xfa\x46\x84\xae\xe0\ +\x01\x0b\x18\x0b\xe6\x2d\x98\xf7\x72\x0e\xa8\x9c\x0f\x80\x49\x0d\ +\xf5\x09\xa8\x29\xf4\xe5\x72\x57\x76\x0f\x44\x20\xac\x19\x9a\x53\ +\x70\xcf\x21\x84\x11\xd4\x00\x1f\xa1\x9f\x4a\xad\x05\x70\x05\x5c\ +\x96\x7d\x06\x5c\x03\xcb\x62\xda\x01\x66\x9a\xb3\x79\x17\x42\x8f\ +\xca\xf9\xd9\x3e\x54\x67\x90\xc6\x92\xb8\x28\xe8\x92\x9e\x80\x5c\ +\x48\x80\x23\xa5\x88\x31\xa4\x10\xb8\x5f\x41\x38\x84\x38\x96\x6a\ +\x4b\x60\x5e\x12\x6d\xa9\x9e\x91\xa5\x80\x9e\x10\x32\xd6\x82\x31\ +\x8c\xbd\xe7\x55\x05\x66\x2a\xce\xb6\x18\x2c\xcb\x84\x03\x30\xb0\ +\xbe\x62\x14\x71\xd7\x09\xd6\xf2\xa8\x02\xbd\xfb\xff\xe0\x62\x11\ +\xe7\x1b\x71\xce\x10\x23\x78\x0f\xc6\xc0\x72\x09\xc6\xb0\x5b\x49\ +\x62\xcf\xea\xdb\xe2\xda\xbb\x57\xe2\x94\xa0\xef\xb9\x69\x50\x0c\ +\x18\xc1\xf2\x02\xda\x32\x34\x49\xcf\x39\x93\x33\x37\x0c\x83\xa4\ +\x5b\x0b\x5a\x43\xdb\x0a\x5d\xc7\xc5\x08\xda\x53\x99\x7a\x4b\x4a\ +\x96\x18\x13\x21\x48\x5a\x4a\xab\xda\x5a\xc3\xf5\x35\xcc\x66\x42\ +\xdb\x82\xb5\xfc\x54\x29\xb1\xef\x1c\x67\x31\x32\xee\x7b\x49\x04\ +\x50\x4a\xf6\x94\xc0\x39\xa9\x7c\x79\x09\x57\x57\xb0\x58\x40\x08\ +\xa4\xba\xe6\x5e\x65\x0c\xbf\xad\xe5\x93\x73\x1c\xc5\x28\xc9\xc3\ +\xb0\x12\xf7\xbd\xbc\xb5\x6d\x61\x3e\x17\xb1\xf7\x30\x1a\xf1\xa1\ +\x69\x38\x57\xb3\x19\x68\x4d\xdd\x75\x9c\x58\xcb\x34\x04\x11\xae\ +\xd7\xb7\x56\x0c\xb4\x96\x33\xf0\x6d\x63\x83\x17\x77\xee\x90\xaa\ +\x61\x80\x61\x20\xc4\xc8\x81\x73\x1c\x19\xc3\xb1\x73\x6c\x7a\x0f\ +\x21\x48\x7d\x6b\x85\x18\xd1\x4a\xf1\xa6\x69\xf8\xb2\xb5\x05\xdb\ +\xdb\xa0\xe6\x73\xf9\x96\xb6\x95\x7a\x6d\xcb\x5d\xad\x79\xa9\x35\ +\x4f\xad\x65\xcf\x7b\x88\x91\x3f\x29\xf1\x7d\x3c\xe6\x6b\xd3\xf0\ +\x77\x32\x81\x9d\x1d\xe1\xdf\x00\x3c\x20\x6c\x94\x21\xaa\x95\x54\ +\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x03\x37\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ +\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ +\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ +\x79\x71\xc9\x65\x3c\x00\x00\x02\xd9\x49\x44\x41\x54\x78\xda\x6c\ +\x93\x5d\x48\x14\x51\x14\xc7\xff\xbb\xb3\xbb\xb6\xab\x95\x5b\x46\ +\xae\x99\xba\x7e\x40\x42\x46\x12\xa5\x4b\x49\xe8\x83\x60\x22\x11\ +\x3d\x94\xf6\xd6\x43\xe9\x6b\x81\x6f\x46\xd1\x43\x54\xd0\xf6\x49\ +\xf6\x24\x44\x81\x6f\x3d\x04\x8b\x08\x9a\x60\x59\xb6\x18\x65\xe9\ +\x22\xbb\xce\xb8\xea\xfa\xb1\x8b\xdf\xe3\x3a\xbb\xb3\xce\xed\xcc\ +\x75\x35\xad\x2e\x73\xb9\x77\xe6\x9c\xf3\xbf\xbf\x73\xee\x19\xc3\ +\xad\xf6\x76\x18\x0c\x06\x3e\x35\x4d\x83\x3e\x68\x5f\x47\x8b\x03\ +\xff\x1f\xdd\xeb\x89\x44\x40\x4d\x24\xc0\x18\x83\x69\xbb\xc5\x48\ +\x22\x09\x32\xd0\xc8\x6a\xa9\xaf\x6f\x9d\x8e\x44\x61\xb7\x5b\xb8\ +\xb0\x46\xce\x43\x81\x00\x3a\x07\x07\x1b\xf5\x33\x68\xfa\x79\xcc\ +\x0e\x6d\x12\x30\x0a\x02\x74\xf5\xc8\x9c\x02\x8a\x44\x24\xb2\x86\ +\x99\xd9\x28\xa6\x66\x56\x21\xcb\x32\xee\x36\x34\xb4\x92\xbd\x8a\ +\xbc\x8b\xf4\x90\x4d\x82\x3a\xc2\x71\x24\xf1\x21\x08\x42\xc5\xe4\ +\x62\x08\xcb\xb2\x8a\xe2\x9c\x83\xc8\xb0\x5a\xa1\xae\xaf\xe3\xc7\ +\xf0\x3c\xde\x7a\x3c\x28\xc9\xc8\x68\x7d\xd2\xde\x7e\x83\xdc\xdd\ +\x26\x8d\x0c\x9b\xc8\x23\x81\x15\xe4\xe6\x59\x77\x20\x0f\x07\xa7\ +\x91\x99\xbe\x1f\xa9\x29\x36\x9c\x38\xea\x42\x82\x6c\x66\xb3\x19\ +\xe5\xc1\xa0\xc2\x09\xd4\x8d\x9c\xe1\x17\x65\x3d\x03\x04\xc7\xd6\ +\x78\x71\xf4\x7a\xea\xc8\x35\xe5\xe5\xf8\xe8\xf3\xc1\xbe\xc7\x8c\ +\x0c\xbb\x8d\x93\x08\x24\x10\x8b\xc5\x0c\x1b\x02\xaa\xca\xc9\x8b\ +\x9c\xa9\xf0\x4b\xab\x70\x1e\xb6\xf0\x53\x74\xc7\x21\x71\x03\x59\ +\x1f\x83\xbf\xfc\xa8\xad\xa8\x24\x1b\xa3\xca\xa9\x88\x93\xc0\xc9\ +\xee\x6e\x12\x88\xc7\xb9\x80\x38\x1e\x85\xd1\x68\xc0\xd8\x64\x9c\ +\x13\xd0\x83\x92\xc2\xd3\x9c\x44\x7f\x5f\x54\xc7\x71\x60\x5f\x0a\ +\xdf\xc7\x07\x06\xd0\xe8\x76\x5f\xd3\xc2\xe1\x21\x23\xa1\x70\x9c\ +\xc2\x1c\x1b\x4f\xa1\x20\x67\x17\xf2\xf9\x4c\x41\x2e\xd1\x64\x67\ +\x0b\xc8\xcb\xb7\x52\x41\xe3\x98\x5f\x4a\x60\xc4\x1f\x42\xaf\xf7\ +\x3b\xca\x9a\x9a\x8e\x45\x80\x3b\x26\x42\xe1\x04\x52\x68\x8d\xdf\ +\xc0\x58\x28\xc6\x4f\xd7\x34\xb6\x45\xc2\x98\x02\x9b\x05\xb0\xa8\ +\xfd\x08\x8e\x2e\xa3\xe6\xfa\x55\xd4\xb9\x5c\x3d\x17\x19\xbb\x67\ +\x8a\x25\x05\x0a\xb2\x6d\x18\x9d\x8c\x22\x2f\xcb\xca\x6f\x80\x17\ +\x32\xb9\x1a\xa8\x37\xc4\x2e\x2f\x7c\xa1\xf7\xa8\x39\x75\x1c\xee\ +\xa7\x12\x66\x9d\xce\xaf\xdf\x04\xa1\xd3\xa4\x28\xca\x06\xc1\x54\ +\x92\x60\x4a\xd9\xca\x7b\x93\x24\xb6\xf8\x09\xc6\xb9\x37\x28\xab\ +\x3c\x8b\x8e\x8e\x7e\x5c\xba\xd0\x82\xd7\x7d\x3d\xe1\xb6\x89\x09\ +\xfc\x21\x38\x44\x04\xa1\x28\xf2\x75\x02\x60\x8b\x60\x61\xb6\x17\ +\xe2\xec\x73\x54\x53\xf0\xc3\x47\xee\xd1\x8e\x61\xc7\x87\xa1\x97\ +\xcd\x7e\x4d\x96\x97\xe5\x70\x38\xdf\x34\x23\x8a\xd8\xeb\x70\x18\ +\x44\x22\xd0\x5b\x5c\x9a\x56\x92\x79\x33\x44\x17\x46\x11\x09\x3c\ +\xc0\x19\x57\x29\x1e\x3f\x7b\x21\x05\x25\xa5\xb9\xcf\x23\x7d\x01\ +\xa4\xcd\xe6\xd7\x83\x90\x7e\xe4\xfc\xf9\x9b\x14\xc0\x88\x40\x5f\ +\x18\x9d\xcc\xd6\x89\xfd\xb3\xe7\x36\x63\xf2\x08\x7b\xd7\x56\xc5\ +\x6a\xaf\x94\xbe\x22\x5f\xfb\xdf\xbf\xa6\xde\x4d\xb9\xbb\x8b\x8b\ +\xab\x8d\x69\x69\xff\x18\x97\xbc\xde\xfb\x97\xcf\xa5\xfe\x1c\x5e\ +\xcd\xec\x93\xc2\x96\x81\x15\x9f\xaf\x8b\x3e\x8b\xdb\x7d\x7e\x0b\ +\x30\x00\x66\x8d\xa1\xfd\x87\x65\xe6\x27\x00\x00\x00\x00\x49\x45\ +\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x02\xa1\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ +\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ +\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ +\x79\x71\xc9\x65\x3c\x00\x00\x02\x43\x49\x44\x41\x54\x78\xda\xa4\ +\x93\xcf\x6b\x13\x51\x10\xc7\xbf\xfb\x23\x6b\xd3\x4d\x63\x53\x51\ +\x69\x93\x2d\x52\x62\xc5\xb6\x54\x08\x52\x28\x28\xb4\xa8\x08\xc5\ +\x5e\x84\xfe\x01\x3d\x49\x2f\x9e\x7a\x29\x96\x9e\xf2\x07\xf4\xa4\ +\x88\x17\x7f\x1c\x82\x22\x2a\xb9\x05\x5a\xe8\x49\x94\x0a\xb1\x58\ +\x24\x97\x68\x49\xa5\x31\x35\x90\xd2\xcd\x26\xd9\xcd\x6e\xd6\x99\ +\xad\x5b\x82\xb4\x50\x71\x60\xf6\xbd\x37\xcc\x7c\xdf\x7c\xde\xdb\ +\x27\xb8\xae\x8b\xff\x31\x79\x66\x69\xe9\x70\x21\x08\xc2\x34\x0d\ +\x7d\xff\x50\xbf\x23\xf3\xd7\x69\xb5\xfc\x40\xf4\x4d\x32\xf9\xe8\ +\x24\x3d\x09\xe4\x77\x17\x17\xe7\x64\xda\x15\x92\x28\xc2\x34\x4d\ +\x8e\x8b\x0e\x21\x7d\x2e\xba\xa8\x14\xbe\x42\x8b\x0f\x63\x20\xd2\ +\x3a\x52\x40\xa4\x1a\xbb\xd9\x14\xbd\x0e\x04\x5a\x28\x8a\x82\x9a\ +\x61\x88\x36\x09\xec\x15\xbe\xa0\x98\xdf\x84\x08\x07\x5a\xe2\x32\ +\x0a\xa5\x12\x82\xc1\x20\x6c\xdb\x46\x6f\x4f\x8f\x27\x20\xd1\xc6\ +\xbe\xc0\x34\x5c\xb7\x8f\x15\x03\x8a\x72\x6d\x65\x7d\x1d\xdb\xbb\ +\x3a\x8a\xe5\x32\x6a\xe1\x5f\xa8\x7e\x32\xd0\xa0\x42\xdf\xce\x77\ +\x77\xe3\x4a\x3c\x8e\x00\xe5\x37\x2d\x4b\x94\x6d\xc7\x39\x86\xfb\ +\xe6\x91\xdc\x4f\x33\x19\x9c\x56\x55\x5c\xd0\x34\x58\x96\x25\xc9\ +\xdc\x06\x73\x3f\xcb\xba\xf8\xfe\xfe\x35\xc6\x6f\xcf\xe0\xd6\xc0\ +\xf1\xdc\x6a\x67\x27\x62\xd1\x28\x6c\x3a\x78\xcb\x34\x45\x91\x05\ +\x98\xfb\xe7\x87\x57\xd8\x5c\x4d\x61\x63\xe5\x25\x9a\x8e\x83\xb5\ +\x6c\x16\x1b\x5b\x5b\xf8\x98\xcb\x79\x6b\x76\xce\x4b\x2e\x2f\xa7\ +\x9f\xa4\x52\xab\xcd\x03\x01\x49\x66\x0e\x56\x3b\xa3\x0d\xa1\x5a\ +\xad\xe2\x5c\xff\x10\x2c\x62\x8e\xc5\x62\xde\xae\x2a\xb5\x6b\xfd\ +\x39\x03\xe6\x56\x43\x21\x69\x6e\x76\xf6\x06\xd5\xc1\xd0\xf5\x80\ +\xcc\x1c\xac\xf6\xee\x6d\x1a\x86\x61\x60\x2d\x93\xc6\x9d\xeb\xf7\ +\x91\xa3\x9d\x7d\x2b\x45\x22\xa8\xd7\xeb\x18\x4f\x24\x50\xd3\xf5\ +\xca\xd9\x78\x7c\x21\x14\x0e\x77\x39\x86\x51\x96\x99\x83\x3b\x78\ +\xf1\x70\x9e\x52\xe7\xbd\x82\x7a\xad\x86\xab\xa3\xa3\xde\x3c\x48\ +\xcc\xbe\x71\x9e\x24\x49\xdf\xec\x7c\xfe\xf9\x1e\xc0\xe7\x5e\x11\ +\x99\x83\x3b\x60\xae\xde\x91\x91\x05\x1e\x2d\xe2\xf5\xbd\x3d\xce\ +\x79\xa4\x60\x5c\x9c\x9c\xdc\xa1\xe2\x22\x79\x03\x97\xa6\xa6\x1e\ +\xec\x9a\xa6\x5b\xa1\x57\xc5\x73\x1e\x7f\xe8\xfa\xa1\xb7\xc7\x39\ +\x8f\xe7\xe4\x88\x8d\x8d\x1d\x5c\x6d\xd7\xe0\xe0\x3d\x49\x55\xfb\ +\xab\xfb\xfb\xba\xd2\x68\x6c\x5b\x1d\x1d\x1a\xf3\xf9\x6d\xff\x1d\ +\x27\xee\x02\xf9\xe3\xf6\x7f\xe3\x14\x79\x84\xaf\xf9\x04\x6f\xc8\ +\xe3\xf6\x5a\x27\x1b\x9e\x98\xc0\x6f\x01\x06\x00\x48\xae\x45\x78\ +\x60\x4e\x1f\xe2\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\ +\x00\x00\x0e\x12\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\ +\x01\x00\x9a\x9c\x18\x00\x00\x0a\x4d\x69\x43\x43\x50\x50\x68\x6f\ +\x74\x6f\x73\x68\x6f\x70\x20\x49\x43\x43\x20\x70\x72\x6f\x66\x69\ +\x6c\x65\x00\x00\x78\xda\x9d\x53\x77\x58\x93\xf7\x16\x3e\xdf\xf7\ +\x65\x0f\x56\x42\xd8\xf0\xb1\x97\x6c\x81\x00\x22\x23\xac\x08\xc8\ +\x10\x59\xa2\x10\x92\x00\x61\x84\x10\x12\x40\xc5\x85\x88\x0a\x56\ +\x14\x15\x11\x9c\x48\x55\xc4\x82\xd5\x0a\x48\x9d\x88\xe2\xa0\x28\ +\xb8\x67\x41\x8a\x88\x5a\x8b\x55\x5c\x38\xee\x1f\xdc\xa7\xb5\x7d\ +\x7a\xef\xed\xed\xfb\xd7\xfb\xbc\xe7\x9c\xe7\xfc\xce\x79\xcf\x0f\ +\x80\x11\x12\x26\x91\xe6\xa2\x6a\x00\x39\x52\x85\x3c\x3a\xd8\x1f\ +\x8f\x4f\x48\xc4\xc9\xbd\x80\x02\x15\x48\xe0\x04\x20\x10\xe6\xcb\ +\xc2\x67\x05\xc5\x00\x00\xf0\x03\x79\x78\x7e\x74\xb0\x3f\xfc\x01\ +\xaf\x6f\x00\x02\x00\x70\xd5\x2e\x24\x12\xc7\xe1\xff\x83\xba\x50\ +\x26\x57\x00\x20\x91\x00\xe0\x22\x12\xe7\x0b\x01\x90\x52\x00\xc8\ +\x2e\x54\xc8\x14\x00\xc8\x18\x00\xb0\x53\xb3\x64\x0a\x00\x94\x00\ +\x00\x6c\x79\x7c\x42\x22\x00\xaa\x0d\x00\xec\xf4\x49\x3e\x05\x00\ +\xd8\xa9\x93\xdc\x17\x00\xd8\xa2\x1c\xa9\x08\x00\x8d\x01\x00\x99\ +\x28\x47\x24\x02\x40\xbb\x00\x60\x55\x81\x52\x2c\x02\xc0\xc2\x00\ +\xa0\xac\x40\x22\x2e\x04\xc0\xae\x01\x80\x59\xb6\x32\x47\x02\x80\ +\xbd\x05\x00\x76\x8e\x58\x90\x0f\x40\x60\x00\x80\x99\x42\x2c\xcc\ +\x00\x20\x38\x02\x00\x43\x1e\x13\xcd\x03\x20\x4c\x03\xa0\x30\xd2\ +\xbf\xe0\xa9\x5f\x70\x85\xb8\x48\x01\x00\xc0\xcb\x95\xcd\x97\x4b\ +\xd2\x33\x14\xb8\x95\xd0\x1a\x77\xf2\xf0\xe0\xe2\x21\xe2\xc2\x6c\ +\xb1\x42\x61\x17\x29\x10\x66\x09\xe4\x22\x9c\x97\x9b\x23\x13\x48\ +\xe7\x03\x4c\xce\x0c\x00\x00\x1a\xf9\xd1\xc1\xfe\x38\x3f\x90\xe7\ +\xe6\xe4\xe1\xe6\x66\xe7\x6c\xef\xf4\xc5\xa2\xfe\x6b\xf0\x6f\x22\ +\x3e\x21\xf1\xdf\xfe\xbc\x8c\x02\x04\x00\x10\x4e\xcf\xef\xda\x5f\ +\xe5\xe5\xd6\x03\x70\xc7\x01\xb0\x75\xbf\x6b\xa9\x5b\x00\xda\x56\ +\x00\x68\xdf\xf9\x5d\x33\xdb\x09\xa0\x5a\x0a\xd0\x7a\xf9\x8b\x79\ +\x38\xfc\x40\x1e\x9e\xa1\x50\xc8\x3c\x1d\x1c\x0a\x0b\x0b\xed\x25\ +\x62\xa1\xbd\x30\xe3\x8b\x3e\xff\x33\xe1\x6f\xe0\x8b\x7e\xf6\xfc\ +\x40\x1e\xfe\xdb\x7a\xf0\x00\x71\x9a\x40\x99\xad\xc0\xa3\x83\xfd\ +\x71\x61\x6e\x76\xae\x52\x8e\xe7\xcb\x04\x42\x31\x6e\xf7\xe7\x23\ +\xfe\xc7\x85\x7f\xfd\x8e\x29\xd1\xe2\x34\xb1\x5c\x2c\x15\x8a\xf1\ +\x58\x89\xb8\x50\x22\x4d\xc7\x79\xb9\x52\x91\x44\x21\xc9\x95\xe2\ +\x12\xe9\x7f\x32\xf1\x1f\x96\xfd\x09\x93\x77\x0d\x00\xac\x86\x4f\ +\xc0\x4e\xb6\x07\xb5\xcb\x6c\xc0\x7e\xee\x01\x02\x8b\x0e\x58\xd2\ +\x76\x00\x40\x7e\xf3\x2d\x8c\x1a\x0b\x91\x00\x10\x67\x34\x32\x79\ +\xf7\x00\x00\x93\xbf\xf9\x8f\x40\x2b\x01\x00\xcd\x97\xa4\xe3\x00\ +\x00\xbc\xe8\x18\x5c\xa8\x94\x17\x4c\xc6\x08\x00\x00\x44\xa0\x81\ +\x2a\xb0\x41\x07\x0c\xc1\x14\xac\xc0\x0e\x9c\xc1\x1d\xbc\xc0\x17\ +\x02\x61\x06\x44\x40\x0c\x24\xc0\x3c\x10\x42\x06\xe4\x80\x1c\x0a\ +\xa1\x18\x96\x41\x19\x54\xc0\x3a\xd8\x04\xb5\xb0\x03\x1a\xa0\x11\ +\x9a\xe1\x10\xb4\xc1\x31\x38\x0d\xe7\xe0\x12\x5c\x81\xeb\x70\x17\ +\x06\x60\x18\x9e\xc2\x18\xbc\x86\x09\x04\x41\xc8\x08\x13\x61\x21\ +\x3a\x88\x11\x62\x8e\xd8\x22\xce\x08\x17\x99\x8e\x04\x22\x61\x48\ +\x34\x92\x80\xa4\x20\xe9\x88\x14\x51\x22\xc5\xc8\x72\xa4\x02\xa9\ +\x42\x6a\x91\x5d\x48\x23\xf2\x2d\x72\x14\x39\x8d\x5c\x40\xfa\x90\ +\xdb\xc8\x20\x32\x8a\xfc\x8a\xbc\x47\x31\x94\x81\xb2\x51\x03\xd4\ +\x02\x75\x40\xb9\xa8\x1f\x1a\x8a\xc6\xa0\x73\xd1\x74\x34\x0f\x5d\ +\x80\x96\xa2\x6b\xd1\x1a\xb4\x1e\x3d\x80\xb6\xa2\xa7\xd1\x4b\xe8\ +\x75\x74\x00\x7d\x8a\x8e\x63\x80\xd1\x31\x0e\x66\x8c\xd9\x61\x5c\ +\x8c\x87\x45\x60\x89\x58\x1a\x26\xc7\x16\x63\xe5\x58\x35\x56\x8f\ +\x35\x63\x1d\x58\x37\x76\x15\x1b\xc0\x9e\x61\xef\x08\x24\x02\x8b\ +\x80\x13\xec\x08\x5e\x84\x10\xc2\x6c\x82\x90\x90\x47\x58\x4c\x58\ +\x43\xa8\x25\xec\x23\xb4\x12\xba\x08\x57\x09\x83\x84\x31\xc2\x27\ +\x22\x93\xa8\x4f\xb4\x25\x7a\x12\xf9\xc4\x78\x62\x3a\xb1\x90\x58\ +\x46\xac\x26\xee\x21\x1e\x21\x9e\x25\x5e\x27\x0e\x13\x5f\x93\x48\ +\x24\x0e\xc9\x92\xe4\x4e\x0a\x21\x25\x90\x32\x49\x0b\x49\x6b\x48\ +\xdb\x48\x2d\xa4\x53\xa4\x3e\xd2\x10\x69\x9c\x4c\x26\xeb\x90\x6d\ +\xc9\xde\xe4\x08\xb2\x80\xac\x20\x97\x91\xb7\x90\x0f\x90\x4f\x92\ +\xfb\xc9\xc3\xe4\xb7\x14\x3a\xc5\x88\xe2\x4c\x09\xa2\x24\x52\xa4\ +\x94\x12\x4a\x35\x65\x3f\xe5\x04\xa5\x9f\x32\x42\x99\xa0\xaa\x51\ +\xcd\xa9\x9e\xd4\x08\xaa\x88\x3a\x9f\x5a\x49\x6d\xa0\x76\x50\x2f\ +\x53\x87\xa9\x13\x34\x75\x9a\x25\xcd\x9b\x16\x43\xcb\xa4\x2d\xa3\ +\xd5\xd0\x9a\x69\x67\x69\xf7\x68\x2f\xe9\x74\xba\x09\xdd\x83\x1e\ +\x45\x97\xd0\x97\xd2\x6b\xe8\x07\xe9\xe7\xe9\x83\xf4\x77\x0c\x0d\ +\x86\x0d\x83\xc7\x48\x62\x28\x19\x6b\x19\x7b\x19\xa7\x18\xb7\x19\ +\x2f\x99\x4c\xa6\x05\xd3\x97\x99\xc8\x54\x30\xd7\x32\x1b\x99\x67\ +\x98\x0f\x98\x6f\x55\x58\x2a\xf6\x2a\x7c\x15\x91\xca\x12\x95\x3a\ +\x95\x56\x95\x7e\x95\xe7\xaa\x54\x55\x73\x55\x3f\xd5\x79\xaa\x0b\ +\x54\xab\x55\x0f\xab\x5e\x56\x7d\xa6\x46\x55\xb3\x50\xe3\xa9\x09\ +\xd4\x16\xab\xd5\xa9\x1d\x55\xbb\xa9\x36\xae\xce\x52\x77\x52\x8f\ +\x50\xcf\x51\x5f\xa3\xbe\x5f\xfd\x82\xfa\x63\x0d\xb2\x86\x85\x46\ +\xa0\x86\x48\xa3\x54\x63\xb7\xc6\x19\x8d\x21\x16\xc6\x32\x65\xf1\ +\x58\x42\xd6\x72\x56\x03\xeb\x2c\x6b\x98\x4d\x62\x5b\xb2\xf9\xec\ +\x4c\x76\x05\xfb\x1b\x76\x2f\x7b\x4c\x53\x43\x73\xaa\x66\xac\x66\ +\x91\x66\x9d\xe6\x71\xcd\x01\x0e\xc6\xb1\xe0\xf0\x39\xd9\x9c\x4a\ +\xce\x21\xce\x0d\xce\x7b\x2d\x03\x2d\x3f\x2d\xb1\xd6\x6a\xad\x66\ +\xad\x7e\xad\x37\xda\x7a\xda\xbe\xda\x62\xed\x72\xed\x16\xed\xeb\ +\xda\xef\x75\x70\x9d\x40\x9d\x2c\x9d\xf5\x3a\x6d\x3a\xf7\x75\x09\ +\xba\x36\xba\x51\xba\x85\xba\xdb\x75\xcf\xea\x3e\xd3\x63\xeb\x79\ +\xe9\x09\xf5\xca\xf5\x0e\xe9\xdd\xd1\x47\xf5\x6d\xf4\xa3\xf5\x17\ +\xea\xef\xd6\xef\xd1\x1f\x37\x30\x34\x08\x36\x90\x19\x6c\x31\x38\ +\x63\xf0\xcc\x90\x63\xe8\x6b\x98\x69\xb8\xd1\xf0\x84\xe1\xa8\x11\ +\xcb\x68\xba\x91\xc4\x68\xa3\xd1\x49\xa3\x27\xb8\x26\xee\x87\x67\ +\xe3\x35\x78\x17\x3e\x66\xac\x6f\x1c\x62\xac\x34\xde\x65\xdc\x6b\ +\x3c\x61\x62\x69\x32\xdb\xa4\xc4\xa4\xc5\xe4\xbe\x29\xcd\x94\x6b\ +\x9a\x66\xba\xd1\xb4\xd3\x74\xcc\xcc\xc8\x2c\xdc\xac\xd8\xac\xc9\ +\xec\x8e\x39\xd5\x9c\x6b\x9e\x61\xbe\xd9\xbc\xdb\xfc\x8d\x85\xa5\ +\x45\x9c\xc5\x4a\x8b\x36\x8b\xc7\x96\xda\x96\x7c\xcb\x05\x96\x4d\ +\x96\xf7\xac\x98\x56\x3e\x56\x79\x56\xf5\x56\xd7\xac\x49\xd6\x5c\ +\xeb\x2c\xeb\x6d\xd6\x57\x6c\x50\x1b\x57\x9b\x0c\x9b\x3a\x9b\xcb\ +\xb6\xa8\xad\x9b\xad\xc4\x76\x9b\x6d\xdf\x14\xe2\x14\x8f\x29\xd2\ +\x29\xf5\x53\x6e\xda\x31\xec\xfc\xec\x0a\xec\x9a\xec\x06\xed\x39\ +\xf6\x61\xf6\x25\xf6\x6d\xf6\xcf\x1d\xcc\x1c\x12\x1d\xd6\x3b\x74\ +\x3b\x7c\x72\x74\x75\xcc\x76\x6c\x70\xbc\xeb\xa4\xe1\x34\xc3\xa9\ +\xc4\xa9\xc3\xe9\x57\x67\x1b\x67\xa1\x73\x9d\xf3\x35\x17\xa6\x4b\ +\x90\xcb\x12\x97\x76\x97\x17\x53\x6d\xa7\x8a\xa7\x6e\x9f\x7a\xcb\ +\x95\xe5\x1a\xee\xba\xd2\xb5\xd3\xf5\xa3\x9b\xbb\x9b\xdc\xad\xd9\ +\x6d\xd4\xdd\xcc\x3d\xc5\x7d\xab\xfb\x4d\x2e\x9b\x1b\xc9\x5d\xc3\ +\x3d\xef\x41\xf4\xf0\xf7\x58\xe2\x71\xcc\xe3\x9d\xa7\x9b\xa7\xc2\ +\xf3\x90\xe7\x2f\x5e\x76\x5e\x59\x5e\xfb\xbd\x1e\x4f\xb3\x9c\x26\ +\x9e\xd6\x30\x6d\xc8\xdb\xc4\x5b\xe0\xbd\xcb\x7b\x60\x3a\x3e\x3d\ +\x65\xfa\xce\xe9\x03\x3e\xc6\x3e\x02\x9f\x7a\x9f\x87\xbe\xa6\xbe\ +\x22\xdf\x3d\xbe\x23\x7e\xd6\x7e\x99\x7e\x07\xfc\x9e\xfb\x3b\xfa\ +\xcb\xfd\x8f\xf8\xbf\xe1\x79\xf2\x16\xf1\x4e\x05\x60\x01\xc1\x01\ +\xe5\x01\xbd\x81\x1a\x81\xb3\x03\x6b\x03\x1f\x04\x99\x04\xa5\x07\ +\x35\x05\x8d\x05\xbb\x06\x2f\x0c\x3e\x15\x42\x0c\x09\x0d\x59\x1f\ +\x72\x93\x6f\xc0\x17\xf2\x1b\xf9\x63\x33\xdc\x67\x2c\x9a\xd1\x15\ +\xca\x08\x9d\x15\x5a\x1b\xfa\x30\xcc\x26\x4c\x1e\xd6\x11\x8e\x86\ +\xcf\x08\xdf\x10\x7e\x6f\xa6\xf9\x4c\xe9\xcc\xb6\x08\x88\xe0\x47\ +\x6c\x88\xb8\x1f\x69\x19\x99\x17\xf9\x7d\x14\x29\x2a\x32\xaa\x2e\ +\xea\x51\xb4\x53\x74\x71\x74\xf7\x2c\xd6\xac\xe4\x59\xfb\x67\xbd\ +\x8e\xf1\x8f\xa9\x8c\xb9\x3b\xdb\x6a\xb6\x72\x76\x67\xac\x6a\x6c\ +\x52\x6c\x63\xec\x9b\xb8\x80\xb8\xaa\xb8\x81\x78\x87\xf8\x45\xf1\ +\x97\x12\x74\x13\x24\x09\xed\x89\xe4\xc4\xd8\xc4\x3d\x89\xe3\x73\ +\x02\xe7\x6c\x9a\x33\x9c\xe4\x9a\x54\x96\x74\x63\xae\xe5\xdc\xa2\ +\xb9\x17\xe6\xe9\xce\xcb\x9e\x77\x3c\x59\x35\x59\x90\x7c\x38\x85\ +\x98\x12\x97\xb2\x3f\xe5\x83\x20\x42\x50\x2f\x18\x4f\xe5\xa7\x6e\ +\x4d\x1d\x13\xf2\x84\x9b\x85\x4f\x45\xbe\xa2\x8d\xa2\x51\xb1\xb7\ +\xb8\x4a\x3c\x92\xe6\x9d\x56\x95\xf6\x38\xdd\x3b\x7d\x43\xfa\x68\ +\x86\x4f\x46\x75\xc6\x33\x09\x4f\x52\x2b\x79\x91\x19\x92\xb9\x23\ +\xf3\x4d\x56\x44\xd6\xde\xac\xcf\xd9\x71\xd9\x2d\x39\x94\x9c\x94\ +\x9c\xa3\x52\x0d\x69\x96\xb4\x2b\xd7\x30\xb7\x28\xb7\x4f\x66\x2b\ +\x2b\x93\x0d\xe4\x79\xe6\x6d\xca\x1b\x93\x87\xca\xf7\xe4\x23\xf9\ +\x73\xf3\xdb\x15\x6c\x85\x4c\xd1\xa3\xb4\x52\xae\x50\x0e\x16\x4c\ +\x2f\xa8\x2b\x78\x5b\x18\x5b\x78\xb8\x48\xbd\x48\x5a\xd4\x33\xdf\ +\x66\xfe\xea\xf9\x23\x0b\x82\x16\x7c\xbd\x90\xb0\x50\xb8\xb0\xb3\ +\xd8\xb8\x78\x59\xf1\xe0\x22\xbf\x45\xbb\x16\x23\x8b\x53\x17\x77\ +\x2e\x31\x5d\x52\xba\x64\x78\x69\xf0\xd2\x7d\xcb\x68\xcb\xb2\x96\ +\xfd\x50\xe2\x58\x52\x55\xf2\x6a\x79\xdc\xf2\x8e\x52\x83\xd2\xa5\ +\xa5\x43\x2b\x82\x57\x34\x95\xa9\x94\xc9\xcb\x6e\xae\xf4\x5a\xb9\ +\x63\x15\x61\x95\x64\x55\xef\x6a\x97\xd5\x5b\x56\x7f\x2a\x17\x95\ +\x5f\xac\x70\xac\xa8\xae\xf8\xb0\x46\xb8\xe6\xe2\x57\x4e\x5f\xd5\ +\x7c\xf5\x79\x6d\xda\xda\xde\x4a\xb7\xca\xed\xeb\x48\xeb\xa4\xeb\ +\x6e\xac\xf7\x59\xbf\xaf\x4a\xbd\x6a\x41\xd5\xd0\x86\xf0\x0d\xad\ +\x1b\xf1\x8d\xe5\x1b\x5f\x6d\x4a\xde\x74\xa1\x7a\x6a\xf5\x8e\xcd\ +\xb4\xcd\xca\xcd\x03\x35\x61\x35\xed\x5b\xcc\xb6\xac\xdb\xf2\xa1\ +\x36\xa3\xf6\x7a\x9d\x7f\x5d\xcb\x56\xfd\xad\xab\xb7\xbe\xd9\x26\ +\xda\xd6\xbf\xdd\x77\x7b\xf3\x0e\x83\x1d\x15\x3b\xde\xef\x94\xec\ +\xbc\xb5\x2b\x78\x57\x6b\xbd\x45\x7d\xf5\x6e\xd2\xee\x82\xdd\x8f\ +\x1a\x62\x1b\xba\xbf\xe6\x7e\xdd\xb8\x47\x77\x4f\xc5\x9e\x8f\x7b\ +\xa5\x7b\x07\xf6\x45\xef\xeb\x6a\x74\x6f\x6c\xdc\xaf\xbf\xbf\xb2\ +\x09\x6d\x52\x36\x8d\x1e\x48\x3a\x70\xe5\x9b\x80\x6f\xda\x9b\xed\ +\x9a\x77\xb5\x70\x5a\x2a\x0e\xc2\x41\xe5\xc1\x27\xdf\xa6\x7c\x7b\ +\xe3\x50\xe8\xa1\xce\xc3\xdc\xc3\xcd\xdf\x99\x7f\xb7\xf5\x08\xeb\ +\x48\x79\x2b\xd2\x3a\xbf\x75\xac\x2d\xa3\x6d\xa0\x3d\xa1\xbd\xef\ +\xe8\x8c\xa3\x9d\x1d\x5e\x1d\x47\xbe\xb7\xff\x7e\xef\x31\xe3\x63\ +\x75\xc7\x35\x8f\x57\x9e\xa0\x9d\x28\x3d\xf1\xf9\xe4\x82\x93\xe3\ +\xa7\x64\xa7\x9e\x9d\x4e\x3f\x3d\xd4\x99\xdc\x79\xf7\x4c\xfc\x99\ +\x6b\x5d\x51\x5d\xbd\x67\x43\xcf\x9e\x3f\x17\x74\xee\x4c\xb7\x5f\ +\xf7\xc9\xf3\xde\xe7\x8f\x5d\xf0\xbc\x70\xf4\x22\xf7\x62\xdb\x25\ +\xb7\x4b\xad\x3d\xae\x3d\x47\x7e\x70\xfd\xe1\x48\xaf\x5b\x6f\xeb\ +\x65\xf7\xcb\xed\x57\x3c\xae\x74\xf4\x4d\xeb\x3b\xd1\xef\xd3\x7f\ +\xfa\x6a\xc0\xd5\x73\xd7\xf8\xd7\x2e\x5d\x9f\x79\xbd\xef\xc6\xec\ +\x1b\xb7\x6e\x26\xdd\x1c\xb8\x25\xba\xf5\xf8\x76\xf6\xed\x17\x77\ +\x0a\xee\x4c\xdc\x5d\x7a\x8f\x78\xaf\xfc\xbe\xda\xfd\xea\x07\xfa\ +\x0f\xea\x7f\xb4\xfe\xb1\x65\xc0\x6d\xe0\xf8\x60\xc0\x60\xcf\xc3\ +\x59\x0f\xef\x0e\x09\x87\x9e\xfe\x94\xff\xd3\x87\xe1\xd2\x47\xcc\ +\x47\xd5\x23\x46\x23\x8d\x8f\x9d\x1f\x1f\x1b\x0d\x1a\xbd\xf2\x64\ +\xce\x93\xe1\xa7\xb2\xa7\x13\xcf\xca\x7e\x56\xff\x79\xeb\x73\xab\ +\xe7\xdf\xfd\xe2\xfb\x4b\xcf\x58\xfc\xd8\xf0\x0b\xf9\x8b\xcf\xbf\ +\xae\x79\xa9\xf3\x72\xef\xab\xa9\xaf\x3a\xc7\x23\xc7\x1f\xbc\xce\ +\x79\x3d\xf1\xa6\xfc\xad\xce\xdb\x7d\xef\xb8\xef\xba\xdf\xc7\xbd\ +\x1f\x99\x28\xfc\x40\xfe\x50\xf3\xd1\xfa\x63\xc7\xa7\xd0\x4f\xf7\ +\x3e\xe7\x7c\xfe\xfc\x2f\xf7\x84\xf3\xfb\x25\xd2\x9f\x33\x00\x00\ +\x00\x20\x63\x48\x52\x4d\x00\x00\x7a\x25\x00\x00\x80\x83\x00\x00\ +\xf9\xff\x00\x00\x80\xe9\x00\x00\x75\x30\x00\x00\xea\x60\x00\x00\ +\x3a\x98\x00\x00\x17\x6f\x92\x5f\xc5\x46\x00\x00\x03\x3f\x49\x44\ +\x41\x54\x78\xda\x4c\x92\x4b\x68\x5c\x75\x18\xc5\xcf\xff\x75\xdf\ +\x33\xf7\xde\xb9\x33\xf7\xde\x98\xa1\x8c\x21\xf8\x00\xd1\x85\x9b\ +\xd6\xae\xdc\x58\x17\x6e\xa2\xa4\xc1\xfa\x44\x70\x63\x45\xea\x2e\ +\xfb\x62\xa5\xa0\xae\xc4\x8d\xe0\x42\x17\x2e\x4a\xc1\x95\x88\xa5\ +\x14\xc1\xd2\xa6\x55\x92\x51\x5a\xd3\x26\xa9\xe9\xb4\x99\x99\x66\ +\x26\xce\x4c\xe7\x79\x1f\xf3\xbf\x2e\x94\x34\x67\x75\x16\x87\x1f\ +\xe7\xfb\x38\x24\x4d\x53\x00\x40\x96\x65\x20\x84\x20\xcb\x32\x00\ +\x00\xa5\x14\x8d\xc6\x83\x57\xaa\xd5\x3f\x4e\xfb\x81\x2f\xc2\xd0\ +\x5f\x73\x5d\xf7\x73\x55\x51\x56\x0f\xe6\xc8\x41\x00\x00\x46\x08\ +\xc9\x08\x21\x32\x8e\xe3\xb9\x2b\x57\xaf\xfd\xa9\xaa\xaa\xe1\x07\ +\x45\x38\x8e\x03\x53\x37\x92\xf1\x78\xfc\xb5\x65\x99\xa7\x18\xe3\ +\x49\x96\x49\x50\x3c\x52\x9e\x10\x22\x00\x48\x42\x29\x6e\xdc\x5c\ +\xff\x0a\x80\x51\xf0\x5c\x28\x8a\x02\xce\x39\x54\x55\x15\xae\xeb\ +\x7e\xd0\x6a\xb7\x7f\x89\xa2\xa8\xc8\xb9\xd8\x07\x10\x42\xc8\x08\ +\xc0\x84\x31\x86\x8d\xdb\x1b\xcb\xad\x56\xeb\x98\xe7\x15\xa0\x69\ +\x2a\x74\x43\x87\xae\xe9\x20\x20\x88\xa2\x09\xea\x8d\xe6\x91\x1b\ +\x37\xff\xfa\x22\x4d\x13\x8b\xff\x0f\xc8\x00\xa4\x8c\x31\x74\x3a\ +\xdd\x23\x1b\x9b\x5b\xa7\x83\x20\x80\x61\xe8\x50\x55\x15\x86\x66\ +\x40\x70\x8e\xa9\x94\xb8\xb7\xb3\x83\x6e\xb7\x07\x02\xf2\x5a\xad\ +\xb6\x73\xfe\xe0\x09\x88\xa2\xc8\x5f\xab\x56\xbf\xb5\x6d\x87\xe7\ +\xf2\x39\xa8\x9a\x0a\x5d\xd7\xa1\x08\x01\x00\xe8\xf6\x3a\xa8\xd7\ +\x9b\x20\x20\xb0\x6d\x1b\x42\x08\xc2\xef\xef\xd4\x1f\x7d\xbd\xd9\ +\x3c\x1b\xc7\xd3\xf9\x30\x70\xa1\xa9\x0a\x54\x55\x85\xae\x68\x60\ +\x8c\x21\x8e\x63\xd4\x6a\xf7\x30\x19\x4f\x60\x18\x3a\x2a\x95\x43\ +\xdf\x15\x3d\xef\x02\xef\x76\x7b\xa0\x84\x82\x10\xf2\xf6\x5e\x7b\ +\xef\xdd\x72\xb9\x0c\x45\x15\x10\x8a\x80\xa6\x69\x60\x9c\x21\xcb\ +\x32\x34\x9b\x4d\xb4\x5a\x7b\x60\x8c\xc1\x76\xec\x5d\xca\xe8\x72\ +\xaf\xff\x70\xc8\x85\xe0\x20\x40\x71\x6b\xeb\xce\x19\xc3\x34\x10\ +\x84\x25\xe4\x72\x39\xa4\x69\x0a\xc1\x05\x08\x21\x18\x0c\x06\xd8\ +\xbe\x7b\x17\x52\x4a\x38\x8e\x83\xf1\x68\x7c\xaa\x56\xab\x75\x01\ +\x80\xef\x3e\xd8\x3d\x7c\xee\xdc\xf9\x2f\x57\x56\xae\xcf\x52\x4a\ +\x30\x33\x3b\x83\xd7\x97\x96\xb0\xb8\xf8\xea\x7e\xf5\x3b\xdb\xdb\ +\xe8\xf7\x87\x28\x14\x0a\x98\xca\xf4\xa7\x5e\xaf\xfb\xbd\xe3\x38\ +\x08\xc3\x10\xf4\xd6\xfa\xed\x17\xd6\x56\xab\xcf\x4f\xd3\x14\x9d\ +\x4e\x17\x2b\x57\xaf\xe1\xe4\x87\x1f\xe1\xf8\xd2\x1b\xd8\xdc\xdc\ +\x42\x1c\xc7\x68\xd4\x1b\x30\x2d\x13\xba\xae\x65\xc3\xc1\x70\xd9\ +\x34\x4d\x54\x2a\x15\xd8\xb6\x0d\xea\x07\xc1\x8f\xf3\xf3\xf3\xab\ +\x9c\x73\xe4\x2c\x0b\x9e\x5b\x40\xd1\xf3\x70\xf9\xd7\xcb\x58\x3c\ +\x7e\x02\x17\x2f\x5d\x82\x65\xe5\xe0\xba\x0e\xc6\x93\xf1\x37\x32\ +\x93\x55\xc7\x71\xa1\x2a\x0a\x92\x24\x01\xcd\xdb\xf9\xf5\x63\x2f\ +\xbf\x74\xd2\xb6\xed\x11\xe7\x1c\xa6\x69\xc2\x34\x4c\x84\x41\x88\ +\xc1\xc3\x3e\xce\x7e\xfa\x19\x28\x25\x10\x82\x8f\xa4\x9c\x7e\x62\ +\x18\x3a\xa2\x68\x02\x99\x65\x10\x42\x80\x66\x99\xc4\x63\xb3\x33\ +\x57\x16\x16\x16\x4e\x94\x4a\xa5\x1e\xe7\x1c\x86\x61\xc0\xb2\x2c\ +\x78\x9e\x87\xce\x3f\x1d\xfc\x76\xfd\x77\x48\x39\x3d\xc3\x39\xff\ +\xdb\xb6\x6d\x3c\xf5\xc4\x93\x70\x6d\x07\xae\xed\x80\xbd\xf5\xe6\ +\x3b\x90\x53\x09\xcf\xf3\x6e\xf9\x81\xff\x43\x92\x24\x4f\x27\x69\ +\x32\x4b\x40\x38\xa5\x14\x9c\x0b\x94\x0f\x95\xb7\x9f\x7d\xee\x99\ +\xf7\x28\x65\xd1\x5c\xe5\x71\xe4\x72\xb9\xfd\xf1\x91\x0b\x3f\x5f\ +\xfc\xcf\x50\xc2\x06\xc3\xd1\xb4\xdf\x1f\xa0\x5e\xaf\xcf\xb5\xdb\ +\x7b\xef\xa7\x69\xf2\xa2\xef\xfb\x8d\xa3\x47\x0f\x7f\x6c\x3b\xf9\ +\xed\x30\x08\xe0\x97\x7c\x48\x29\xf7\x01\xff\x0e\x00\x87\x7e\x46\ +\xf6\x00\xdd\xdd\x70\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\ +\x82\ +\x00\x00\x02\xaf\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ +\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ +\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ +\x79\x71\xc9\x65\x3c\x00\x00\x02\x51\x49\x44\x41\x54\x78\xda\x9c\ +\x53\xcf\x6b\x13\x51\x10\xfe\x36\xfb\x62\x8d\x69\x48\x62\x9b\x18\ +\x8d\xab\x5d\xd3\x84\xa2\x10\xbd\x48\x0f\x62\xa9\xd8\x93\xe0\x49\ +\x0f\xa5\x20\x52\xb0\xe0\x7f\xe0\x45\x3c\xf5\xa6\x77\x7b\xe8\x55\ +\x28\x41\x3d\x78\x28\x7a\xf0\x47\x51\xa4\xbd\x58\x0d\xa8\x60\x5a\ +\x13\x51\xd0\x43\x89\x69\xf3\x63\xb3\xc9\xee\x3e\x67\x9e\xd9\xa2\ +\x08\x4a\x1d\x18\xde\xdb\x99\xf9\xbe\xf7\xcd\x7b\xb3\xda\x8d\x85\ +\x05\xb0\x69\x9a\x76\x9e\x96\xfd\xf8\xbb\x3d\x71\x1c\x67\xad\xdb\ +\xe9\xe0\xdc\xf3\x19\x48\x0a\x08\xd7\x75\xfd\xe4\x81\xeb\x93\x93\ +\x73\x0e\x7d\x73\xc2\x95\x12\x5d\xda\x77\x3d\x4f\xed\x2b\x95\x0a\ +\x1e\x15\x8b\x57\xa5\x94\x1a\xa5\x4b\x3e\x28\x30\xf1\xf8\x32\xcc\ +\xb5\x7b\x20\x56\x4d\x72\xb1\xe3\xc0\xe9\x76\xe1\xf6\xbc\xd3\x6e\ +\xc3\x6a\x36\xd1\x68\x34\x30\x3b\x35\x35\x47\xb9\xb3\x44\x92\xf5\ +\x09\x04\xfb\xf0\xa7\x07\x57\x5a\x32\x78\x41\xd3\x2e\xe1\xc5\xea\ +\x2a\x3c\x22\x8a\xc5\x62\x68\xb5\x5a\x38\x3e\x32\xa2\x0a\xab\xd5\ +\x2a\xee\x2c\x2e\x22\x9f\x4c\xde\x5e\x29\xcc\x3e\x85\x8e\x02\x85\ +\xe7\x05\xa9\x1b\x44\x40\xcf\x65\x8f\x9e\x9c\x60\x6d\x99\x4c\x06\ +\x74\x82\x22\x89\xc7\xe3\x08\xea\xba\x22\x38\x35\x3a\x8a\x0e\xa9\ +\x0b\x85\xc3\x18\x68\x5d\x3c\x23\x1f\xbe\x7a\x2d\x3d\x77\x50\xb8\ +\x12\xc6\x5e\xe3\xd8\xf0\x26\x5d\x4c\x40\xd3\x54\xaf\xd1\x68\x54\ +\x9d\xc8\x24\x1f\x89\x8c\x09\x39\xc6\x8a\x4e\xe4\xf3\xb0\x6d\x1b\ +\x49\xc2\x54\x2b\x45\x43\xb8\x1e\x0e\xed\x8e\x26\xf7\x59\x56\x1b\ +\xbf\x2a\xe0\xd3\x7d\x25\xb2\x47\xe2\x2b\xe2\x5a\xc6\x30\x96\x14\ +\xc8\xa1\x60\x38\x16\x6a\x12\x3b\x3d\x25\xca\xe5\xf2\x36\xc0\x57\ +\xc2\x2b\x7f\xb3\x82\xc3\xa9\x14\xb8\x96\x31\x8c\x15\x8e\x87\x5c\ +\x24\x65\x26\xac\xf7\x75\x94\x0b\xd7\x30\x40\xb7\xde\x97\x1b\x47\ +\x5f\x76\xec\x37\x25\xf6\x87\x25\x04\x4b\x4b\xf8\xba\xbe\x07\x56\ +\xdb\x46\xc4\x34\x13\x8c\xe5\x16\x44\x24\x91\x4e\x4d\x27\x7e\x3e\ +\x0b\x4f\xd2\xca\xf2\x7d\x38\xc2\x50\x40\x7e\x0d\x6e\x63\x73\xf9\ +\x2e\x4e\x8f\x8d\xab\x9a\x69\x53\x2d\x29\xc6\xb2\x02\xb1\xb5\xb1\ +\x41\x7d\x59\x2a\xda\x4f\x00\x23\x9d\xc6\x97\x67\x37\x15\x41\x93\ +\x62\x3c\x58\xe6\x90\x89\x66\xbd\x8e\x46\xad\xa6\xea\x42\xa1\x10\ +\x1c\x45\xe0\x4a\xe1\xf0\xf0\x90\xb3\xd5\x88\xcc\xc8\x66\x71\xd0\ +\x3c\xf2\xc7\x1c\x7f\x2e\x6d\x0f\xa0\xaa\x67\xac\xe8\x7a\x08\x76\ +\x3a\x34\x71\xe4\xbe\xad\xbf\x7d\x87\x7f\x99\xae\x0b\x30\x56\x34\ +\x6c\xf4\x4b\xc9\x5a\x74\xec\xc4\x18\xc3\x58\xf1\xe6\x9b\xac\x6c\ +\xcd\xdf\x7a\x89\xff\xb0\xf2\x77\x54\x78\x76\x76\x91\xc7\x7a\xff\ +\xc5\x4e\x8c\x2f\xad\xf6\x43\x80\x01\x00\xc1\x52\x4e\xcc\x97\x5f\ +\x6c\x5a\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x02\x77\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ +\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ +\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ +\x79\x71\xc9\x65\x3c\x00\x00\x02\x19\x49\x44\x41\x54\x78\xda\x8c\ +\x93\xbd\xaf\xa1\x41\x14\xc6\xcf\xcb\x2b\xe2\x26\x16\x05\xb1\x42\ +\x43\xa2\x11\xbd\x66\x23\xa2\x22\xb9\xff\xc1\xaa\xf7\xd6\x1a\x0a\ +\x09\x89\x46\xb7\xd1\xdd\x68\x97\xbf\x60\x2b\x8a\x0d\xb1\x85\x8f\ +\x42\x50\xae\x50\x21\x44\x04\xb1\xbe\x3f\x76\x9e\xb3\x5e\xb9\x37\ +\x5b\xac\x93\x8c\x31\x73\xcc\xef\x79\xce\x99\x21\x25\x93\x49\xba\ +\x5e\xaf\x24\x49\xd2\x33\x11\x7d\xa4\xff\xc7\x8f\xf3\xf9\xdc\x3b\ +\x1e\x8f\x94\xc9\x64\x48\xc6\x8e\x4a\xa5\xa2\xd3\xe9\x64\x4b\x24\ +\x12\xaf\x22\xc9\x40\x0c\xb1\x77\x1f\x58\xf7\x7a\x3d\x2a\x95\x4a\ +\x2f\x37\x50\x0f\x1f\x00\x3c\x8b\x24\x94\x3f\xb5\x5a\x2d\x9a\x4e\ +\xa7\xa4\x56\xab\xc9\x60\x30\xd0\x78\x3c\x26\x9d\x4e\x47\xbb\xdd\ +\x8e\xdc\x6e\x37\xad\xd7\x6b\x4a\xa7\xd3\xaf\xf1\x78\x1c\x10\x49\ +\x8c\x5f\x92\x50\xfd\xf2\x88\x32\x20\xc3\xe1\x90\x34\x1a\x0d\xcb\ +\x67\xb3\xd9\x68\xa3\xd1\xf8\x2a\xa3\x16\xfc\xe8\x72\xb9\xfc\x33\ +\x2b\x10\x28\x87\x42\x21\x2a\x16\x8b\x64\xb5\x5a\xc9\x62\xb1\x50\ +\xbd\x5e\xdf\x71\xf9\x02\x20\xfa\x27\x51\xb3\xd9\xa4\x6e\xb7\xcb\ +\xfd\xa8\x56\xab\xd4\xef\xf7\xa9\xdd\x6e\x93\x2c\xcb\x34\x9f\xcf\ +\xa9\x50\x28\xd0\x6c\x36\xa3\x72\xb9\x8c\x86\xd3\x7e\xbf\x97\xb8\ +\x07\x0a\xc0\xe7\xf3\xdd\x95\x03\x81\x00\xcf\x18\x70\xe8\xf7\xfb\ +\xd9\x89\x56\xab\xa5\x5a\xad\xc6\xd0\xc3\xe1\xf0\x17\x00\x12\x00\ +\xb9\x5c\x8e\xed\x79\xbd\x5e\x4a\xa5\x52\x64\xb3\xd9\xc8\xe9\x74\ +\x52\x24\x12\xa1\x7c\x3e\x4f\x66\xb3\x99\x3c\x1e\x0f\x94\x19\x70\ +\x77\x00\x12\x00\xe1\x70\x98\x1d\x38\x1c\x0e\xee\xbc\xc9\x64\xe2\ +\x32\x50\x52\x30\x18\x64\x37\xc8\x0d\x06\x03\x06\x88\xa6\x32\x40\ +\xa5\x38\xc0\x95\x2d\x16\x0b\xae\x0f\xca\x88\xd1\x68\xc4\x80\xc9\ +\x64\x42\xcb\xe5\x92\x0f\xe2\xb6\xde\xf5\x00\x24\x6c\xc0\x32\x1c\ +\xe0\x40\x2c\x16\xbb\x5f\x29\x94\xed\x76\x3b\xcf\x6f\x01\xdb\xed\ +\xf6\xbd\x03\x58\x53\x1c\x54\x2a\x15\xea\x74\x3a\x3c\x03\x88\x1c\ +\xe6\xdb\x41\x5a\xad\x56\x70\xcc\xaf\x58\x56\x48\x8a\xed\xb7\x25\ +\xa0\x0f\x58\xbb\x5c\x2e\xe5\xff\x82\x07\xf4\x3d\x1a\x8d\xfe\x14\ +\x8e\x26\x0c\x00\x49\x51\xc7\x7d\x23\xb0\x36\x1a\x8d\xac\x86\x40\ +\x0e\x00\xc4\x66\xb3\x69\x89\x7e\x7c\x13\x5f\x57\x2c\xa8\xd7\xeb\ +\x3f\x0b\x7b\x36\x7a\x30\x84\xf2\x48\xf4\x2d\xaf\xbc\x60\xd8\x7f\ +\x12\xe3\x03\xfa\xf1\xc0\xf9\xeb\x4d\xf9\x37\x2f\x04\xe0\x8f\x00\ +\x03\x00\xe7\xe3\x7a\x6e\x30\xbb\xf3\xb7\x00\x00\x00\x00\x49\x45\ +\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x10\xe5\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\ +\x01\x00\x9a\x9c\x18\x00\x00\x0a\x4d\x69\x43\x43\x50\x50\x68\x6f\ +\x74\x6f\x73\x68\x6f\x70\x20\x49\x43\x43\x20\x70\x72\x6f\x66\x69\ +\x6c\x65\x00\x00\x78\xda\x9d\x53\x77\x58\x93\xf7\x16\x3e\xdf\xf7\ +\x65\x0f\x56\x42\xd8\xf0\xb1\x97\x6c\x81\x00\x22\x23\xac\x08\xc8\ +\x10\x59\xa2\x10\x92\x00\x61\x84\x10\x12\x40\xc5\x85\x88\x0a\x56\ +\x14\x15\x11\x9c\x48\x55\xc4\x82\xd5\x0a\x48\x9d\x88\xe2\xa0\x28\ +\xb8\x67\x41\x8a\x88\x5a\x8b\x55\x5c\x38\xee\x1f\xdc\xa7\xb5\x7d\ +\x7a\xef\xed\xed\xfb\xd7\xfb\xbc\xe7\x9c\xe7\xfc\xce\x79\xcf\x0f\ +\x80\x11\x12\x26\x91\xe6\xa2\x6a\x00\x39\x52\x85\x3c\x3a\xd8\x1f\ +\x8f\x4f\x48\xc4\xc9\xbd\x80\x02\x15\x48\xe0\x04\x20\x10\xe6\xcb\ +\xc2\x67\x05\xc5\x00\x00\xf0\x03\x79\x78\x7e\x74\xb0\x3f\xfc\x01\ +\xaf\x6f\x00\x02\x00\x70\xd5\x2e\x24\x12\xc7\xe1\xff\x83\xba\x50\ +\x26\x57\x00\x20\x91\x00\xe0\x22\x12\xe7\x0b\x01\x90\x52\x00\xc8\ +\x2e\x54\xc8\x14\x00\xc8\x18\x00\xb0\x53\xb3\x64\x0a\x00\x94\x00\ +\x00\x6c\x79\x7c\x42\x22\x00\xaa\x0d\x00\xec\xf4\x49\x3e\x05\x00\ +\xd8\xa9\x93\xdc\x17\x00\xd8\xa2\x1c\xa9\x08\x00\x8d\x01\x00\x99\ +\x28\x47\x24\x02\x40\xbb\x00\x60\x55\x81\x52\x2c\x02\xc0\xc2\x00\ +\xa0\xac\x40\x22\x2e\x04\xc0\xae\x01\x80\x59\xb6\x32\x47\x02\x80\ +\xbd\x05\x00\x76\x8e\x58\x90\x0f\x40\x60\x00\x80\x99\x42\x2c\xcc\ +\x00\x20\x38\x02\x00\x43\x1e\x13\xcd\x03\x20\x4c\x03\xa0\x30\xd2\ +\xbf\xe0\xa9\x5f\x70\x85\xb8\x48\x01\x00\xc0\xcb\x95\xcd\x97\x4b\ +\xd2\x33\x14\xb8\x95\xd0\x1a\x77\xf2\xf0\xe0\xe2\x21\xe2\xc2\x6c\ +\xb1\x42\x61\x17\x29\x10\x66\x09\xe4\x22\x9c\x97\x9b\x23\x13\x48\ +\xe7\x03\x4c\xce\x0c\x00\x00\x1a\xf9\xd1\xc1\xfe\x38\x3f\x90\xe7\ +\xe6\xe4\xe1\xe6\x66\xe7\x6c\xef\xf4\xc5\xa2\xfe\x6b\xf0\x6f\x22\ +\x3e\x21\xf1\xdf\xfe\xbc\x8c\x02\x04\x00\x10\x4e\xcf\xef\xda\x5f\ +\xe5\xe5\xd6\x03\x70\xc7\x01\xb0\x75\xbf\x6b\xa9\x5b\x00\xda\x56\ +\x00\x68\xdf\xf9\x5d\x33\xdb\x09\xa0\x5a\x0a\xd0\x7a\xf9\x8b\x79\ +\x38\xfc\x40\x1e\x9e\xa1\x50\xc8\x3c\x1d\x1c\x0a\x0b\x0b\xed\x25\ +\x62\xa1\xbd\x30\xe3\x8b\x3e\xff\x33\xe1\x6f\xe0\x8b\x7e\xf6\xfc\ +\x40\x1e\xfe\xdb\x7a\xf0\x00\x71\x9a\x40\x99\xad\xc0\xa3\x83\xfd\ +\x71\x61\x6e\x76\xae\x52\x8e\xe7\xcb\x04\x42\x31\x6e\xf7\xe7\x23\ +\xfe\xc7\x85\x7f\xfd\x8e\x29\xd1\xe2\x34\xb1\x5c\x2c\x15\x8a\xf1\ +\x58\x89\xb8\x50\x22\x4d\xc7\x79\xb9\x52\x91\x44\x21\xc9\x95\xe2\ +\x12\xe9\x7f\x32\xf1\x1f\x96\xfd\x09\x93\x77\x0d\x00\xac\x86\x4f\ +\xc0\x4e\xb6\x07\xb5\xcb\x6c\xc0\x7e\xee\x01\x02\x8b\x0e\x58\xd2\ +\x76\x00\x40\x7e\xf3\x2d\x8c\x1a\x0b\x91\x00\x10\x67\x34\x32\x79\ +\xf7\x00\x00\x93\xbf\xf9\x8f\x40\x2b\x01\x00\xcd\x97\xa4\xe3\x00\ +\x00\xbc\xe8\x18\x5c\xa8\x94\x17\x4c\xc6\x08\x00\x00\x44\xa0\x81\ +\x2a\xb0\x41\x07\x0c\xc1\x14\xac\xc0\x0e\x9c\xc1\x1d\xbc\xc0\x17\ +\x02\x61\x06\x44\x40\x0c\x24\xc0\x3c\x10\x42\x06\xe4\x80\x1c\x0a\ +\xa1\x18\x96\x41\x19\x54\xc0\x3a\xd8\x04\xb5\xb0\x03\x1a\xa0\x11\ +\x9a\xe1\x10\xb4\xc1\x31\x38\x0d\xe7\xe0\x12\x5c\x81\xeb\x70\x17\ +\x06\x60\x18\x9e\xc2\x18\xbc\x86\x09\x04\x41\xc8\x08\x13\x61\x21\ +\x3a\x88\x11\x62\x8e\xd8\x22\xce\x08\x17\x99\x8e\x04\x22\x61\x48\ +\x34\x92\x80\xa4\x20\xe9\x88\x14\x51\x22\xc5\xc8\x72\xa4\x02\xa9\ +\x42\x6a\x91\x5d\x48\x23\xf2\x2d\x72\x14\x39\x8d\x5c\x40\xfa\x90\ +\xdb\xc8\x20\x32\x8a\xfc\x8a\xbc\x47\x31\x94\x81\xb2\x51\x03\xd4\ +\x02\x75\x40\xb9\xa8\x1f\x1a\x8a\xc6\xa0\x73\xd1\x74\x34\x0f\x5d\ +\x80\x96\xa2\x6b\xd1\x1a\xb4\x1e\x3d\x80\xb6\xa2\xa7\xd1\x4b\xe8\ +\x75\x74\x00\x7d\x8a\x8e\x63\x80\xd1\x31\x0e\x66\x8c\xd9\x61\x5c\ +\x8c\x87\x45\x60\x89\x58\x1a\x26\xc7\x16\x63\xe5\x58\x35\x56\x8f\ +\x35\x63\x1d\x58\x37\x76\x15\x1b\xc0\x9e\x61\xef\x08\x24\x02\x8b\ +\x80\x13\xec\x08\x5e\x84\x10\xc2\x6c\x82\x90\x90\x47\x58\x4c\x58\ +\x43\xa8\x25\xec\x23\xb4\x12\xba\x08\x57\x09\x83\x84\x31\xc2\x27\ +\x22\x93\xa8\x4f\xb4\x25\x7a\x12\xf9\xc4\x78\x62\x3a\xb1\x90\x58\ +\x46\xac\x26\xee\x21\x1e\x21\x9e\x25\x5e\x27\x0e\x13\x5f\x93\x48\ +\x24\x0e\xc9\x92\xe4\x4e\x0a\x21\x25\x90\x32\x49\x0b\x49\x6b\x48\ +\xdb\x48\x2d\xa4\x53\xa4\x3e\xd2\x10\x69\x9c\x4c\x26\xeb\x90\x6d\ +\xc9\xde\xe4\x08\xb2\x80\xac\x20\x97\x91\xb7\x90\x0f\x90\x4f\x92\ +\xfb\xc9\xc3\xe4\xb7\x14\x3a\xc5\x88\xe2\x4c\x09\xa2\x24\x52\xa4\ +\x94\x12\x4a\x35\x65\x3f\xe5\x04\xa5\x9f\x32\x42\x99\xa0\xaa\x51\ +\xcd\xa9\x9e\xd4\x08\xaa\x88\x3a\x9f\x5a\x49\x6d\xa0\x76\x50\x2f\ +\x53\x87\xa9\x13\x34\x75\x9a\x25\xcd\x9b\x16\x43\xcb\xa4\x2d\xa3\ +\xd5\xd0\x9a\x69\x67\x69\xf7\x68\x2f\xe9\x74\xba\x09\xdd\x83\x1e\ +\x45\x97\xd0\x97\xd2\x6b\xe8\x07\xe9\xe7\xe9\x83\xf4\x77\x0c\x0d\ +\x86\x0d\x83\xc7\x48\x62\x28\x19\x6b\x19\x7b\x19\xa7\x18\xb7\x19\ +\x2f\x99\x4c\xa6\x05\xd3\x97\x99\xc8\x54\x30\xd7\x32\x1b\x99\x67\ +\x98\x0f\x98\x6f\x55\x58\x2a\xf6\x2a\x7c\x15\x91\xca\x12\x95\x3a\ +\x95\x56\x95\x7e\x95\xe7\xaa\x54\x55\x73\x55\x3f\xd5\x79\xaa\x0b\ +\x54\xab\x55\x0f\xab\x5e\x56\x7d\xa6\x46\x55\xb3\x50\xe3\xa9\x09\ +\xd4\x16\xab\xd5\xa9\x1d\x55\xbb\xa9\x36\xae\xce\x52\x77\x52\x8f\ +\x50\xcf\x51\x5f\xa3\xbe\x5f\xfd\x82\xfa\x63\x0d\xb2\x86\x85\x46\ +\xa0\x86\x48\xa3\x54\x63\xb7\xc6\x19\x8d\x21\x16\xc6\x32\x65\xf1\ +\x58\x42\xd6\x72\x56\x03\xeb\x2c\x6b\x98\x4d\x62\x5b\xb2\xf9\xec\ +\x4c\x76\x05\xfb\x1b\x76\x2f\x7b\x4c\x53\x43\x73\xaa\x66\xac\x66\ +\x91\x66\x9d\xe6\x71\xcd\x01\x0e\xc6\xb1\xe0\xf0\x39\xd9\x9c\x4a\ +\xce\x21\xce\x0d\xce\x7b\x2d\x03\x2d\x3f\x2d\xb1\xd6\x6a\xad\x66\ +\xad\x7e\xad\x37\xda\x7a\xda\xbe\xda\x62\xed\x72\xed\x16\xed\xeb\ +\xda\xef\x75\x70\x9d\x40\x9d\x2c\x9d\xf5\x3a\x6d\x3a\xf7\x75\x09\ +\xba\x36\xba\x51\xba\x85\xba\xdb\x75\xcf\xea\x3e\xd3\x63\xeb\x79\ +\xe9\x09\xf5\xca\xf5\x0e\xe9\xdd\xd1\x47\xf5\x6d\xf4\xa3\xf5\x17\ +\xea\xef\xd6\xef\xd1\x1f\x37\x30\x34\x08\x36\x90\x19\x6c\x31\x38\ +\x63\xf0\xcc\x90\x63\xe8\x6b\x98\x69\xb8\xd1\xf0\x84\xe1\xa8\x11\ +\xcb\x68\xba\x91\xc4\x68\xa3\xd1\x49\xa3\x27\xb8\x26\xee\x87\x67\ +\xe3\x35\x78\x17\x3e\x66\xac\x6f\x1c\x62\xac\x34\xde\x65\xdc\x6b\ +\x3c\x61\x62\x69\x32\xdb\xa4\xc4\xa4\xc5\xe4\xbe\x29\xcd\x94\x6b\ +\x9a\x66\xba\xd1\xb4\xd3\x74\xcc\xcc\xc8\x2c\xdc\xac\xd8\xac\xc9\ +\xec\x8e\x39\xd5\x9c\x6b\x9e\x61\xbe\xd9\xbc\xdb\xfc\x8d\x85\xa5\ +\x45\x9c\xc5\x4a\x8b\x36\x8b\xc7\x96\xda\x96\x7c\xcb\x05\x96\x4d\ +\x96\xf7\xac\x98\x56\x3e\x56\x79\x56\xf5\x56\xd7\xac\x49\xd6\x5c\ +\xeb\x2c\xeb\x6d\xd6\x57\x6c\x50\x1b\x57\x9b\x0c\x9b\x3a\x9b\xcb\ +\xb6\xa8\xad\x9b\xad\xc4\x76\x9b\x6d\xdf\x14\xe2\x14\x8f\x29\xd2\ +\x29\xf5\x53\x6e\xda\x31\xec\xfc\xec\x0a\xec\x9a\xec\x06\xed\x39\ +\xf6\x61\xf6\x25\xf6\x6d\xf6\xcf\x1d\xcc\x1c\x12\x1d\xd6\x3b\x74\ +\x3b\x7c\x72\x74\x75\xcc\x76\x6c\x70\xbc\xeb\xa4\xe1\x34\xc3\xa9\ +\xc4\xa9\xc3\xe9\x57\x67\x1b\x67\xa1\x73\x9d\xf3\x35\x17\xa6\x4b\ +\x90\xcb\x12\x97\x76\x97\x17\x53\x6d\xa7\x8a\xa7\x6e\x9f\x7a\xcb\ +\x95\xe5\x1a\xee\xba\xd2\xb5\xd3\xf5\xa3\x9b\xbb\x9b\xdc\xad\xd9\ +\x6d\xd4\xdd\xcc\x3d\xc5\x7d\xab\xfb\x4d\x2e\x9b\x1b\xc9\x5d\xc3\ +\x3d\xef\x41\xf4\xf0\xf7\x58\xe2\x71\xcc\xe3\x9d\xa7\x9b\xa7\xc2\ +\xf3\x90\xe7\x2f\x5e\x76\x5e\x59\x5e\xfb\xbd\x1e\x4f\xb3\x9c\x26\ +\x9e\xd6\x30\x6d\xc8\xdb\xc4\x5b\xe0\xbd\xcb\x7b\x60\x3a\x3e\x3d\ +\x65\xfa\xce\xe9\x03\x3e\xc6\x3e\x02\x9f\x7a\x9f\x87\xbe\xa6\xbe\ +\x22\xdf\x3d\xbe\x23\x7e\xd6\x7e\x99\x7e\x07\xfc\x9e\xfb\x3b\xfa\ +\xcb\xfd\x8f\xf8\xbf\xe1\x79\xf2\x16\xf1\x4e\x05\x60\x01\xc1\x01\ +\xe5\x01\xbd\x81\x1a\x81\xb3\x03\x6b\x03\x1f\x04\x99\x04\xa5\x07\ +\x35\x05\x8d\x05\xbb\x06\x2f\x0c\x3e\x15\x42\x0c\x09\x0d\x59\x1f\ +\x72\x93\x6f\xc0\x17\xf2\x1b\xf9\x63\x33\xdc\x67\x2c\x9a\xd1\x15\ +\xca\x08\x9d\x15\x5a\x1b\xfa\x30\xcc\x26\x4c\x1e\xd6\x11\x8e\x86\ +\xcf\x08\xdf\x10\x7e\x6f\xa6\xf9\x4c\xe9\xcc\xb6\x08\x88\xe0\x47\ +\x6c\x88\xb8\x1f\x69\x19\x99\x17\xf9\x7d\x14\x29\x2a\x32\xaa\x2e\ +\xea\x51\xb4\x53\x74\x71\x74\xf7\x2c\xd6\xac\xe4\x59\xfb\x67\xbd\ +\x8e\xf1\x8f\xa9\x8c\xb9\x3b\xdb\x6a\xb6\x72\x76\x67\xac\x6a\x6c\ +\x52\x6c\x63\xec\x9b\xb8\x80\xb8\xaa\xb8\x81\x78\x87\xf8\x45\xf1\ +\x97\x12\x74\x13\x24\x09\xed\x89\xe4\xc4\xd8\xc4\x3d\x89\xe3\x73\ +\x02\xe7\x6c\x9a\x33\x9c\xe4\x9a\x54\x96\x74\x63\xae\xe5\xdc\xa2\ +\xb9\x17\xe6\xe9\xce\xcb\x9e\x77\x3c\x59\x35\x59\x90\x7c\x38\x85\ +\x98\x12\x97\xb2\x3f\xe5\x83\x20\x42\x50\x2f\x18\x4f\xe5\xa7\x6e\ +\x4d\x1d\x13\xf2\x84\x9b\x85\x4f\x45\xbe\xa2\x8d\xa2\x51\xb1\xb7\ +\xb8\x4a\x3c\x92\xe6\x9d\x56\x95\xf6\x38\xdd\x3b\x7d\x43\xfa\x68\ +\x86\x4f\x46\x75\xc6\x33\x09\x4f\x52\x2b\x79\x91\x19\x92\xb9\x23\ +\xf3\x4d\x56\x44\xd6\xde\xac\xcf\xd9\x71\xd9\x2d\x39\x94\x9c\x94\ +\x9c\xa3\x52\x0d\x69\x96\xb4\x2b\xd7\x30\xb7\x28\xb7\x4f\x66\x2b\ +\x2b\x93\x0d\xe4\x79\xe6\x6d\xca\x1b\x93\x87\xca\xf7\xe4\x23\xf9\ +\x73\xf3\xdb\x15\x6c\x85\x4c\xd1\xa3\xb4\x52\xae\x50\x0e\x16\x4c\ +\x2f\xa8\x2b\x78\x5b\x18\x5b\x78\xb8\x48\xbd\x48\x5a\xd4\x33\xdf\ +\x66\xfe\xea\xf9\x23\x0b\x82\x16\x7c\xbd\x90\xb0\x50\xb8\xb0\xb3\ +\xd8\xb8\x78\x59\xf1\xe0\x22\xbf\x45\xbb\x16\x23\x8b\x53\x17\x77\ +\x2e\x31\x5d\x52\xba\x64\x78\x69\xf0\xd2\x7d\xcb\x68\xcb\xb2\x96\ +\xfd\x50\xe2\x58\x52\x55\xf2\x6a\x79\xdc\xf2\x8e\x52\x83\xd2\xa5\ +\xa5\x43\x2b\x82\x57\x34\x95\xa9\x94\xc9\xcb\x6e\xae\xf4\x5a\xb9\ +\x63\x15\x61\x95\x64\x55\xef\x6a\x97\xd5\x5b\x56\x7f\x2a\x17\x95\ +\x5f\xac\x70\xac\xa8\xae\xf8\xb0\x46\xb8\xe6\xe2\x57\x4e\x5f\xd5\ +\x7c\xf5\x79\x6d\xda\xda\xde\x4a\xb7\xca\xed\xeb\x48\xeb\xa4\xeb\ +\x6e\xac\xf7\x59\xbf\xaf\x4a\xbd\x6a\x41\xd5\xd0\x86\xf0\x0d\xad\ +\x1b\xf1\x8d\xe5\x1b\x5f\x6d\x4a\xde\x74\xa1\x7a\x6a\xf5\x8e\xcd\ +\xb4\xcd\xca\xcd\x03\x35\x61\x35\xed\x5b\xcc\xb6\xac\xdb\xf2\xa1\ +\x36\xa3\xf6\x7a\x9d\x7f\x5d\xcb\x56\xfd\xad\xab\xb7\xbe\xd9\x26\ +\xda\xd6\xbf\xdd\x77\x7b\xf3\x0e\x83\x1d\x15\x3b\xde\xef\x94\xec\ +\xbc\xb5\x2b\x78\x57\x6b\xbd\x45\x7d\xf5\x6e\xd2\xee\x82\xdd\x8f\ +\x1a\x62\x1b\xba\xbf\xe6\x7e\xdd\xb8\x47\x77\x4f\xc5\x9e\x8f\x7b\ +\xa5\x7b\x07\xf6\x45\xef\xeb\x6a\x74\x6f\x6c\xdc\xaf\xbf\xbf\xb2\ +\x09\x6d\x52\x36\x8d\x1e\x48\x3a\x70\xe5\x9b\x80\x6f\xda\x9b\xed\ +\x9a\x77\xb5\x70\x5a\x2a\x0e\xc2\x41\xe5\xc1\x27\xdf\xa6\x7c\x7b\ +\xe3\x50\xe8\xa1\xce\xc3\xdc\xc3\xcd\xdf\x99\x7f\xb7\xf5\x08\xeb\ +\x48\x79\x2b\xd2\x3a\xbf\x75\xac\x2d\xa3\x6d\xa0\x3d\xa1\xbd\xef\ +\xe8\x8c\xa3\x9d\x1d\x5e\x1d\x47\xbe\xb7\xff\x7e\xef\x31\xe3\x63\ +\x75\xc7\x35\x8f\x57\x9e\xa0\x9d\x28\x3d\xf1\xf9\xe4\x82\x93\xe3\ +\xa7\x64\xa7\x9e\x9d\x4e\x3f\x3d\xd4\x99\xdc\x79\xf7\x4c\xfc\x99\ +\x6b\x5d\x51\x5d\xbd\x67\x43\xcf\x9e\x3f\x17\x74\xee\x4c\xb7\x5f\ +\xf7\xc9\xf3\xde\xe7\x8f\x5d\xf0\xbc\x70\xf4\x22\xf7\x62\xdb\x25\ +\xb7\x4b\xad\x3d\xae\x3d\x47\x7e\x70\xfd\xe1\x48\xaf\x5b\x6f\xeb\ +\x65\xf7\xcb\xed\x57\x3c\xae\x74\xf4\x4d\xeb\x3b\xd1\xef\xd3\x7f\ +\xfa\x6a\xc0\xd5\x73\xd7\xf8\xd7\x2e\x5d\x9f\x79\xbd\xef\xc6\xec\ +\x1b\xb7\x6e\x26\xdd\x1c\xb8\x25\xba\xf5\xf8\x76\xf6\xed\x17\x77\ +\x0a\xee\x4c\xdc\x5d\x7a\x8f\x78\xaf\xfc\xbe\xda\xfd\xea\x07\xfa\ +\x0f\xea\x7f\xb4\xfe\xb1\x65\xc0\x6d\xe0\xf8\x60\xc0\x60\xcf\xc3\ +\x59\x0f\xef\x0e\x09\x87\x9e\xfe\x94\xff\xd3\x87\xe1\xd2\x47\xcc\ +\x47\xd5\x23\x46\x23\x8d\x8f\x9d\x1f\x1f\x1b\x0d\x1a\xbd\xf2\x64\ +\xce\x93\xe1\xa7\xb2\xa7\x13\xcf\xca\x7e\x56\xff\x79\xeb\x73\xab\ +\xe7\xdf\xfd\xe2\xfb\x4b\xcf\x58\xfc\xd8\xf0\x0b\xf9\x8b\xcf\xbf\ +\xae\x79\xa9\xf3\x72\xef\xab\xa9\xaf\x3a\xc7\x23\xc7\x1f\xbc\xce\ +\x79\x3d\xf1\xa6\xfc\xad\xce\xdb\x7d\xef\xb8\xef\xba\xdf\xc7\xbd\ +\x1f\x99\x28\xfc\x40\xfe\x50\xf3\xd1\xfa\x63\xc7\xa7\xd0\x4f\xf7\ +\x3e\xe7\x7c\xfe\xfc\x2f\xf7\x84\xf3\xfb\x25\xd2\x9f\x33\x00\x00\ +\x00\x20\x63\x48\x52\x4d\x00\x00\x7a\x25\x00\x00\x80\x83\x00\x00\ +\xf9\xff\x00\x00\x80\xe9\x00\x00\x75\x30\x00\x00\xea\x60\x00\x00\ +\x3a\x98\x00\x00\x17\x6f\x92\x5f\xc5\x46\x00\x00\x06\x12\x49\x44\ +\x41\x54\x78\xda\x7c\x95\x5b\x88\x9c\x67\x1d\xc6\x7f\xef\xe9\x3b\ +\xce\x69\x67\x27\x9b\x60\x36\x49\x37\xd1\xda\x1c\xbd\xe8\x42\x41\ +\x94\x62\xa3\xb4\xf5\xc2\xda\xa4\x04\x04\x6b\xbd\xb2\x45\x6c\xb0\ +\x08\x52\x4d\xc1\x88\x17\x45\xd4\xd6\x96\x5e\xa9\x45\xad\x85\xea\ +\x85\x42\xab\x22\xc4\xa2\xa6\x34\x90\x50\x85\x66\xb3\x49\x9a\xc3\ +\xe6\xb0\x87\xc9\xcc\xec\xce\xec\xee\xcc\xce\xcc\x37\x87\xef\x7b\ +\xbd\x98\xdd\x69\x6a\xc0\x17\x5e\xf8\x2e\xbe\x97\xe7\xff\x7f\x9e\ +\xff\xf3\xfc\x85\xb5\x96\x38\x8e\xd9\x38\xd6\x5a\x84\x10\xc3\xef\ +\x8d\x23\xa5\xa4\x5a\xad\xde\x77\xe9\xd2\x95\x43\xfd\x7e\x7c\x5f\ +\x2e\x97\x5d\xcc\x8d\x64\xcf\x14\x36\x15\xfe\x1e\x06\xc1\xd9\x24\ +\x4e\x6e\xff\x5f\x00\x56\x6b\x8d\xf8\x3f\x00\xc2\xae\xbf\x50\x4a\ +\xb1\xb4\x54\x7d\x78\x6a\xea\xdc\x1f\xd7\x9a\x4d\x7f\x74\x34\xcf\ +\x68\x61\x94\x54\x2a\x44\x49\xd9\xd6\x4a\xff\xd2\xf7\xfd\x17\x83\ +\x20\xb8\x01\x90\x24\x16\xb0\x18\x63\x90\xdc\x79\x04\xa0\x01\x29\ +\x84\x40\x08\x81\xb5\xd6\xdc\x2a\x95\x9e\xeb\xc7\x89\x3f\x36\xb6\ +\x99\x5c\x2e\x87\x1f\x78\x78\xbe\x4f\xa1\xb0\xc9\xcf\xe7\xf3\x47\ +\x3b\x9d\xce\x7b\xd5\x6a\xf5\xcb\x83\x37\xc8\x75\x12\x3e\x0a\x60\ +\xad\xd5\xc0\x56\xc0\x02\xf1\x46\xf5\x37\x6e\xce\xfe\x60\x6e\xbe\ +\xf8\xe9\x54\x2a\x24\x08\x7c\x3c\xcf\x43\x0a\x09\xd6\x12\xf7\xfb\ +\x48\x29\x29\x14\x0a\x05\xcf\xf3\xfe\x74\xeb\xd6\xad\x9f\x4a\x29\ +\x13\x25\xe5\x1d\x00\x42\x08\xe1\x0a\x21\x1a\x40\x6c\xad\x45\x29\ +\xc5\xe2\x52\xf5\xfe\x0f\x3e\xb8\xfc\xdd\xc0\xf7\xc8\x64\x32\xf8\ +\x81\x8b\x71\x34\x8e\xe3\xe0\xba\x2e\x5a\x6b\x00\xfa\xfd\x3e\xe9\ +\x74\x5a\xcc\xce\xcf\x7f\xe7\xf2\x95\xab\xdf\x16\x52\xdd\xd9\x01\ +\xd0\x02\x56\x37\x74\x88\xa2\x28\x3d\x7d\x6e\xfa\x65\x29\x85\x49\ +\xa5\x52\x08\x09\xc6\x18\x8c\x31\x03\x00\xc7\x41\x29\x85\xb5\x96\ +\x5e\xaf\xc7\x8d\x9b\x37\x89\xa2\x88\x99\x99\x6b\xc7\xda\xed\x68\ +\xfb\xff\x02\xd8\xf5\xcb\x06\x35\x57\x67\xae\xfd\xb0\xde\x68\x1c\ +\xc8\x66\xb3\xb8\xae\x8b\xeb\x3a\x43\x00\xd7\x71\x91\x52\x21\x84\ +\x20\x8e\x63\x9a\xad\x26\xa5\x72\x89\x7e\x3f\x46\x08\x59\x28\x16\ +\x8b\x07\x59\x17\xf3\xa3\x0a\x0b\x31\xa0\x66\x71\xe9\xa1\xab\x57\ +\x67\x9e\xc9\xe7\xf3\xa4\xd3\x69\x5c\xd7\xc5\x71\x34\x4a\x2b\x1c\ +\xc7\xc1\x68\x85\x12\x82\x24\x49\xe8\x74\x3a\x94\x2b\x15\x5a\xad\ +\x08\x81\x20\x95\x0a\x69\x34\xd6\x76\x0c\x01\x36\xe6\x7e\x83\xcb\ +\x4e\xb7\xf7\xd9\xe9\xe9\xf3\xbf\x33\xc6\x90\xcd\x66\x51\x5a\xa1\ +\x8d\x1c\x56\xee\x1a\x83\x94\x0a\x84\xa0\xdb\xeb\x52\xad\x55\xa9\ +\x54\x16\xc1\x0e\x3a\x0f\x82\x80\x42\xa1\x30\x37\x04\x28\x95\xcb\ +\xb7\xf1\x64\x9d\xd9\x9b\xf3\xc7\x56\xeb\x6b\x85\xf1\xf1\x8f\xe1\ +\x98\x01\xd7\xc6\x31\x38\xc6\xe0\x1a\x07\xa5\x34\x52\x08\xe2\x24\ +\xa1\xd5\x6c\x51\x2a\x55\x48\xe2\x81\x9f\xb4\x56\x6c\x1a\x2b\xcc\ +\x6f\xd9\x32\x76\x62\x08\x50\xab\xad\x0e\xe9\xe9\xf5\xbb\x3f\x29\ +\x97\xcb\x0f\xe6\xf3\x79\xc2\x20\xc0\x38\x1a\x6d\x14\x46\x1b\x8c\ +\x71\xd0\x4a\x0f\xbc\x01\xf4\xba\x5d\xca\xe5\x32\xf5\x46\x1d\x81\ +\x44\x6b\xcd\x68\x61\x94\x5c\x2e\xfb\x4d\x0b\x73\x43\x91\x93\xb8\ +\x4f\x12\xf7\x11\xd8\xc9\xea\x52\xf5\x29\xcf\xf3\xc8\x66\xb2\x08\ +\x25\xd1\x46\xa1\xb5\x46\x1b\x83\x31\x1a\xa9\xe4\x50\xd8\x95\xd5\ +\x15\xca\xe5\x0a\xd8\x81\x16\x41\x10\x60\xb4\x79\xa7\xdd\x6e\xff\ +\xb9\x56\xab\x31\xec\xc0\x0a\x4b\xe0\xfb\x74\x3b\xdd\xef\x09\x81\ +\xb3\x7d\xfb\x36\x46\x46\x72\x08\x29\xb0\x76\x60\x79\x47\x1b\xa4\ +\x52\x43\xbd\xa2\x4e\xc4\xad\x52\x89\x28\x6a\x23\x84\xc4\xf7\x03\ +\xa4\x94\x9d\x46\xa3\xfe\x7d\x4b\x42\x1c\xc7\x6c\x1e\x1b\x1b\x00\ +\xf8\xae\xcf\xc5\x0b\x97\x9f\x3d\x79\xf2\xe4\xa1\x38\x49\xd8\xb1\ +\x7d\x1b\xfb\x3f\xb5\x8f\xc9\xc9\x7b\xc9\xa4\x33\xd8\x64\x10\x64\ +\xd6\x5a\x04\x82\x5e\xaf\xc7\x62\xb5\xca\x62\x65\x09\x90\xb8\xae\ +\x4f\x36\x9b\xa5\xde\x58\xfd\x79\x92\xc4\xa7\x1a\x6b\x0d\x7c\xdf\ +\xff\xb0\x83\xf7\xdf\x3f\xfb\xf4\x0b\x3f\x7b\xe9\xf9\x95\x95\x65\ +\xda\x51\x9b\x28\x8a\x30\xc6\xe1\x9e\xdd\xf7\xf0\xc4\xd7\x1e\xe7\ +\xc8\x91\xc3\xb8\xae\x03\x40\xaf\xd7\xa3\xde\xa8\x33\x37\x3b\x47\ +\xb7\xdb\xc3\xf3\x3c\xc2\x30\xa0\xd3\x69\xcf\xb4\xdb\xad\x17\x93\ +\x24\x21\x9d\x4e\x33\x31\x31\xf1\xa1\x93\xaf\x5c\xbe\xf2\x85\x7e\ +\xbf\x47\x3a\x9d\x26\x93\xce\x90\xcb\xe5\x30\x8e\x61\x6a\x6a\x8a\ +\x6f\x3d\x7d\x94\x43\x87\x8f\x70\x76\xea\xdc\xa0\x22\xad\x29\x97\ +\x2b\xac\x2c\xaf\xe0\x38\x0e\x99\x4c\x06\xd7\x73\x58\x6b\x36\x7f\ +\x0c\x94\x3d\xcf\x63\xc7\x8e\x1d\x28\x75\x5b\x54\x64\x32\xd9\x33\ +\x5a\xeb\xa1\x4b\xcd\xfa\x38\x66\x32\x19\x46\xf3\x79\xce\x9c\x79\ +\x8f\x47\x1f\x7d\x8c\x37\xde\xf8\x03\xed\x28\x62\xad\xb9\x86\xe7\ +\xf9\xa4\xd2\x69\x82\xd0\xa3\xb9\xd6\x7c\x27\x8e\x93\x5f\x09\xa1\ +\xd8\xba\x75\x9c\x30\x0c\x48\x92\x84\x8d\xcc\xe7\xcd\x37\xff\x3a\ +\xfe\xda\x6f\x5f\xfb\xf7\xc2\x42\x71\xf3\x7a\x06\x11\xc7\x31\x71\ +\x1c\x93\x24\x09\x16\x4b\xd4\xed\x12\xb5\xdb\x3c\xf1\xf5\xaf\xf2\ +\xc8\x23\x5f\xa2\xdb\xe9\x91\xcd\x65\x00\x4b\xad\xb6\xfc\xe0\xc1\ +\xd7\x5f\xdf\x4a\xb7\x7b\x94\xe5\xe5\x2d\x94\xcb\xff\xe2\xc6\x8d\ +\x63\x34\x9b\xd7\xd4\xf1\xe3\xc7\x99\x2f\x16\xeb\xa9\x30\xd5\xbb\ +\x70\xfe\xc2\x43\x1b\x29\xba\x11\x19\x52\x4a\xa4\x50\x38\xeb\x63\ +\x3a\x75\xf6\x1c\xdb\xc6\xc7\xf9\xc4\x27\x3f\x4e\x10\xf8\x34\x1a\ +\x8d\x13\xf7\xbf\xf2\x4a\x85\x56\xeb\x17\xcc\xcc\x6c\x61\x6e\x2e\ +\x45\xad\xb6\xcf\xd6\xeb\x0f\xb7\xba\xdd\x53\x72\x7d\x11\xb0\x7b\ +\xef\xee\x97\x1e\x78\xe0\x73\x2f\xa8\xf5\x00\xf3\x3c\xef\xb6\xeb\ +\xe2\x3a\x0e\xa1\x1f\xe2\xb9\x1e\x6f\xbd\xf5\x17\xb4\xd6\x24\x49\ +\xd2\xed\xf7\xfb\x3f\x62\x69\xe9\x28\x17\x2f\xc2\xf4\x34\x5c\xbf\ +\x8e\x2d\x95\x58\x8a\xa2\xbb\x17\x92\xe4\x1b\x7a\xb8\x26\x81\x7b\ +\x27\x27\x9f\x03\x11\x9e\x3e\x7d\xfa\xc9\x56\xab\x35\xd0\xc2\x75\ +\x07\x54\xc5\x31\x71\x92\x60\x1c\xc3\xd2\x62\x95\xe9\xa9\x69\xf6\ +\xee\xdf\xf3\x1b\x63\xcc\xbb\x5c\xbf\x3e\x4a\xb5\x0a\x51\x34\xf0\ +\x08\x03\x1b\x97\x61\x6c\xbd\x03\x81\x4d\x20\xf0\xfd\xf6\xfe\xfd\ +\xfb\x9e\x3a\xf8\xf9\x83\x5f\x99\xd8\x39\x31\xe7\x38\x83\xd1\x74\ +\x1c\x07\x3f\x08\xf0\x7d\x9f\x30\x0c\x09\x82\x80\x85\x62\xb1\xa1\ +\xb5\x7e\x39\x0c\x43\xfa\xb3\xb3\xa7\xd6\xea\x75\x1a\x40\x1d\x98\ +\x01\x2e\x01\x25\xf8\xa7\x1e\x6e\x02\x40\x29\x29\xc3\x54\x68\xef\ +\x9a\xb8\xeb\xf7\x23\xf9\x91\xbf\x2d\x2c\x2c\x1c\x2e\x16\x8b\x8f\ +\x57\xab\xb5\xcf\xb4\x5a\x2d\x8d\x10\x60\x2d\x13\xbb\x26\x5a\x07\ +\x0e\xec\x7f\x46\x08\x71\x1e\xa0\x12\xc7\xcf\x56\x60\x67\x03\x0e\ +\xd4\x81\x79\x88\x5b\xf0\xbc\x07\xaf\x0a\x6b\x2d\x6f\xbf\xfd\x8f\ +\xe1\x9e\xb1\xd6\xb2\x5a\x5f\x8b\xe3\x38\x26\x8a\x22\x6a\xb5\x1a\ +\x9d\x4e\x77\x52\x29\xf9\x45\x21\xb9\x3b\x97\xcd\xad\xec\xda\xb5\ +\xf3\xd7\xb9\x91\xec\x7f\xa4\x12\x68\xa5\x49\xed\xd9\xcb\x02\xec\ +\x99\x85\x27\x17\x61\xbc\x09\x27\x1c\x78\xd5\x87\xfe\x7f\x07\x00\ +\x06\x52\xb7\xcc\xfb\x20\x61\xb3\x00\x00\x00\x00\x49\x45\x4e\x44\ +\xae\x42\x60\x82\ +\x00\x00\x10\xa4\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\ +\x01\x00\x9a\x9c\x18\x00\x00\x0a\x4d\x69\x43\x43\x50\x50\x68\x6f\ +\x74\x6f\x73\x68\x6f\x70\x20\x49\x43\x43\x20\x70\x72\x6f\x66\x69\ +\x6c\x65\x00\x00\x78\xda\x9d\x53\x77\x58\x93\xf7\x16\x3e\xdf\xf7\ +\x65\x0f\x56\x42\xd8\xf0\xb1\x97\x6c\x81\x00\x22\x23\xac\x08\xc8\ +\x10\x59\xa2\x10\x92\x00\x61\x84\x10\x12\x40\xc5\x85\x88\x0a\x56\ +\x14\x15\x11\x9c\x48\x55\xc4\x82\xd5\x0a\x48\x9d\x88\xe2\xa0\x28\ +\xb8\x67\x41\x8a\x88\x5a\x8b\x55\x5c\x38\xee\x1f\xdc\xa7\xb5\x7d\ +\x7a\xef\xed\xed\xfb\xd7\xfb\xbc\xe7\x9c\xe7\xfc\xce\x79\xcf\x0f\ +\x80\x11\x12\x26\x91\xe6\xa2\x6a\x00\x39\x52\x85\x3c\x3a\xd8\x1f\ +\x8f\x4f\x48\xc4\xc9\xbd\x80\x02\x15\x48\xe0\x04\x20\x10\xe6\xcb\ +\xc2\x67\x05\xc5\x00\x00\xf0\x03\x79\x78\x7e\x74\xb0\x3f\xfc\x01\ +\xaf\x6f\x00\x02\x00\x70\xd5\x2e\x24\x12\xc7\xe1\xff\x83\xba\x50\ +\x26\x57\x00\x20\x91\x00\xe0\x22\x12\xe7\x0b\x01\x90\x52\x00\xc8\ +\x2e\x54\xc8\x14\x00\xc8\x18\x00\xb0\x53\xb3\x64\x0a\x00\x94\x00\ +\x00\x6c\x79\x7c\x42\x22\x00\xaa\x0d\x00\xec\xf4\x49\x3e\x05\x00\ +\xd8\xa9\x93\xdc\x17\x00\xd8\xa2\x1c\xa9\x08\x00\x8d\x01\x00\x99\ +\x28\x47\x24\x02\x40\xbb\x00\x60\x55\x81\x52\x2c\x02\xc0\xc2\x00\ +\xa0\xac\x40\x22\x2e\x04\xc0\xae\x01\x80\x59\xb6\x32\x47\x02\x80\ +\xbd\x05\x00\x76\x8e\x58\x90\x0f\x40\x60\x00\x80\x99\x42\x2c\xcc\ +\x00\x20\x38\x02\x00\x43\x1e\x13\xcd\x03\x20\x4c\x03\xa0\x30\xd2\ +\xbf\xe0\xa9\x5f\x70\x85\xb8\x48\x01\x00\xc0\xcb\x95\xcd\x97\x4b\ +\xd2\x33\x14\xb8\x95\xd0\x1a\x77\xf2\xf0\xe0\xe2\x21\xe2\xc2\x6c\ +\xb1\x42\x61\x17\x29\x10\x66\x09\xe4\x22\x9c\x97\x9b\x23\x13\x48\ +\xe7\x03\x4c\xce\x0c\x00\x00\x1a\xf9\xd1\xc1\xfe\x38\x3f\x90\xe7\ +\xe6\xe4\xe1\xe6\x66\xe7\x6c\xef\xf4\xc5\xa2\xfe\x6b\xf0\x6f\x22\ +\x3e\x21\xf1\xdf\xfe\xbc\x8c\x02\x04\x00\x10\x4e\xcf\xef\xda\x5f\ +\xe5\xe5\xd6\x03\x70\xc7\x01\xb0\x75\xbf\x6b\xa9\x5b\x00\xda\x56\ +\x00\x68\xdf\xf9\x5d\x33\xdb\x09\xa0\x5a\x0a\xd0\x7a\xf9\x8b\x79\ +\x38\xfc\x40\x1e\x9e\xa1\x50\xc8\x3c\x1d\x1c\x0a\x0b\x0b\xed\x25\ +\x62\xa1\xbd\x30\xe3\x8b\x3e\xff\x33\xe1\x6f\xe0\x8b\x7e\xf6\xfc\ +\x40\x1e\xfe\xdb\x7a\xf0\x00\x71\x9a\x40\x99\xad\xc0\xa3\x83\xfd\ +\x71\x61\x6e\x76\xae\x52\x8e\xe7\xcb\x04\x42\x31\x6e\xf7\xe7\x23\ +\xfe\xc7\x85\x7f\xfd\x8e\x29\xd1\xe2\x34\xb1\x5c\x2c\x15\x8a\xf1\ +\x58\x89\xb8\x50\x22\x4d\xc7\x79\xb9\x52\x91\x44\x21\xc9\x95\xe2\ +\x12\xe9\x7f\x32\xf1\x1f\x96\xfd\x09\x93\x77\x0d\x00\xac\x86\x4f\ +\xc0\x4e\xb6\x07\xb5\xcb\x6c\xc0\x7e\xee\x01\x02\x8b\x0e\x58\xd2\ +\x76\x00\x40\x7e\xf3\x2d\x8c\x1a\x0b\x91\x00\x10\x67\x34\x32\x79\ +\xf7\x00\x00\x93\xbf\xf9\x8f\x40\x2b\x01\x00\xcd\x97\xa4\xe3\x00\ +\x00\xbc\xe8\x18\x5c\xa8\x94\x17\x4c\xc6\x08\x00\x00\x44\xa0\x81\ +\x2a\xb0\x41\x07\x0c\xc1\x14\xac\xc0\x0e\x9c\xc1\x1d\xbc\xc0\x17\ +\x02\x61\x06\x44\x40\x0c\x24\xc0\x3c\x10\x42\x06\xe4\x80\x1c\x0a\ +\xa1\x18\x96\x41\x19\x54\xc0\x3a\xd8\x04\xb5\xb0\x03\x1a\xa0\x11\ +\x9a\xe1\x10\xb4\xc1\x31\x38\x0d\xe7\xe0\x12\x5c\x81\xeb\x70\x17\ +\x06\x60\x18\x9e\xc2\x18\xbc\x86\x09\x04\x41\xc8\x08\x13\x61\x21\ +\x3a\x88\x11\x62\x8e\xd8\x22\xce\x08\x17\x99\x8e\x04\x22\x61\x48\ +\x34\x92\x80\xa4\x20\xe9\x88\x14\x51\x22\xc5\xc8\x72\xa4\x02\xa9\ +\x42\x6a\x91\x5d\x48\x23\xf2\x2d\x72\x14\x39\x8d\x5c\x40\xfa\x90\ +\xdb\xc8\x20\x32\x8a\xfc\x8a\xbc\x47\x31\x94\x81\xb2\x51\x03\xd4\ +\x02\x75\x40\xb9\xa8\x1f\x1a\x8a\xc6\xa0\x73\xd1\x74\x34\x0f\x5d\ +\x80\x96\xa2\x6b\xd1\x1a\xb4\x1e\x3d\x80\xb6\xa2\xa7\xd1\x4b\xe8\ +\x75\x74\x00\x7d\x8a\x8e\x63\x80\xd1\x31\x0e\x66\x8c\xd9\x61\x5c\ +\x8c\x87\x45\x60\x89\x58\x1a\x26\xc7\x16\x63\xe5\x58\x35\x56\x8f\ +\x35\x63\x1d\x58\x37\x76\x15\x1b\xc0\x9e\x61\xef\x08\x24\x02\x8b\ +\x80\x13\xec\x08\x5e\x84\x10\xc2\x6c\x82\x90\x90\x47\x58\x4c\x58\ +\x43\xa8\x25\xec\x23\xb4\x12\xba\x08\x57\x09\x83\x84\x31\xc2\x27\ +\x22\x93\xa8\x4f\xb4\x25\x7a\x12\xf9\xc4\x78\x62\x3a\xb1\x90\x58\ +\x46\xac\x26\xee\x21\x1e\x21\x9e\x25\x5e\x27\x0e\x13\x5f\x93\x48\ +\x24\x0e\xc9\x92\xe4\x4e\x0a\x21\x25\x90\x32\x49\x0b\x49\x6b\x48\ +\xdb\x48\x2d\xa4\x53\xa4\x3e\xd2\x10\x69\x9c\x4c\x26\xeb\x90\x6d\ +\xc9\xde\xe4\x08\xb2\x80\xac\x20\x97\x91\xb7\x90\x0f\x90\x4f\x92\ +\xfb\xc9\xc3\xe4\xb7\x14\x3a\xc5\x88\xe2\x4c\x09\xa2\x24\x52\xa4\ +\x94\x12\x4a\x35\x65\x3f\xe5\x04\xa5\x9f\x32\x42\x99\xa0\xaa\x51\ +\xcd\xa9\x9e\xd4\x08\xaa\x88\x3a\x9f\x5a\x49\x6d\xa0\x76\x50\x2f\ +\x53\x87\xa9\x13\x34\x75\x9a\x25\xcd\x9b\x16\x43\xcb\xa4\x2d\xa3\ +\xd5\xd0\x9a\x69\x67\x69\xf7\x68\x2f\xe9\x74\xba\x09\xdd\x83\x1e\ +\x45\x97\xd0\x97\xd2\x6b\xe8\x07\xe9\xe7\xe9\x83\xf4\x77\x0c\x0d\ +\x86\x0d\x83\xc7\x48\x62\x28\x19\x6b\x19\x7b\x19\xa7\x18\xb7\x19\ +\x2f\x99\x4c\xa6\x05\xd3\x97\x99\xc8\x54\x30\xd7\x32\x1b\x99\x67\ +\x98\x0f\x98\x6f\x55\x58\x2a\xf6\x2a\x7c\x15\x91\xca\x12\x95\x3a\ +\x95\x56\x95\x7e\x95\xe7\xaa\x54\x55\x73\x55\x3f\xd5\x79\xaa\x0b\ +\x54\xab\x55\x0f\xab\x5e\x56\x7d\xa6\x46\x55\xb3\x50\xe3\xa9\x09\ +\xd4\x16\xab\xd5\xa9\x1d\x55\xbb\xa9\x36\xae\xce\x52\x77\x52\x8f\ +\x50\xcf\x51\x5f\xa3\xbe\x5f\xfd\x82\xfa\x63\x0d\xb2\x86\x85\x46\ +\xa0\x86\x48\xa3\x54\x63\xb7\xc6\x19\x8d\x21\x16\xc6\x32\x65\xf1\ +\x58\x42\xd6\x72\x56\x03\xeb\x2c\x6b\x98\x4d\x62\x5b\xb2\xf9\xec\ +\x4c\x76\x05\xfb\x1b\x76\x2f\x7b\x4c\x53\x43\x73\xaa\x66\xac\x66\ +\x91\x66\x9d\xe6\x71\xcd\x01\x0e\xc6\xb1\xe0\xf0\x39\xd9\x9c\x4a\ +\xce\x21\xce\x0d\xce\x7b\x2d\x03\x2d\x3f\x2d\xb1\xd6\x6a\xad\x66\ +\xad\x7e\xad\x37\xda\x7a\xda\xbe\xda\x62\xed\x72\xed\x16\xed\xeb\ +\xda\xef\x75\x70\x9d\x40\x9d\x2c\x9d\xf5\x3a\x6d\x3a\xf7\x75\x09\ +\xba\x36\xba\x51\xba\x85\xba\xdb\x75\xcf\xea\x3e\xd3\x63\xeb\x79\ +\xe9\x09\xf5\xca\xf5\x0e\xe9\xdd\xd1\x47\xf5\x6d\xf4\xa3\xf5\x17\ +\xea\xef\xd6\xef\xd1\x1f\x37\x30\x34\x08\x36\x90\x19\x6c\x31\x38\ +\x63\xf0\xcc\x90\x63\xe8\x6b\x98\x69\xb8\xd1\xf0\x84\xe1\xa8\x11\ +\xcb\x68\xba\x91\xc4\x68\xa3\xd1\x49\xa3\x27\xb8\x26\xee\x87\x67\ +\xe3\x35\x78\x17\x3e\x66\xac\x6f\x1c\x62\xac\x34\xde\x65\xdc\x6b\ +\x3c\x61\x62\x69\x32\xdb\xa4\xc4\xa4\xc5\xe4\xbe\x29\xcd\x94\x6b\ +\x9a\x66\xba\xd1\xb4\xd3\x74\xcc\xcc\xc8\x2c\xdc\xac\xd8\xac\xc9\ +\xec\x8e\x39\xd5\x9c\x6b\x9e\x61\xbe\xd9\xbc\xdb\xfc\x8d\x85\xa5\ +\x45\x9c\xc5\x4a\x8b\x36\x8b\xc7\x96\xda\x96\x7c\xcb\x05\x96\x4d\ +\x96\xf7\xac\x98\x56\x3e\x56\x79\x56\xf5\x56\xd7\xac\x49\xd6\x5c\ +\xeb\x2c\xeb\x6d\xd6\x57\x6c\x50\x1b\x57\x9b\x0c\x9b\x3a\x9b\xcb\ +\xb6\xa8\xad\x9b\xad\xc4\x76\x9b\x6d\xdf\x14\xe2\x14\x8f\x29\xd2\ +\x29\xf5\x53\x6e\xda\x31\xec\xfc\xec\x0a\xec\x9a\xec\x06\xed\x39\ +\xf6\x61\xf6\x25\xf6\x6d\xf6\xcf\x1d\xcc\x1c\x12\x1d\xd6\x3b\x74\ +\x3b\x7c\x72\x74\x75\xcc\x76\x6c\x70\xbc\xeb\xa4\xe1\x34\xc3\xa9\ +\xc4\xa9\xc3\xe9\x57\x67\x1b\x67\xa1\x73\x9d\xf3\x35\x17\xa6\x4b\ +\x90\xcb\x12\x97\x76\x97\x17\x53\x6d\xa7\x8a\xa7\x6e\x9f\x7a\xcb\ +\x95\xe5\x1a\xee\xba\xd2\xb5\xd3\xf5\xa3\x9b\xbb\x9b\xdc\xad\xd9\ +\x6d\xd4\xdd\xcc\x3d\xc5\x7d\xab\xfb\x4d\x2e\x9b\x1b\xc9\x5d\xc3\ +\x3d\xef\x41\xf4\xf0\xf7\x58\xe2\x71\xcc\xe3\x9d\xa7\x9b\xa7\xc2\ +\xf3\x90\xe7\x2f\x5e\x76\x5e\x59\x5e\xfb\xbd\x1e\x4f\xb3\x9c\x26\ +\x9e\xd6\x30\x6d\xc8\xdb\xc4\x5b\xe0\xbd\xcb\x7b\x60\x3a\x3e\x3d\ +\x65\xfa\xce\xe9\x03\x3e\xc6\x3e\x02\x9f\x7a\x9f\x87\xbe\xa6\xbe\ +\x22\xdf\x3d\xbe\x23\x7e\xd6\x7e\x99\x7e\x07\xfc\x9e\xfb\x3b\xfa\ +\xcb\xfd\x8f\xf8\xbf\xe1\x79\xf2\x16\xf1\x4e\x05\x60\x01\xc1\x01\ +\xe5\x01\xbd\x81\x1a\x81\xb3\x03\x6b\x03\x1f\x04\x99\x04\xa5\x07\ +\x35\x05\x8d\x05\xbb\x06\x2f\x0c\x3e\x15\x42\x0c\x09\x0d\x59\x1f\ +\x72\x93\x6f\xc0\x17\xf2\x1b\xf9\x63\x33\xdc\x67\x2c\x9a\xd1\x15\ +\xca\x08\x9d\x15\x5a\x1b\xfa\x30\xcc\x26\x4c\x1e\xd6\x11\x8e\x86\ +\xcf\x08\xdf\x10\x7e\x6f\xa6\xf9\x4c\xe9\xcc\xb6\x08\x88\xe0\x47\ +\x6c\x88\xb8\x1f\x69\x19\x99\x17\xf9\x7d\x14\x29\x2a\x32\xaa\x2e\ +\xea\x51\xb4\x53\x74\x71\x74\xf7\x2c\xd6\xac\xe4\x59\xfb\x67\xbd\ +\x8e\xf1\x8f\xa9\x8c\xb9\x3b\xdb\x6a\xb6\x72\x76\x67\xac\x6a\x6c\ +\x52\x6c\x63\xec\x9b\xb8\x80\xb8\xaa\xb8\x81\x78\x87\xf8\x45\xf1\ +\x97\x12\x74\x13\x24\x09\xed\x89\xe4\xc4\xd8\xc4\x3d\x89\xe3\x73\ +\x02\xe7\x6c\x9a\x33\x9c\xe4\x9a\x54\x96\x74\x63\xae\xe5\xdc\xa2\ +\xb9\x17\xe6\xe9\xce\xcb\x9e\x77\x3c\x59\x35\x59\x90\x7c\x38\x85\ +\x98\x12\x97\xb2\x3f\xe5\x83\x20\x42\x50\x2f\x18\x4f\xe5\xa7\x6e\ +\x4d\x1d\x13\xf2\x84\x9b\x85\x4f\x45\xbe\xa2\x8d\xa2\x51\xb1\xb7\ +\xb8\x4a\x3c\x92\xe6\x9d\x56\x95\xf6\x38\xdd\x3b\x7d\x43\xfa\x68\ +\x86\x4f\x46\x75\xc6\x33\x09\x4f\x52\x2b\x79\x91\x19\x92\xb9\x23\ +\xf3\x4d\x56\x44\xd6\xde\xac\xcf\xd9\x71\xd9\x2d\x39\x94\x9c\x94\ +\x9c\xa3\x52\x0d\x69\x96\xb4\x2b\xd7\x30\xb7\x28\xb7\x4f\x66\x2b\ +\x2b\x93\x0d\xe4\x79\xe6\x6d\xca\x1b\x93\x87\xca\xf7\xe4\x23\xf9\ +\x73\xf3\xdb\x15\x6c\x85\x4c\xd1\xa3\xb4\x52\xae\x50\x0e\x16\x4c\ +\x2f\xa8\x2b\x78\x5b\x18\x5b\x78\xb8\x48\xbd\x48\x5a\xd4\x33\xdf\ +\x66\xfe\xea\xf9\x23\x0b\x82\x16\x7c\xbd\x90\xb0\x50\xb8\xb0\xb3\ +\xd8\xb8\x78\x59\xf1\xe0\x22\xbf\x45\xbb\x16\x23\x8b\x53\x17\x77\ +\x2e\x31\x5d\x52\xba\x64\x78\x69\xf0\xd2\x7d\xcb\x68\xcb\xb2\x96\ +\xfd\x50\xe2\x58\x52\x55\xf2\x6a\x79\xdc\xf2\x8e\x52\x83\xd2\xa5\ +\xa5\x43\x2b\x82\x57\x34\x95\xa9\x94\xc9\xcb\x6e\xae\xf4\x5a\xb9\ +\x63\x15\x61\x95\x64\x55\xef\x6a\x97\xd5\x5b\x56\x7f\x2a\x17\x95\ +\x5f\xac\x70\xac\xa8\xae\xf8\xb0\x46\xb8\xe6\xe2\x57\x4e\x5f\xd5\ +\x7c\xf5\x79\x6d\xda\xda\xde\x4a\xb7\xca\xed\xeb\x48\xeb\xa4\xeb\ +\x6e\xac\xf7\x59\xbf\xaf\x4a\xbd\x6a\x41\xd5\xd0\x86\xf0\x0d\xad\ +\x1b\xf1\x8d\xe5\x1b\x5f\x6d\x4a\xde\x74\xa1\x7a\x6a\xf5\x8e\xcd\ +\xb4\xcd\xca\xcd\x03\x35\x61\x35\xed\x5b\xcc\xb6\xac\xdb\xf2\xa1\ +\x36\xa3\xf6\x7a\x9d\x7f\x5d\xcb\x56\xfd\xad\xab\xb7\xbe\xd9\x26\ +\xda\xd6\xbf\xdd\x77\x7b\xf3\x0e\x83\x1d\x15\x3b\xde\xef\x94\xec\ +\xbc\xb5\x2b\x78\x57\x6b\xbd\x45\x7d\xf5\x6e\xd2\xee\x82\xdd\x8f\ +\x1a\x62\x1b\xba\xbf\xe6\x7e\xdd\xb8\x47\x77\x4f\xc5\x9e\x8f\x7b\ +\xa5\x7b\x07\xf6\x45\xef\xeb\x6a\x74\x6f\x6c\xdc\xaf\xbf\xbf\xb2\ +\x09\x6d\x52\x36\x8d\x1e\x48\x3a\x70\xe5\x9b\x80\x6f\xda\x9b\xed\ +\x9a\x77\xb5\x70\x5a\x2a\x0e\xc2\x41\xe5\xc1\x27\xdf\xa6\x7c\x7b\ +\xe3\x50\xe8\xa1\xce\xc3\xdc\xc3\xcd\xdf\x99\x7f\xb7\xf5\x08\xeb\ +\x48\x79\x2b\xd2\x3a\xbf\x75\xac\x2d\xa3\x6d\xa0\x3d\xa1\xbd\xef\ +\xe8\x8c\xa3\x9d\x1d\x5e\x1d\x47\xbe\xb7\xff\x7e\xef\x31\xe3\x63\ +\x75\xc7\x35\x8f\x57\x9e\xa0\x9d\x28\x3d\xf1\xf9\xe4\x82\x93\xe3\ +\xa7\x64\xa7\x9e\x9d\x4e\x3f\x3d\xd4\x99\xdc\x79\xf7\x4c\xfc\x99\ +\x6b\x5d\x51\x5d\xbd\x67\x43\xcf\x9e\x3f\x17\x74\xee\x4c\xb7\x5f\ +\xf7\xc9\xf3\xde\xe7\x8f\x5d\xf0\xbc\x70\xf4\x22\xf7\x62\xdb\x25\ +\xb7\x4b\xad\x3d\xae\x3d\x47\x7e\x70\xfd\xe1\x48\xaf\x5b\x6f\xeb\ +\x65\xf7\xcb\xed\x57\x3c\xae\x74\xf4\x4d\xeb\x3b\xd1\xef\xd3\x7f\ +\xfa\x6a\xc0\xd5\x73\xd7\xf8\xd7\x2e\x5d\x9f\x79\xbd\xef\xc6\xec\ +\x1b\xb7\x6e\x26\xdd\x1c\xb8\x25\xba\xf5\xf8\x76\xf6\xed\x17\x77\ +\x0a\xee\x4c\xdc\x5d\x7a\x8f\x78\xaf\xfc\xbe\xda\xfd\xea\x07\xfa\ +\x0f\xea\x7f\xb4\xfe\xb1\x65\xc0\x6d\xe0\xf8\x60\xc0\x60\xcf\xc3\ +\x59\x0f\xef\x0e\x09\x87\x9e\xfe\x94\xff\xd3\x87\xe1\xd2\x47\xcc\ +\x47\xd5\x23\x46\x23\x8d\x8f\x9d\x1f\x1f\x1b\x0d\x1a\xbd\xf2\x64\ +\xce\x93\xe1\xa7\xb2\xa7\x13\xcf\xca\x7e\x56\xff\x79\xeb\x73\xab\ +\xe7\xdf\xfd\xe2\xfb\x4b\xcf\x58\xfc\xd8\xf0\x0b\xf9\x8b\xcf\xbf\ +\xae\x79\xa9\xf3\x72\xef\xab\xa9\xaf\x3a\xc7\x23\xc7\x1f\xbc\xce\ +\x79\x3d\xf1\xa6\xfc\xad\xce\xdb\x7d\xef\xb8\xef\xba\xdf\xc7\xbd\ +\x1f\x99\x28\xfc\x40\xfe\x50\xf3\xd1\xfa\x63\xc7\xa7\xd0\x4f\xf7\ +\x3e\xe7\x7c\xfe\xfc\x2f\xf7\x84\xf3\xfb\x25\xd2\x9f\x33\x00\x00\ +\x00\x20\x63\x48\x52\x4d\x00\x00\x7a\x25\x00\x00\x80\x83\x00\x00\ +\xf9\xff\x00\x00\x80\xe9\x00\x00\x75\x30\x00\x00\xea\x60\x00\x00\ +\x3a\x98\x00\x00\x17\x6f\x92\x5f\xc5\x46\x00\x00\x05\xd1\x49\x44\ +\x41\x54\x78\xda\x7c\x95\x4d\x6c\x5c\x57\x1d\xc5\x7f\xf7\xf3\x7d\ +\xcd\x7b\x33\x1e\x4f\x9c\xc8\x4e\xed\xba\x81\xaa\xa1\x75\x58\xd4\ +\x52\x25\x04\xa2\x6d\x40\x7c\xac\x68\x82\x22\xb1\x28\x2c\xa9\x10\ +\xad\x08\x08\x04\xb4\x12\x2a\x15\xaa\x10\xd0\xd2\x6e\x41\x05\xc4\ +\xa2\x05\x01\x8b\x82\x84\x14\x56\xa9\x40\x4a\x88\x90\x1a\xc7\x85\ +\x36\x89\xab\x24\x76\x6c\x8f\xed\x71\x6d\x8f\xe7\xfb\xbd\x77\x59\ +\x8c\x67\xec\x10\x89\xbf\x74\xf5\xde\xe2\xdd\x77\xee\xff\x9c\xff\ +\x39\x57\x38\xe7\xc8\xb2\x8c\x41\x39\xe7\x10\x42\x0c\xdf\x07\x25\ +\xa5\xa4\x56\xab\x3d\xf2\xde\x7b\xd7\x4e\xa5\x69\xf6\x48\xa9\x54\ +\x5c\x2f\x8d\x14\x2f\x56\x0e\x55\xfe\x16\x85\xe1\xe5\x3c\xcb\x0f\ +\x7e\x2f\x00\xa7\xb5\x46\xfc\x1f\x00\xe1\xf6\x76\x28\xa5\xd8\xd8\ +\xa8\x7d\x6e\x6e\xee\xca\x1f\x77\x1b\x8d\x60\x74\xb4\xcc\x68\x65\ +\x94\x42\x21\x42\x49\xd9\xd2\x4a\xff\x22\x08\x82\x97\xc3\x30\xbc\ +\x01\x90\xe7\x0e\x70\x18\x63\x90\xdc\x5d\x02\xd0\x80\x14\x42\x20\ +\x84\xc0\x39\x67\x56\x56\x57\x9f\x4b\xb3\x3c\x18\x1b\x3b\x4c\xa9\ +\x54\x22\x08\x7d\xfc\x20\xa0\x52\x39\x14\x94\xcb\xe5\x67\x3a\x9d\ +\xce\xa5\x5a\xad\xf6\x85\xfe\x1e\xe4\x1e\x09\x77\x02\x38\xe7\x34\ +\x30\x01\x38\x20\x1b\x9c\xfe\xc6\xcd\x5b\x3f\x58\x5c\x5a\xfe\x58\ +\xa1\x10\x11\x86\x01\xbe\xef\x23\x85\x04\xe7\xc8\xd2\x14\x29\x25\ +\x95\x4a\xa5\xe2\xfb\xfe\x9f\x56\x56\x56\x7e\x2a\xa5\xcc\x95\x94\ +\x77\x01\x08\x21\x84\x27\x84\xa8\x03\x99\x73\x0e\xa5\x14\xeb\x1b\ +\xb5\x4f\xbe\xfb\xee\xd5\xef\x84\x81\x4f\x92\x24\x04\xa1\x87\xb1\ +\x1a\x6b\x2d\x9e\xe7\xa1\xb5\x06\x20\x4d\x53\xe2\x38\x16\xb7\x96\ +\x96\xbe\x75\xf5\xda\xf5\x6f\x08\xa9\xee\xee\x00\x68\x02\xdb\x03\ +\x1d\xda\xed\x76\x3c\x7f\x65\xfe\x55\x29\x85\x29\x14\x0a\x08\x09\ +\xc6\x18\x8c\x31\x7d\x00\x6b\x51\x4a\xe1\x9c\xa3\xd7\xeb\x71\xe3\ +\xe6\x4d\xda\xed\x36\x0b\x0b\xef\x3f\xdb\x6a\xb5\x27\xff\x17\xc0\ +\xed\x2d\x06\xd4\x5c\x5f\x78\xff\xf9\x9d\x7a\xfd\x44\xb1\x58\xc4\ +\xf3\x3c\x3c\xcf\x0e\x01\x3c\xeb\x21\xa5\x42\x08\x41\x96\x65\x34\ +\x9a\x0d\x56\xab\xab\xa4\x69\x86\x10\xb2\xb2\xbc\xbc\x7c\x92\x3d\ +\x31\xef\x54\x58\x88\x3e\x35\xeb\x1b\x9f\xbd\x7e\x7d\xe1\x6c\xb9\ +\x5c\x26\x8e\x63\x3c\xcf\xc3\x5a\x8d\xd2\x0a\x6b\x2d\x46\x2b\x94\ +\x10\xe4\x79\x4e\xa7\xd3\xa1\xba\xb6\x46\xb3\xd9\x46\x20\x28\x14\ +\x22\xea\xf5\xdd\xa9\x21\xc0\x60\xee\x07\x5c\x76\xba\xbd\x4f\xcc\ +\xcf\xbf\xf3\x5b\x63\x0c\xc5\x62\x11\xa5\x15\xda\xc8\xe1\xc9\x3d\ +\x63\x90\x52\x81\x10\x74\x7b\x5d\x6a\x9b\x35\xd6\xd6\xd6\xc1\xf5\ +\x3b\x0f\xc3\x90\x4a\xa5\xb2\x38\x04\x58\xad\x56\x0f\xf0\xe4\xec\ +\xad\x9b\x4b\xcf\x6e\xef\xec\x56\x8e\x1e\x1d\xc7\x9a\x3e\xd7\xc6\ +\x1a\xac\x31\x78\xc6\xa2\x94\x46\x0a\x41\x96\xe7\x34\x1b\x4d\x56\ +\x57\xd7\xc8\xb3\xbe\x9f\xb4\x56\x1c\x1a\xab\x2c\x1d\x39\x32\x76\ +\x6e\x08\xb0\xb9\xb9\x3d\xa4\xa7\x97\x76\x7f\x52\xad\x56\x3f\x53\ +\x2e\x97\x89\xc2\x10\x63\x35\xda\x28\x8c\x36\x18\x63\xd1\x4a\xf7\ +\xbd\x01\xf4\xba\x5d\xaa\xd5\x2a\x3b\xf5\x1d\x04\x12\xad\x35\xa3\ +\x95\x51\x4a\xa5\xe2\xd7\x1c\x2c\x0e\x45\xce\xb3\x94\x3c\x4b\x11\ +\xb8\xd9\xda\x46\xed\x29\xdf\xf7\x29\x26\x45\x84\x92\x68\xa3\xd0\ +\x5a\xa3\x8d\xc1\x18\x8d\x54\x72\x28\xec\xd6\xf6\x16\xd5\xea\x1a\ +\xb8\xbe\x16\x61\x18\x62\xb4\x79\xab\xd5\x6a\xfd\x79\x73\x73\x93\ +\x61\x07\x4e\x38\xc2\x20\xa0\xdb\xe9\x7e\x4f\x08\xec\xe4\xe4\x3d\ +\x8c\x8c\x94\x10\x52\xe0\x5c\xdf\xf2\x56\x1b\xa4\x52\x43\xbd\xda\ +\x9d\x36\x2b\xab\xab\xb4\xdb\x2d\x84\x90\x04\x41\x88\x94\xb2\x53\ +\xaf\xef\x7c\xdf\x91\x93\x65\x19\x87\xc7\xc6\xfa\x00\x81\x17\xf0\ +\x9f\x7f\x5f\xfd\xee\xf9\xf3\xe7\x4f\x65\x79\xce\xd4\xe4\x3d\xcc\ +\x7c\xf4\x21\x66\x67\x1f\x26\x89\x13\x5c\xde\x0f\x32\xe7\x1c\x02\ +\x41\xaf\xd7\x63\xbd\x56\x63\x7d\x6d\x03\x90\x78\x5e\x40\xb1\x58\ +\x64\xa7\xbe\xfd\xf3\x3c\xcf\xfe\x51\xdf\xad\x13\x04\xc1\x7e\x07\ +\x6f\xbf\x7d\xf9\xe9\x97\x7e\xf6\xca\x8b\x5b\x5b\x1f\xd0\x6a\xb7\ +\x68\xb7\xdb\x18\x63\x79\xe0\xf8\x03\x7c\xe5\xcb\x4f\x72\xe6\xcc\ +\x69\x3c\xcf\x02\xd0\xeb\xf5\xd8\xa9\xef\xb0\x78\x6b\x91\x6e\xb7\ +\x87\xef\xfb\x44\x51\x48\xa7\xd3\x5a\x68\xb5\x9a\x2f\xe7\x79\x4e\ +\x1c\xc7\x4c\x4f\x4f\xef\x3b\xf9\xda\xd5\x6b\x9f\x4e\xd3\x1e\x71\ +\x1c\x93\xc4\x09\xa5\x52\x09\x63\x0d\x73\x73\x73\x7c\xfd\xe9\x67\ +\x38\x75\xfa\x0c\x97\xe7\xae\xf4\x4f\xa4\x35\xd5\xea\x1a\x5b\x1f\ +\x6c\x61\xad\x25\x49\x12\x3c\xdf\xb2\xdb\x68\xfc\x18\xa8\xfa\xbe\ +\xcf\xd4\xd4\x14\x4a\x1d\x88\x8a\x24\x29\x5e\xd4\x5a\x0f\x5d\x6a\ +\xf6\xc6\x31\x49\x12\x46\xcb\x65\x2e\x5e\xbc\xc4\x13\x4f\x7c\x91\ +\xd7\x5f\xff\x1d\xad\x76\x9b\xdd\xc6\x2e\xbe\x1f\x50\x88\x63\xc2\ +\xc8\xa7\xb1\xdb\x78\x2b\xcb\xf2\x5f\x0a\xa1\x98\x98\x38\x4a\x14\ +\x85\xe4\x79\xbe\x0f\x30\x39\x35\xf9\x9b\x89\x89\xf1\x6a\x9e\xe7\ +\x78\x9e\x87\xd1\x7b\x59\x63\x2c\x9e\xf5\xa8\x8c\x96\xc9\x1d\x9c\ +\xfd\xe6\xb7\x79\xfe\x87\x2f\xe0\x1c\x7b\xd4\x04\x64\x59\x46\xab\ +\xdd\xfa\x91\xd2\xca\x09\x29\x50\x5a\xed\xdd\x07\x07\xe2\x3a\x8a\ +\xa3\xa5\x47\x1f\x7b\xf4\xc5\x41\x5b\xfd\x58\xb0\xc3\xa7\xd1\x96\ +\x42\x18\x51\x4c\x12\x7e\xff\xc6\x1f\xf8\xe7\x85\x4b\xc4\x49\x81\ +\x20\xf0\x69\xb5\x5a\xe7\x8c\xd1\xe7\xac\xd1\x58\xa3\xd8\xad\xd7\ +\x87\x03\xb1\x1f\x76\xce\x71\xfc\xc1\xe3\xaf\x3c\xfe\xf8\x63\x2f\ +\xa9\xbd\x00\xf3\x7d\xff\xc0\xf2\xf0\xac\x25\x0a\x22\x7c\xcf\xe7\ +\xcd\x37\xff\x82\xd6\x9a\x3c\xcf\xbb\x69\x9a\xbe\xe0\xfb\x3e\xd6\ +\x5a\xa2\xa8\x80\xef\xf9\xf8\x9e\x4f\x5c\x88\x0f\xf8\xc0\x39\x04\ +\xf0\xf0\xec\xec\x73\x20\xa2\x0b\x17\x2e\x7c\xb5\xd9\x6c\xf6\xb5\ +\xf0\x3c\xb2\x2c\x23\xcf\x32\xb2\x3c\xc7\x58\xc3\xc6\x7a\x8d\xf9\ +\xb9\x79\x1e\x9c\xf9\xc8\xaf\x8d\x31\x7f\xcf\xb2\x0c\xa5\x14\xf7\ +\x7f\xe8\xc3\x24\x71\xbc\x1f\xc9\xfb\x1d\x08\x5c\x0e\x61\x10\xb4\ +\x66\x66\x1e\x7a\xea\xe4\xa7\x4e\x7e\x69\xfa\xbe\xe9\x45\x6b\xfb\ +\xa3\x69\xad\x25\x08\x43\x82\x20\x20\x8a\x22\xc2\x30\xe4\xf6\xf2\ +\x72\x5d\x6b\xfd\x6a\x14\x45\x58\x6b\x99\x18\x9f\x20\x49\x92\x7e\ +\xe6\x1f\xa0\x48\x0f\x6f\x02\x40\x29\x29\xa3\x42\xe4\xee\x9d\xbe\ +\xf7\x8d\x91\xf2\xc8\x5f\x6f\xdf\xbe\x7d\x7a\x79\x79\xf9\xc9\x5a\ +\x6d\xf3\xe3\xcd\x66\x53\x23\x04\x38\xc7\xf4\xb1\xe9\xe6\x89\x13\ +\x33\x67\x85\x10\xef\x00\x8c\x96\x47\x39\x72\xf8\xf0\x1d\x3f\x1e\ +\x54\x3f\xae\xa5\x1b\x24\xa9\xb0\x56\x0b\xad\x75\x56\x2a\x95\xb6\ +\x7d\xdf\x7f\x6d\x64\x64\xe4\xb5\x4e\xa7\x3b\xab\x94\xfc\xbc\x90\ +\xdc\x5f\x2a\x96\xb6\x8e\x1d\xbb\xef\x57\xa5\x91\xe2\xbf\xd2\x2c\ +\x45\x2b\xcd\xc4\xf8\x38\x4a\xa9\xe1\x68\x1e\xac\xff\x0e\x00\x68\ +\xf0\x5f\xd8\xaf\x76\x9c\x4c\x00\x00\x00\x00\x49\x45\x4e\x44\xae\ +\x42\x60\x82\ +\x00\x00\x03\x66\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ +\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ +\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ +\x79\x71\xc9\x65\x3c\x00\x00\x03\x08\x49\x44\x41\x54\x78\xda\x84\ +\x53\x6d\x48\x53\x51\x18\x7e\xce\xfd\xd8\x75\x9b\x8e\xdc\x2c\xdd\ +\x4c\x5d\x4e\xa7\xc9\xe6\xb7\xf6\x61\x61\x11\x14\x52\x16\xf5\xc7\ +\x0a\x0b\xa2\x3f\x41\x51\x41\x61\x7f\x0a\x84\xa2\x9f\xfd\xeb\x67\ +\x7f\xfa\x51\x44\x50\x91\x14\x15\x5a\x14\x41\x25\x6d\x44\x59\x68\ +\x69\xd9\x74\xa6\x6d\xd7\x7d\x38\xb6\xdd\x6d\x77\xa7\x73\x2d\x4d\ +\x84\xe8\x81\x87\xf7\x7d\xef\x7b\xde\xe7\xbe\xe7\x9c\xf7\x10\x30\ +\x48\x84\x20\x4f\xb3\xf8\x8b\xb3\x1b\xe9\xbc\xe5\x38\x14\xb3\x74\ +\x2f\x73\xab\x18\x47\x28\x45\x6f\x36\x0b\xff\xc2\x3a\xde\xc6\xb2\ +\x06\xcd\x61\x24\x4b\x04\xbe\x87\x09\x48\x82\x89\x8a\xb8\x62\xaf\ +\x76\x75\x5a\x4a\xcb\x9d\x31\x85\xae\x9d\x0d\xce\x15\x7c\xf1\xa3\ +\xef\x67\x18\xd0\xc8\xe1\x1f\xf0\xcf\x01\x43\x53\xc4\xf1\x33\x04\ +\x57\x20\x12\x29\xcc\x31\x5b\x84\x4d\x7b\xf6\x18\xb5\x78\xcc\x0f\ +\x07\x23\x34\x0a\xcb\xea\x0a\x19\x4f\x32\xda\x19\xc7\x53\x04\x91\ +\x99\x10\xc4\xde\xd3\xa7\x61\x30\x1a\xa1\xb2\xde\xb5\x98\xe7\xb0\ +\x85\xe5\xc7\xb4\x02\x81\x2e\xa9\x66\xfe\xb9\x86\xd6\xd6\xfd\xee\ +\xba\x3a\xcb\x3b\x8f\x47\x9e\x78\xe7\x8d\xc5\x13\x88\x4a\x3a\x1d\ +\x94\x78\x1c\x82\x28\x22\xae\x6d\x8b\x47\x23\x5b\x7e\x6d\x5e\xa0\ +\xdd\xf9\x77\xe7\xcf\x3e\xd3\x0d\xbd\xa7\x3a\xac\x2e\xa7\x15\x43\ +\x9f\x6d\xd6\xae\x43\xde\xb0\x51\x44\x74\x6c\x78\x18\xf6\x8a\x0a\ +\x68\x96\xc5\x1a\x4a\x16\x6a\x84\xad\xce\xc5\xfa\xae\xc1\x69\x53\ +\x65\xbd\xdb\x8e\x74\x32\x09\xcd\xea\xf2\x4c\xb9\x0e\x5b\x94\x0c\ +\xdc\xba\xe9\x6d\xda\xbe\xa3\xd1\xf3\xe4\xb1\x37\xf7\xb7\x40\xc1\ +\xa2\x40\x26\xbb\x28\xc0\x75\xd5\x29\x23\xc9\xb9\xb9\x8d\x99\x74\ +\x1a\x2a\xe3\xae\xfa\xf4\xc7\xf1\x92\xa2\x60\xce\xc4\x0f\x4b\x85\ +\xb3\x0a\xcf\xfb\x6e\xd2\x57\xdd\x35\x1f\x73\x43\xc9\x47\x33\x25\ +\x26\x4c\x15\xe7\x82\x27\xb5\x07\x41\x09\x87\x7c\x75\x66\xc8\x28\ +\x66\xaa\x4b\x2a\xdd\x4d\xec\x42\x85\xf0\x6c\x20\xf5\x32\x3c\xfa\ +\x4d\x3a\xd1\xe3\xd4\xd7\xb4\x54\xa5\x14\x17\xa6\xdb\xaa\x6d\x85\ +\x5b\xda\x0b\x9e\xe6\x04\x12\xe1\x3c\xc1\x8e\x2c\xfd\xc2\x7f\x6d\ +\xba\x8c\x41\x7d\x07\x1e\x99\x8e\x40\xa5\x24\xc0\x7d\xb8\xb1\x3e\ +\x96\x26\xb6\x57\xaf\x07\xfc\x74\x77\x77\x45\xc1\x6a\x87\x79\x2a\ +\x91\xc0\xd9\x8e\xa3\xb8\x3d\xe5\x41\xe9\xaa\x62\x93\xcb\x5c\x5e\ +\x6b\xa0\xba\x35\xdf\x02\x93\xe2\x92\x39\xa0\xcd\xfd\xa6\xc3\x3b\ +\x83\xf2\x2c\x69\x6c\x6e\x41\x24\x1a\x13\xef\x8f\xb4\xbe\x1f\xf7\ +\x49\x93\x49\x76\x26\xb2\x2c\x43\xb3\x1a\xd4\x54\x46\xaa\x36\x97\ +\xb9\x69\x54\x69\x23\x7c\x77\xdf\x0a\x70\xe2\x7e\x83\x24\xd4\x1c\ +\xeb\x74\xef\x5b\x19\x19\x2a\xb6\x4b\x32\xc6\x15\x0b\x82\xf9\x95\ +\xa1\xab\x0f\xfb\x3d\x49\xce\x17\x6b\x19\xf6\x0e\x0c\x6e\xf0\x6f\ +\xa3\x69\x55\x0f\x45\x35\xd0\x74\x36\x07\xa3\xd1\x27\x84\x3f\x70\ +\xe7\x4c\xe7\xfa\xf2\xee\xa6\x2a\xeb\x5a\x4b\x7e\x9e\xe4\xf3\x4d\ +\xe3\xd2\xde\x52\x9c\xbf\xeb\x43\x59\x99\x15\x72\x28\x9a\x7a\xfb\ +\xe9\xfb\x68\x5f\xff\xeb\x7b\xea\x83\x93\xd7\x97\x0d\x9e\xcc\x41\ +\x89\x36\xd7\xda\xcd\xf5\xd9\x4c\x76\xfe\x2d\x2d\x6f\x97\xaa\xd0\ +\xd5\x39\xac\x35\x90\x4c\xe5\xfc\xe6\x9e\x11\xed\x41\x2d\x61\x90\ +\xf0\xf5\x87\x2e\xc0\xda\xd0\x4e\x79\x29\x41\x05\x7d\x0c\x82\x3e\ +\xde\x36\x7d\xf5\xcd\xcb\xa2\xe3\xeb\x48\x26\x69\x20\x99\x84\x91\ +\xa8\x8a\x1e\x3f\xbc\x2f\xe8\xec\xe8\x45\x1a\x99\x04\x8d\x4c\x2c\ +\xb6\x40\xfe\x0c\x85\x05\xff\x87\xac\xfd\x71\xf9\xc7\x5f\x02\x0c\ +\x00\x00\x31\x44\x70\x94\xe4\x6d\xa8\x00\x00\x00\x00\x49\x45\x4e\ +\x44\xae\x42\x60\x82\ +\x00\x00\x0c\xdf\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\ +\x01\x00\x9a\x9c\x18\x00\x00\x0a\x4f\x69\x43\x43\x50\x50\x68\x6f\ +\x74\x6f\x73\x68\x6f\x70\x20\x49\x43\x43\x20\x70\x72\x6f\x66\x69\ +\x6c\x65\x00\x00\x78\xda\x9d\x53\x67\x54\x53\xe9\x16\x3d\xf7\xde\ +\xf4\x42\x4b\x88\x80\x94\x4b\x6f\x52\x15\x08\x20\x52\x42\x8b\x80\ +\x14\x91\x26\x2a\x21\x09\x10\x4a\x88\x21\xa1\xd9\x15\x51\xc1\x11\ +\x45\x45\x04\x1b\xc8\xa0\x88\x03\x8e\x8e\x80\x8c\x15\x51\x2c\x0c\ +\x8a\x0a\xd8\x07\xe4\x21\xa2\x8e\x83\xa3\x88\x8a\xca\xfb\xe1\x7b\ +\xa3\x6b\xd6\xbc\xf7\xe6\xcd\xfe\xb5\xd7\x3e\xe7\xac\xf3\x9d\xb3\ +\xcf\x07\xc0\x08\x0c\x96\x48\x33\x51\x35\x80\x0c\xa9\x42\x1e\x11\ +\xe0\x83\xc7\xc4\xc6\xe1\xe4\x2e\x40\x81\x0a\x24\x70\x00\x10\x08\ +\xb3\x64\x21\x73\xfd\x23\x01\x00\xf8\x7e\x3c\x3c\x2b\x22\xc0\x07\ +\xbe\x00\x01\x78\xd3\x0b\x08\x00\xc0\x4d\x9b\xc0\x30\x1c\x87\xff\ +\x0f\xea\x42\x99\x5c\x01\x80\x84\x01\xc0\x74\x91\x38\x4b\x08\x80\ +\x14\x00\x40\x7a\x8e\x42\xa6\x00\x40\x46\x01\x80\x9d\x98\x26\x53\ +\x00\xa0\x04\x00\x60\xcb\x63\x62\xe3\x00\x50\x2d\x00\x60\x27\x7f\ +\xe6\xd3\x00\x80\x9d\xf8\x99\x7b\x01\x00\x5b\x94\x21\x15\x01\xa0\ +\x91\x00\x20\x13\x65\x88\x44\x00\x68\x3b\x00\xac\xcf\x56\x8a\x45\ +\x00\x58\x30\x00\x14\x66\x4b\xc4\x39\x00\xd8\x2d\x00\x30\x49\x57\ +\x66\x48\x00\xb0\xb7\x00\xc0\xce\x10\x0b\xb2\x00\x08\x0c\x00\x30\ +\x51\x88\x85\x29\x00\x04\x7b\x00\x60\xc8\x23\x23\x78\x00\x84\x99\ +\x00\x14\x46\xf2\x57\x3c\xf1\x2b\xae\x10\xe7\x2a\x00\x00\x78\x99\ +\xb2\x3c\xb9\x24\x39\x45\x81\x5b\x08\x2d\x71\x07\x57\x57\x2e\x1e\ +\x28\xce\x49\x17\x2b\x14\x36\x61\x02\x61\x9a\x40\x2e\xc2\x79\x99\ +\x19\x32\x81\x34\x0f\xe0\xf3\xcc\x00\x00\xa0\x91\x15\x11\xe0\x83\ +\xf3\xfd\x78\xce\x0e\xae\xce\xce\x36\x8e\xb6\x0e\x5f\x2d\xea\xbf\ +\x06\xff\x22\x62\x62\xe3\xfe\xe5\xcf\xab\x70\x40\x00\x00\xe1\x74\ +\x7e\xd1\xfe\x2c\x2f\xb3\x1a\x80\x3b\x06\x80\x6d\xfe\xa2\x25\xee\ +\x04\x68\x5e\x0b\xa0\x75\xf7\x8b\x66\xb2\x0f\x40\xb5\x00\xa0\xe9\ +\xda\x57\xf3\x70\xf8\x7e\x3c\x3c\x45\xa1\x90\xb9\xd9\xd9\xe5\xe4\ +\xe4\xd8\x4a\xc4\x42\x5b\x61\xca\x57\x7d\xfe\x67\xc2\x5f\xc0\x57\ +\xfd\x6c\xf9\x7e\x3c\xfc\xf7\xf5\xe0\xbe\xe2\x24\x81\x32\x5d\x81\ +\x47\x04\xf8\xe0\xc2\xcc\xf4\x4c\xa5\x1c\xcf\x92\x09\x84\x62\xdc\ +\xe6\x8f\x47\xfc\xb7\x0b\xff\xfc\x1d\xd3\x22\xc4\x49\x62\xb9\x58\ +\x2a\x14\xe3\x51\x12\x71\x8e\x44\x9a\x8c\xf3\x32\xa5\x22\x89\x42\ +\x92\x29\xc5\x25\xd2\xff\x64\xe2\xdf\x2c\xfb\x03\x3e\xdf\x35\x00\ +\xb0\x6a\x3e\x01\x7b\x91\x2d\xa8\x5d\x63\x03\xf6\x4b\x27\x10\x58\ +\x74\xc0\xe2\xf7\x00\x00\xf2\xbb\x6f\xc1\xd4\x28\x08\x03\x80\x68\ +\x83\xe1\xcf\x77\xff\xef\x3f\xfd\x47\xa0\x25\x00\x80\x66\x49\x92\ +\x71\x00\x00\x5e\x44\x24\x2e\x54\xca\xb3\x3f\xc7\x08\x00\x00\x44\ +\xa0\x81\x2a\xb0\x41\x1b\xf4\xc1\x18\x2c\xc0\x06\x1c\xc1\x05\xdc\ +\xc1\x0b\xfc\x60\x36\x84\x42\x24\xc4\xc2\x42\x10\x42\x0a\x64\x80\ +\x1c\x72\x60\x29\xac\x82\x42\x28\x86\xcd\xb0\x1d\x2a\x60\x2f\xd4\ +\x40\x1d\x34\xc0\x51\x68\x86\x93\x70\x0e\x2e\xc2\x55\xb8\x0e\x3d\ +\x70\x0f\xfa\x61\x08\x9e\xc1\x28\xbc\x81\x09\x04\x41\xc8\x08\x13\ +\x61\x21\xda\x88\x01\x62\x8a\x58\x23\x8e\x08\x17\x99\x85\xf8\x21\ +\xc1\x48\x04\x12\x8b\x24\x20\xc9\x88\x14\x51\x22\x4b\x91\x35\x48\ +\x31\x52\x8a\x54\x20\x55\x48\x1d\xf2\x3d\x72\x02\x39\x87\x5c\x46\ +\xba\x91\x3b\xc8\x00\x32\x82\xfc\x86\xbc\x47\x31\x94\x81\xb2\x51\ +\x3d\xd4\x0c\xb5\x43\xb9\xa8\x37\x1a\x84\x46\xa2\x0b\xd0\x64\x74\ +\x31\x9a\x8f\x16\xa0\x9b\xd0\x72\xb4\x1a\x3d\x8c\x36\xa1\xe7\xd0\ +\xab\x68\x0f\xda\x8f\x3e\x43\xc7\x30\xc0\xe8\x18\x07\x33\xc4\x6c\ +\x30\x2e\xc6\xc3\x42\xb1\x38\x2c\x09\x93\x63\xcb\xb1\x22\xac\x0c\ +\xab\xc6\x1a\xb0\x56\xac\x03\xbb\x89\xf5\x63\xcf\xb1\x77\x04\x12\ +\x81\x45\xc0\x09\x36\x04\x77\x42\x20\x61\x1e\x41\x48\x58\x4c\x58\ +\x4e\xd8\x48\xa8\x20\x1c\x24\x34\x11\xda\x09\x37\x09\x03\x84\x51\ +\xc2\x27\x22\x93\xa8\x4b\xb4\x26\xba\x11\xf9\xc4\x18\x62\x32\x31\ +\x87\x58\x48\x2c\x23\xd6\x12\x8f\x13\x2f\x10\x7b\x88\x43\xc4\x37\ +\x24\x12\x89\x43\x32\x27\xb9\x90\x02\x49\xb1\xa4\x54\xd2\x12\xd2\ +\x46\xd2\x6e\x52\x23\xe9\x2c\xa9\x9b\x34\x48\x1a\x23\x93\xc9\xda\ +\x64\x6b\xb2\x07\x39\x94\x2c\x20\x2b\xc8\x85\xe4\x9d\xe4\xc3\xe4\ +\x33\xe4\x1b\xe4\x21\xf2\x5b\x0a\x9d\x62\x40\x71\xa4\xf8\x53\xe2\ +\x28\x52\xca\x6a\x4a\x19\xe5\x10\xe5\x34\xe5\x06\x65\x98\x32\x41\ +\x55\xa3\x9a\x52\xdd\xa8\xa1\x54\x11\x35\x8f\x5a\x42\xad\xa1\xb6\ +\x52\xaf\x51\x87\xa8\x13\x34\x75\x9a\x39\xcd\x83\x16\x49\x4b\xa5\ +\xad\xa2\x95\xd3\x1a\x68\x17\x68\xf7\x69\xaf\xe8\x74\xba\x11\xdd\ +\x95\x1e\x4e\x97\xd0\x57\xd2\xcb\xe9\x47\xe8\x97\xe8\x03\xf4\x77\ +\x0c\x0d\x86\x15\x83\xc7\x88\x67\x28\x19\x9b\x18\x07\x18\x67\x19\ +\x77\x18\xaf\x98\x4c\xa6\x19\xd3\x8b\x19\xc7\x54\x30\x37\x31\xeb\ +\x98\xe7\x99\x0f\x99\x6f\x55\x58\x2a\xb6\x2a\x7c\x15\x91\xca\x0a\ +\x95\x4a\x95\x26\x95\x1b\x2a\x2f\x54\xa9\xaa\xa6\xaa\xde\xaa\x0b\ +\x55\xf3\x55\xcb\x54\x8f\xa9\x5e\x53\x7d\xae\x46\x55\x33\x53\xe3\ +\xa9\x09\xd4\x96\xab\x55\xaa\x9d\x50\xeb\x53\x1b\x53\x67\xa9\x3b\ +\xa8\x87\xaa\x67\xa8\x6f\x54\x3f\xa4\x7e\x59\xfd\x89\x06\x59\xc3\ +\x4c\xc3\x4f\x43\xa4\x51\xa0\xb1\x5f\xe3\xbc\xc6\x20\x0b\x63\x19\ +\xb3\x78\x2c\x21\x6b\x0d\xab\x86\x75\x81\x35\xc4\x26\xb1\xcd\xd9\ +\x7c\x76\x2a\xbb\x98\xfd\x1d\xbb\x8b\x3d\xaa\xa9\xa1\x39\x43\x33\ +\x4a\x33\x57\xb3\x52\xf3\x94\x66\x3f\x07\xe3\x98\x71\xf8\x9c\x74\ +\x4e\x09\xe7\x28\xa7\x97\xf3\x7e\x8a\xde\x14\xef\x29\xe2\x29\x1b\ +\xa6\x34\x4c\xb9\x31\x65\x5c\x6b\xaa\x96\x97\x96\x58\xab\x48\xab\ +\x51\xab\x47\xeb\xbd\x36\xae\xed\xa7\x9d\xa6\xbd\x45\xbb\x59\xfb\ +\x81\x0e\x41\xc7\x4a\x27\x5c\x27\x47\x67\x8f\xce\x05\x9d\xe7\x53\ +\xd9\x53\xdd\xa7\x0a\xa7\x16\x4d\x3d\x3a\xf5\xae\x2e\xaa\x6b\xa5\ +\x1b\xa1\xbb\x44\x77\xbf\x6e\xa7\xee\x98\x9e\xbe\x5e\x80\x9e\x4c\ +\x6f\xa7\xde\x79\xbd\xe7\xfa\x1c\x7d\x2f\xfd\x54\xfd\x6d\xfa\xa7\ +\xf5\x47\x0c\x58\x06\xb3\x0c\x24\x06\xdb\x0c\xce\x18\x3c\xc5\x35\ +\x71\x6f\x3c\x1d\x2f\xc7\xdb\xf1\x51\x43\x5d\xc3\x40\x43\xa5\x61\ +\x95\x61\x97\xe1\x84\x91\xb9\xd1\x3c\xa3\xd5\x46\x8d\x46\x0f\x8c\ +\x69\xc6\x5c\xe3\x24\xe3\x6d\xc6\x6d\xc6\xa3\x26\x06\x26\x21\x26\ +\x4b\x4d\xea\x4d\xee\x9a\x52\x4d\xb9\xa6\x29\xa6\x3b\x4c\x3b\x4c\ +\xc7\xcd\xcc\xcd\xa2\xcd\xd6\x99\x35\x9b\x3d\x31\xd7\x32\xe7\x9b\ +\xe7\x9b\xd7\x9b\xdf\xb7\x60\x5a\x78\x5a\x2c\xb6\xa8\xb6\xb8\x65\ +\x49\xb2\xe4\x5a\xa6\x59\xee\xb6\xbc\x6e\x85\x5a\x39\x59\xa5\x58\ +\x55\x5a\x5d\xb3\x46\xad\x9d\xad\x25\xd6\xbb\xad\xbb\xa7\x11\xa7\ +\xb9\x4e\x93\x4e\xab\x9e\xd6\x67\xc3\xb0\xf1\xb6\xc9\xb6\xa9\xb7\ +\x19\xb0\xe5\xd8\x06\xdb\xae\xb6\x6d\xb6\x7d\x61\x67\x62\x17\x67\ +\xb7\xc5\xae\xc3\xee\x93\xbd\x93\x7d\xba\x7d\x8d\xfd\x3d\x07\x0d\ +\x87\xd9\x0e\xab\x1d\x5a\x1d\x7e\x73\xb4\x72\x14\x3a\x56\x3a\xde\ +\x9a\xce\x9c\xee\x3f\x7d\xc5\xf4\x96\xe9\x2f\x67\x58\xcf\x10\xcf\ +\xd8\x33\xe3\xb6\x13\xcb\x29\xc4\x69\x9d\x53\x9b\xd3\x47\x67\x17\ +\x67\xb9\x73\x83\xf3\x88\x8b\x89\x4b\x82\xcb\x2e\x97\x3e\x2e\x9b\ +\x1b\xc6\xdd\xc8\xbd\xe4\x4a\x74\xf5\x71\x5d\xe1\x7a\xd2\xf5\x9d\ +\x9b\xb3\x9b\xc2\xed\xa8\xdb\xaf\xee\x36\xee\x69\xee\x87\xdc\x9f\ +\xcc\x34\x9f\x29\x9e\x59\x33\x73\xd0\xc3\xc8\x43\xe0\x51\xe5\xd1\ +\x3f\x0b\x9f\x95\x30\x6b\xdf\xac\x7e\x4f\x43\x4f\x81\x67\xb5\xe7\ +\x23\x2f\x63\x2f\x91\x57\xad\xd7\xb0\xb7\xa5\x77\xaa\xf7\x61\xef\ +\x17\x3e\xf6\x3e\x72\x9f\xe3\x3e\xe3\x3c\x37\xde\x32\xde\x59\x5f\ +\xcc\x37\xc0\xb7\xc8\xb7\xcb\x4f\xc3\x6f\x9e\x5f\x85\xdf\x43\x7f\ +\x23\xff\x64\xff\x7a\xff\xd1\x00\xa7\x80\x25\x01\x67\x03\x89\x81\ +\x41\x81\x5b\x02\xfb\xf8\x7a\x7c\x21\xbf\x8e\x3f\x3a\xdb\x65\xf6\ +\xb2\xd9\xed\x41\x8c\xa0\xb9\x41\x15\x41\x8f\x82\xad\x82\xe5\xc1\ +\xad\x21\x68\xc8\xec\x90\xad\x21\xf7\xe7\x98\xce\x91\xce\x69\x0e\ +\x85\x50\x7e\xe8\xd6\xd0\x07\x61\xe6\x61\x8b\xc3\x7e\x0c\x27\x85\ +\x87\x85\x57\x86\x3f\x8e\x70\x88\x58\x1a\xd1\x31\x97\x35\x77\xd1\ +\xdc\x43\x73\xdf\x44\xfa\x44\x96\x44\xde\x9b\x67\x31\x4f\x39\xaf\ +\x2d\x4a\x35\x2a\x3e\xaa\x2e\x6a\x3c\xda\x37\xba\x34\xba\x3f\xc6\ +\x2e\x66\x59\xcc\xd5\x58\x9d\x58\x49\x6c\x4b\x1c\x39\x2e\x2a\xae\ +\x36\x6e\x6c\xbe\xdf\xfc\xed\xf3\x87\xe2\x9d\xe2\x0b\xe3\x7b\x17\ +\x98\x2f\xc8\x5d\x70\x79\xa1\xce\xc2\xf4\x85\xa7\x16\xa9\x2e\x12\ +\x2c\x3a\x96\x40\x4c\x88\x4e\x38\x94\xf0\x41\x10\x2a\xa8\x16\x8c\ +\x25\xf2\x13\x77\x25\x8e\x0a\x79\xc2\x1d\xc2\x67\x22\x2f\xd1\x36\ +\xd1\x88\xd8\x43\x5c\x2a\x1e\x4e\xf2\x48\x2a\x4d\x7a\x92\xec\x91\ +\xbc\x35\x79\x24\xc5\x33\xa5\x2c\xe5\xb9\x84\x27\xa9\x90\xbc\x4c\ +\x0d\x4c\xdd\x9b\x3a\x9e\x16\x9a\x76\x20\x6d\x32\x3d\x3a\xbd\x31\ +\x83\x92\x91\x90\x71\x42\xaa\x21\x4d\x93\xb6\x67\xea\x67\xe6\x66\ +\x76\xcb\xac\x65\x85\xb2\xfe\xc5\x6e\x8b\xb7\x2f\x1e\x95\x07\xc9\ +\x6b\xb3\x90\xac\x05\x59\x2d\x0a\xb6\x42\xa6\xe8\x54\x5a\x28\xd7\ +\x2a\x07\xb2\x67\x65\x57\x66\xbf\xcd\x89\xca\x39\x96\xab\x9e\x2b\ +\xcd\xed\xcc\xb3\xca\xdb\x90\x37\x9c\xef\x9f\xff\xed\x12\xc2\x12\ +\xe1\x92\xb6\xa5\x86\x4b\x57\x2d\x1d\x58\xe6\xbd\xac\x6a\x39\xb2\ +\x3c\x71\x79\xdb\x0a\xe3\x15\x05\x2b\x86\x56\x06\xac\x3c\xb8\x8a\ +\xb6\x2a\x6d\xd5\x4f\xab\xed\x57\x97\xae\x7e\xbd\x26\x7a\x4d\x6b\ +\x81\x5e\xc1\xca\x82\xc1\xb5\x01\x6b\xeb\x0b\x55\x0a\xe5\x85\x7d\ +\xeb\xdc\xd7\xed\x5d\x4f\x58\x2f\x59\xdf\xb5\x61\xfa\x86\x9d\x1b\ +\x3e\x15\x89\x8a\xae\x14\xdb\x17\x97\x15\x7f\xd8\x28\xdc\x78\xe5\ +\x1b\x87\x6f\xca\xbf\x99\xdc\x94\xb4\xa9\xab\xc4\xb9\x64\xcf\x66\ +\xd2\x66\xe9\xe6\xde\x2d\x9e\x5b\x0e\x96\xaa\x97\xe6\x97\x0e\x6e\ +\x0d\xd9\xda\xb4\x0d\xdf\x56\xb4\xed\xf5\xf6\x45\xdb\x2f\x97\xcd\ +\x28\xdb\xbb\x83\xb6\x43\xb9\xa3\xbf\x3c\xb8\xbc\x65\xa7\xc9\xce\ +\xcd\x3b\x3f\x54\xa4\x54\xf4\x54\xfa\x54\x36\xee\xd2\xdd\xb5\x61\ +\xd7\xf8\x6e\xd1\xee\x1b\x7b\xbc\xf6\x34\xec\xd5\xdb\x5b\xbc\xf7\ +\xfd\x3e\xc9\xbe\xdb\x55\x01\x55\x4d\xd5\x66\xd5\x65\xfb\x49\xfb\ +\xb3\xf7\x3f\xae\x89\xaa\xe9\xf8\x96\xfb\x6d\x5d\xad\x4e\x6d\x71\ +\xed\xc7\x03\xd2\x03\xfd\x07\x23\x0e\xb6\xd7\xb9\xd4\xd5\x1d\xd2\ +\x3d\x54\x52\x8f\xd6\x2b\xeb\x47\x0e\xc7\x1f\xbe\xfe\x9d\xef\x77\ +\x2d\x0d\x36\x0d\x55\x8d\x9c\xc6\xe2\x23\x70\x44\x79\xe4\xe9\xf7\ +\x09\xdf\xf7\x1e\x0d\x3a\xda\x76\x8c\x7b\xac\xe1\x07\xd3\x1f\x76\ +\x1d\x67\x1d\x2f\x6a\x42\x9a\xf2\x9a\x46\x9b\x53\x9a\xfb\x5b\x62\ +\x5b\xba\x4f\xcc\x3e\xd1\xd6\xea\xde\x7a\xfc\x47\xdb\x1f\x0f\x9c\ +\x34\x3c\x59\x79\x4a\xf3\x54\xc9\x69\xda\xe9\x82\xd3\x93\x67\xf2\ +\xcf\x8c\x9d\x95\x9d\x7d\x7e\x2e\xf9\xdc\x60\xdb\xa2\xb6\x7b\xe7\ +\x63\xce\xdf\x6a\x0f\x6f\xef\xba\x10\x74\xe1\xd2\x45\xff\x8b\xe7\ +\x3b\xbc\x3b\xce\x5c\xf2\xb8\x74\xf2\xb2\xdb\xe5\x13\x57\xb8\x57\ +\x9a\xaf\x3a\x5f\x6d\xea\x74\xea\x3c\xfe\x93\xd3\x4f\xc7\xbb\x9c\ +\xbb\x9a\xae\xb9\x5c\x6b\xb9\xee\x7a\xbd\xb5\x7b\x66\xf7\xe9\x1b\ +\x9e\x37\xce\xdd\xf4\xbd\x79\xf1\x16\xff\xd6\xd5\x9e\x39\x3d\xdd\ +\xbd\xf3\x7a\x6f\xf7\xc5\xf7\xf5\xdf\x16\xdd\x7e\x72\x27\xfd\xce\ +\xcb\xbb\xd9\x77\x27\xee\xad\xbc\x4f\xbc\x5f\xf4\x40\xed\x41\xd9\ +\x43\xdd\x87\xd5\x3f\x5b\xfe\xdc\xd8\xef\xdc\x7f\x6a\xc0\x77\xa0\ +\xf3\xd1\xdc\x47\xf7\x06\x85\x83\xcf\xfe\x91\xf5\x8f\x0f\x43\x05\ +\x8f\x99\x8f\xcb\x86\x0d\x86\xeb\x9e\x38\x3e\x39\x39\xe2\x3f\x72\ +\xfd\xe9\xfc\xa7\x43\xcf\x64\xcf\x26\x9e\x17\xfe\xa2\xfe\xcb\xae\ +\x17\x16\x2f\x7e\xf8\xd5\xeb\xd7\xce\xd1\x98\xd1\xa1\x97\xf2\x97\ +\x93\xbf\x6d\x7c\xa5\xfd\xea\xc0\xeb\x19\xaf\xdb\xc6\xc2\xc6\x1e\ +\xbe\xc9\x78\x33\x31\x5e\xf4\x56\xfb\xed\xc1\x77\xdc\x77\x1d\xef\ +\xa3\xdf\x0f\x4f\xe4\x7c\x20\x7f\x28\xff\x68\xf9\xb1\xf5\x53\xd0\ +\xa7\xfb\x93\x19\x93\x93\xff\x04\x03\x98\xf3\xfc\x63\x33\x2d\xdb\ +\x00\x00\x00\x20\x63\x48\x52\x4d\x00\x00\x7a\x25\x00\x00\x80\x83\ +\x00\x00\xf9\xff\x00\x00\x80\xe9\x00\x00\x75\x30\x00\x00\xea\x60\ +\x00\x00\x3a\x98\x00\x00\x17\x6f\x92\x5f\xc5\x46\x00\x00\x02\x0a\ +\x49\x44\x41\x54\x78\xda\x64\xd1\x4d\xab\x1c\x45\x18\xc5\xf1\x5f\ +\xdf\x69\x1a\xc6\x5c\xbc\x12\x74\x1b\x10\x84\x08\x92\xe0\x07\x30\ +\xb8\x0a\x8c\x9b\x04\xfc\x0a\x42\xc4\x95\xa2\x5b\x97\x82\x2b\x05\ +\x51\x70\xe5\x87\x88\x2b\xc1\x95\xe0\x3e\x82\x2b\x21\x20\x64\xe7\ +\xdb\x9d\x3b\xd3\x3d\xfd\x52\xd5\x35\xed\xa2\x2a\xde\x01\x1b\x0e\ +\x0f\x05\x7d\xce\xff\xd4\x53\xd5\x72\xf7\x2e\x37\x6f\x72\x71\xc1\ +\x8d\x1b\x34\xcd\x6d\x55\xf5\x48\x4a\x6f\x0b\xe1\x4d\xd3\xc4\x30\ +\xfc\xe2\x70\xf8\xc9\xe1\xf0\xad\xae\x7b\xaa\xef\xe9\x7b\xa6\x49\ +\xb5\xdc\xbb\x97\x03\xce\xcf\x69\x9a\x8f\x55\xd5\x67\xe6\x79\x6d\ +\x9a\x18\xc7\xac\x1c\x42\xdf\x0f\xfa\xfe\x53\x7d\xff\xa5\x61\x20\ +\x04\xb5\xf3\x73\xd6\xeb\xc6\x6a\xf5\xd8\xf1\xb8\x31\xcf\xf9\xe7\ +\xc3\x21\xcf\x69\x22\x46\x42\x60\x1c\xd7\xa6\xe9\x0b\xd3\x74\x5f\ +\x08\x0f\x85\x10\x6a\x4d\x03\x5f\x9b\xe7\x8d\x18\xb3\xa9\x6d\xe9\ +\xba\x4c\x0f\x81\x94\xb2\x72\x08\xc3\xb0\x59\xc6\xf1\xf3\xc8\x27\ +\xd5\xf2\xe0\xc1\x1d\x75\xfd\x44\x4a\x2b\xc3\xc0\x6e\x97\xd5\x75\ +\x99\x9e\x12\xcb\x92\x95\x12\xe3\x28\xc5\xa8\x27\x05\xde\xa8\x85\ +\xf0\x48\x8c\x2b\xe3\x98\xc9\xdb\x2d\x57\x57\xb9\x49\x8c\xd9\x08\ +\x55\xc5\x3c\x0b\xcb\x62\x40\xcf\x6a\xe2\x83\x5a\xdf\x6f\xa4\x74\ +\x5d\xbd\x6d\xf3\x86\x43\xe0\x78\x74\xfa\xc5\x62\x3e\x14\x0d\xbc\ +\x55\xeb\xba\x5b\xff\x5b\x5c\x8c\xd9\xfc\x9c\x8e\x05\x11\x53\xa6\ +\x6b\xf3\xbc\x55\x6b\x5b\xe6\xd9\x7f\xcf\x16\xe3\xf5\xbd\x4f\xcc\ +\x09\x73\xa6\x3a\x5c\x07\xa8\xb5\xed\x33\x31\xbe\x26\x46\xcf\xb5\ +\x9c\x98\xe1\x58\xe8\x03\x3a\xec\x8b\x26\x9e\x9d\xd9\xef\x7f\xb0\ +\xdb\xb1\xdf\x4b\xc3\x20\xa6\x24\x14\x5a\x3a\xa9\xdd\xe1\x0a\x97\ +\x45\xfb\x1c\xf8\x73\x95\xb8\x33\xf2\x24\xb2\x9a\x4b\x5d\xa8\xca\ +\x4c\x18\x4b\xe5\xbf\xf1\x0f\x76\x08\xa4\x86\xd7\xeb\x9e\x5f\x07\ +\xbe\x19\xf9\x30\x16\xf2\xf1\xc4\x3c\x97\xbb\xee\xb1\x2d\xe6\x09\ +\x67\x7c\xb5\xe6\x69\x75\x99\xeb\x35\x07\x1e\x0f\x6c\x42\x31\x9e\ +\xd6\x1f\x4a\x40\x57\xce\xf8\xfe\x05\xde\x7d\x99\x74\x76\xcc\xc4\ +\x10\x79\x67\xe4\xa3\x8e\x6e\x8b\xbf\xf0\x27\xfe\x28\xba\xcc\xdb\ +\xef\x12\xef\x37\x3c\x7c\x91\xf4\x12\xaa\x6d\x79\x96\x7d\xa9\xb7\ +\xe7\xd5\x8e\xf7\x3a\xee\x0f\xdc\x9e\x72\x93\xdf\x12\x3f\xae\xf8\ +\x6e\xcd\xef\x17\x78\xa5\xe8\xdf\x01\x00\xd9\x09\x61\x5f\xc2\xb1\ +\xaf\xf3\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x02\xf0\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ +\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ +\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ +\x79\x71\xc9\x65\x3c\x00\x00\x02\x92\x49\x44\x41\x54\x78\xda\x84\ +\x53\x5f\x48\x53\x61\x14\x3f\xf7\xee\x6e\x22\x0b\x4c\x6b\xac\x60\ +\x6a\xb5\x39\xdd\x06\x36\xd9\x70\x6b\xb8\x31\xd4\xb2\x35\x82\x3d\ +\x4c\x50\x50\x08\x1f\x24\x84\x24\x56\xea\xc3\xdc\x2a\x63\x36\x1d\ +\x4d\xa1\xb0\xf0\xc1\x17\x1f\x7c\xb0\x3f\x0f\x51\x96\x12\xe8\xa6\ +\x84\xa6\xeb\x45\xa9\x84\x22\x25\x08\xb7\x97\xec\x45\xe6\xee\xdd\ +\xed\x7c\x97\xb5\x36\x1a\x74\xe0\xc7\xe1\xfb\x9d\xf3\xfb\x9d\xf3\ +\xdd\x8f\x4b\x41\x4e\xd0\xc5\xc5\x20\xa9\xa8\x80\xc3\xdd\x5d\x48\ +\x1f\x1c\x40\x75\x43\x03\x68\x6d\x36\xa0\x45\x22\xa0\x69\x5a\x85\ +\x2d\x8d\x90\x1f\x3f\x18\x28\x10\x4a\xa3\x11\xaa\x4d\x26\x41\x98\ +\x89\xaa\x74\x3a\xdd\x38\x3b\x34\xf4\xf8\x0f\x41\x21\xdc\x7e\xff\ +\xd5\x3c\x83\x53\x7a\xbd\x20\x16\x31\x79\x74\x55\x9a\xe3\x9a\x66\ +\x03\x81\x47\xd1\xf5\x75\xf8\xb0\xb5\x05\x75\x3a\x1d\x58\xb1\x0f\ +\x79\x4a\xe8\x2c\xaf\xab\x83\xd3\x48\x30\xcc\x3f\x0b\x55\x71\x1c\ +\xd7\xfc\x34\x18\x9c\xc0\x0c\x89\xfd\x7d\x28\x2d\x2b\xa3\x30\xf3\ +\xa4\xc8\x11\x03\x53\x47\x07\x88\xc4\xe2\x42\x37\x51\xe3\x84\xf3\ +\xcf\x42\xa1\x87\xc9\x64\x12\x44\x78\x1d\x8d\x52\x09\xdf\xe3\x71\ +\xbe\x5c\x2e\x17\x1a\xb0\x4e\xd3\x50\x38\xd4\x2c\xc7\x5d\x78\x82\ +\xe2\x58\x2c\x06\x57\x70\xc8\xd6\xe6\x26\x9c\x51\x28\xc0\x6e\x30\ +\x80\xba\xb2\x12\x2e\x79\x3c\xd7\x70\x83\x85\x42\x06\xd5\x1c\xcb\ +\xb6\x3c\x0f\x87\x1f\xbc\x5f\x5b\x83\xbb\x7e\x3f\x1c\xe0\x8b\xdc\ +\x1a\x1c\x24\x2b\x0b\x1f\xd6\xd1\xdb\xdb\x8b\xd3\x17\xf0\x1e\xdb\ +\x4c\x01\xf1\xc5\x17\x13\x13\xe3\xef\x56\x56\xe0\x8e\xd7\x9b\x2d\ +\x04\x46\x47\x41\x52\x54\x04\x2d\x3d\x3d\xd7\x29\x8a\x9a\x47\xa3\ +\xcf\x84\xcf\x35\xa8\x61\x59\xd6\xf1\x6a\x72\x32\xbc\xbc\xb4\x04\ +\xbe\xfe\xfe\x6c\x61\x64\x6c\x0c\x8c\xf5\xf5\xd0\xdc\xdd\xed\x41\ +\xf1\x1b\x51\x46\x9c\x6b\xa0\x21\xe2\xd7\x53\x53\xf7\x23\x8b\x8b\ +\xe0\xef\xeb\xcb\x8a\xef\x85\xc3\x60\xb6\x58\xa0\xb1\xab\xeb\x06\ +\x8a\xe7\x50\xfc\x29\x77\x65\x62\xa0\xe1\x52\x29\xe7\xfc\xf4\x74\ +\x28\x8a\xe2\x00\xae\x2d\x91\x48\x84\xe2\xed\x60\x10\x2c\x56\x2b\ +\xd8\x3b\x3b\xfb\x80\x88\x19\x26\x2b\xfe\x8a\xdf\xe7\xcb\xea\x2a\ +\x30\x38\xf9\xf2\xdb\x99\x99\x91\xbd\x78\x1c\xc6\x87\x87\x41\x2a\ +\x95\x0a\x0d\x37\x7d\x3e\x41\x6c\x6b\x6f\x1f\xc0\xc9\x2f\x71\xf2\ +\x47\xc2\xef\xe0\xab\xec\x6c\x6c\xfc\x5d\x41\xdf\xda\xaa\x3d\xeb\ +\x76\x0f\xfc\xe4\x79\x7e\x2e\x12\xe1\x5d\x2e\x17\x3f\x1f\x8d\xf2\ +\xbf\xf0\x4c\x78\x52\x37\xb4\xb5\x81\xa2\xb6\xb6\xf0\x83\x9f\xd0\ +\x6a\xa1\xc6\xe9\xd4\xa9\x1d\x0e\x2f\x31\xd9\xde\xdb\xe3\xf7\x31\ +\x93\x33\xe1\x49\xfd\x7f\x41\xfe\x98\x92\x12\x95\xaa\x49\x6e\x36\ +\x0f\x11\x13\x92\x8f\xaa\x54\x76\xe4\x8f\x21\x4a\x49\x1d\x71\x04\ +\x51\x8c\x28\x42\x88\x33\x3a\x8a\xca\x1c\x4e\x22\x8e\x4b\x64\x32\ +\x85\x58\x26\x3b\x97\x4a\x24\x96\x0f\x13\x89\x6f\xc8\xa5\x10\x6c\ +\x26\x13\x1c\xe6\x70\x04\xdc\x6f\x01\x06\x00\x2d\x06\x04\x62\x7f\ +\xe8\x51\x71\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x02\x75\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ +\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ +\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ +\x79\x71\xc9\x65\x3c\x00\x00\x02\x17\x49\x44\x41\x54\x78\xda\xa4\ +\x93\x4f\x6b\x13\x51\x14\xc5\xcf\x9b\x37\x93\x49\xc6\xa9\x25\xa3\ +\x15\x63\x16\x2e\x02\x06\xad\x0b\x45\xc1\x95\x5f\xc0\x3f\x0b\x21\ +\x1b\xa5\x20\xb8\x31\xea\x7c\x82\xac\xf4\x2b\x54\x5b\x23\x88\x1b\ +\x4b\xb3\x70\xe1\x42\xb7\x82\x20\xe8\x42\xbb\x70\xa1\x62\x36\xa2\ +\xa0\x51\xda\x1a\xc2\x4c\xa6\x26\x93\xcc\x78\xef\x75\x5a\xb4\x2a\ +\x54\x7c\x70\xb8\x2f\x93\x7b\x7e\xf7\xbc\x37\x89\xaa\xd5\x6a\x50\ +\x4a\x9d\x06\xb0\x07\xff\xb6\x3e\xa5\x69\xfa\xc0\x4c\x92\x84\x3f\ +\x94\x9b\xcd\xe6\xcd\x38\x8e\xb7\xe4\xb4\x2c\x0b\xf5\x7a\xfd\x12\ +\xef\xcd\xd1\x68\xc4\xd5\x18\x0c\x06\xf0\x7d\x1f\x0c\x64\x11\x5d\ +\xea\x78\x3c\x46\x18\xf6\xa9\xa6\x62\xf4\x3c\x0f\xf3\xf3\xd7\x41\ +\x3e\xe3\x67\x80\xe2\xca\x86\x6a\xb5\x0a\x4e\xf2\xed\x68\x05\xa3\ +\xc7\x2f\xb1\xb2\xf2\x95\x9e\x6b\x32\xdb\xb0\xed\x3c\xa2\x28\x60\ +\x33\x4b\x09\x20\x8b\x6d\xf0\x43\x9e\xc6\x49\x58\x69\x79\x07\x56\ +\x57\xbb\x64\x88\x91\xcf\x6f\x13\xb3\x65\xe5\xa9\x27\x16\x00\xf9\ +\x8c\x0d\x23\x49\x33\x48\x00\x34\x39\x8a\x22\xa8\xbd\xbb\x08\x94\ +\x60\xf2\x60\x05\xe5\xd9\x3a\x26\x4f\x1c\x13\x90\x69\xda\x92\x90\ +\x3d\xec\x35\x86\xc3\x21\x48\x3f\x40\xa5\x22\x9d\x37\x84\x73\xed\ +\xbc\x5c\xd6\xf6\xe9\x0a\x3c\xff\x14\x7a\x8d\x16\x01\x8b\xa8\x35\ +\xaf\x12\xc0\x94\x04\xec\x61\xef\xc6\x11\xb8\x26\xef\xbf\xa0\xdf\ +\x5d\x43\xf7\xe1\x53\xb8\x07\xf6\xa1\x78\xf9\x24\xfa\xb7\x1f\xc1\ +\x75\x8b\x48\x5b\x4b\xb8\x77\xf7\x19\xbf\x72\x49\xb0\x7e\x04\x93\ +\x29\xb4\x24\x8e\xe3\x38\xe8\xf5\x7a\x30\x0c\x0b\xfa\xed\x07\x84\ +\xfe\x2c\x0a\x85\x09\x0c\x0c\x2d\x46\x5e\xb6\xad\xd7\x13\x68\x01\ +\xb4\xdb\x6d\x94\x4a\xa5\x1c\x37\x34\x1a\x8d\x2d\xfd\x0e\xb8\x37\ +\x08\x82\x5c\xa7\xd3\x01\x63\x3d\xd7\x75\x67\xb4\xd6\xbb\x37\x37\ +\xd2\xa4\xb2\x4c\x31\xcd\x8f\x9b\xbf\xa3\x0b\xff\x4c\xf7\xb5\xc0\ +\x80\x02\x69\x82\xfb\x7e\xe9\x98\xce\x01\xaf\x86\x7e\xb6\xbf\x41\ +\xfb\xdf\xf8\xa4\x40\xfd\x35\xe7\xe2\xd4\x2d\xbc\x89\x8f\xc8\x7e\ +\xbf\xb5\x84\x73\xcb\x17\xff\xd4\xc6\x53\x77\x92\x2a\xa4\x43\xa4\ +\xc3\xa4\xaa\x24\x5a\x0c\x71\xe6\xce\x59\x01\xdc\xbf\xd0\xe2\xf2\ +\x82\x93\x93\xde\x65\xfb\xe7\xa4\xd7\x9c\xc0\xca\x8e\xe1\x66\x72\ +\xf8\xad\xe0\x8a\x33\x83\x29\x75\x5c\xc6\x2c\xa7\x4f\x30\x17\x2d\ +\x64\x43\xd7\x38\x7a\xa6\x48\xf1\x9f\xe6\x7f\xd6\x77\x01\x06\x00\ +\xf9\x1f\x11\xa0\x42\x25\x9c\x34\x00\x00\x00\x00\x49\x45\x4e\x44\ +\xae\x42\x60\x82\ +\x00\x00\x10\xe6\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\ +\x01\x00\x9a\x9c\x18\x00\x00\x0a\x4d\x69\x43\x43\x50\x50\x68\x6f\ +\x74\x6f\x73\x68\x6f\x70\x20\x49\x43\x43\x20\x70\x72\x6f\x66\x69\ +\x6c\x65\x00\x00\x78\xda\x9d\x53\x77\x58\x93\xf7\x16\x3e\xdf\xf7\ +\x65\x0f\x56\x42\xd8\xf0\xb1\x97\x6c\x81\x00\x22\x23\xac\x08\xc8\ +\x10\x59\xa2\x10\x92\x00\x61\x84\x10\x12\x40\xc5\x85\x88\x0a\x56\ +\x14\x15\x11\x9c\x48\x55\xc4\x82\xd5\x0a\x48\x9d\x88\xe2\xa0\x28\ +\xb8\x67\x41\x8a\x88\x5a\x8b\x55\x5c\x38\xee\x1f\xdc\xa7\xb5\x7d\ +\x7a\xef\xed\xed\xfb\xd7\xfb\xbc\xe7\x9c\xe7\xfc\xce\x79\xcf\x0f\ +\x80\x11\x12\x26\x91\xe6\xa2\x6a\x00\x39\x52\x85\x3c\x3a\xd8\x1f\ +\x8f\x4f\x48\xc4\xc9\xbd\x80\x02\x15\x48\xe0\x04\x20\x10\xe6\xcb\ +\xc2\x67\x05\xc5\x00\x00\xf0\x03\x79\x78\x7e\x74\xb0\x3f\xfc\x01\ +\xaf\x6f\x00\x02\x00\x70\xd5\x2e\x24\x12\xc7\xe1\xff\x83\xba\x50\ +\x26\x57\x00\x20\x91\x00\xe0\x22\x12\xe7\x0b\x01\x90\x52\x00\xc8\ +\x2e\x54\xc8\x14\x00\xc8\x18\x00\xb0\x53\xb3\x64\x0a\x00\x94\x00\ +\x00\x6c\x79\x7c\x42\x22\x00\xaa\x0d\x00\xec\xf4\x49\x3e\x05\x00\ +\xd8\xa9\x93\xdc\x17\x00\xd8\xa2\x1c\xa9\x08\x00\x8d\x01\x00\x99\ +\x28\x47\x24\x02\x40\xbb\x00\x60\x55\x81\x52\x2c\x02\xc0\xc2\x00\ +\xa0\xac\x40\x22\x2e\x04\xc0\xae\x01\x80\x59\xb6\x32\x47\x02\x80\ +\xbd\x05\x00\x76\x8e\x58\x90\x0f\x40\x60\x00\x80\x99\x42\x2c\xcc\ +\x00\x20\x38\x02\x00\x43\x1e\x13\xcd\x03\x20\x4c\x03\xa0\x30\xd2\ +\xbf\xe0\xa9\x5f\x70\x85\xb8\x48\x01\x00\xc0\xcb\x95\xcd\x97\x4b\ +\xd2\x33\x14\xb8\x95\xd0\x1a\x77\xf2\xf0\xe0\xe2\x21\xe2\xc2\x6c\ +\xb1\x42\x61\x17\x29\x10\x66\x09\xe4\x22\x9c\x97\x9b\x23\x13\x48\ +\xe7\x03\x4c\xce\x0c\x00\x00\x1a\xf9\xd1\xc1\xfe\x38\x3f\x90\xe7\ +\xe6\xe4\xe1\xe6\x66\xe7\x6c\xef\xf4\xc5\xa2\xfe\x6b\xf0\x6f\x22\ +\x3e\x21\xf1\xdf\xfe\xbc\x8c\x02\x04\x00\x10\x4e\xcf\xef\xda\x5f\ +\xe5\xe5\xd6\x03\x70\xc7\x01\xb0\x75\xbf\x6b\xa9\x5b\x00\xda\x56\ +\x00\x68\xdf\xf9\x5d\x33\xdb\x09\xa0\x5a\x0a\xd0\x7a\xf9\x8b\x79\ +\x38\xfc\x40\x1e\x9e\xa1\x50\xc8\x3c\x1d\x1c\x0a\x0b\x0b\xed\x25\ +\x62\xa1\xbd\x30\xe3\x8b\x3e\xff\x33\xe1\x6f\xe0\x8b\x7e\xf6\xfc\ +\x40\x1e\xfe\xdb\x7a\xf0\x00\x71\x9a\x40\x99\xad\xc0\xa3\x83\xfd\ +\x71\x61\x6e\x76\xae\x52\x8e\xe7\xcb\x04\x42\x31\x6e\xf7\xe7\x23\ +\xfe\xc7\x85\x7f\xfd\x8e\x29\xd1\xe2\x34\xb1\x5c\x2c\x15\x8a\xf1\ +\x58\x89\xb8\x50\x22\x4d\xc7\x79\xb9\x52\x91\x44\x21\xc9\x95\xe2\ +\x12\xe9\x7f\x32\xf1\x1f\x96\xfd\x09\x93\x77\x0d\x00\xac\x86\x4f\ +\xc0\x4e\xb6\x07\xb5\xcb\x6c\xc0\x7e\xee\x01\x02\x8b\x0e\x58\xd2\ +\x76\x00\x40\x7e\xf3\x2d\x8c\x1a\x0b\x91\x00\x10\x67\x34\x32\x79\ +\xf7\x00\x00\x93\xbf\xf9\x8f\x40\x2b\x01\x00\xcd\x97\xa4\xe3\x00\ +\x00\xbc\xe8\x18\x5c\xa8\x94\x17\x4c\xc6\x08\x00\x00\x44\xa0\x81\ +\x2a\xb0\x41\x07\x0c\xc1\x14\xac\xc0\x0e\x9c\xc1\x1d\xbc\xc0\x17\ +\x02\x61\x06\x44\x40\x0c\x24\xc0\x3c\x10\x42\x06\xe4\x80\x1c\x0a\ +\xa1\x18\x96\x41\x19\x54\xc0\x3a\xd8\x04\xb5\xb0\x03\x1a\xa0\x11\ +\x9a\xe1\x10\xb4\xc1\x31\x38\x0d\xe7\xe0\x12\x5c\x81\xeb\x70\x17\ +\x06\x60\x18\x9e\xc2\x18\xbc\x86\x09\x04\x41\xc8\x08\x13\x61\x21\ +\x3a\x88\x11\x62\x8e\xd8\x22\xce\x08\x17\x99\x8e\x04\x22\x61\x48\ +\x34\x92\x80\xa4\x20\xe9\x88\x14\x51\x22\xc5\xc8\x72\xa4\x02\xa9\ +\x42\x6a\x91\x5d\x48\x23\xf2\x2d\x72\x14\x39\x8d\x5c\x40\xfa\x90\ +\xdb\xc8\x20\x32\x8a\xfc\x8a\xbc\x47\x31\x94\x81\xb2\x51\x03\xd4\ +\x02\x75\x40\xb9\xa8\x1f\x1a\x8a\xc6\xa0\x73\xd1\x74\x34\x0f\x5d\ +\x80\x96\xa2\x6b\xd1\x1a\xb4\x1e\x3d\x80\xb6\xa2\xa7\xd1\x4b\xe8\ +\x75\x74\x00\x7d\x8a\x8e\x63\x80\xd1\x31\x0e\x66\x8c\xd9\x61\x5c\ +\x8c\x87\x45\x60\x89\x58\x1a\x26\xc7\x16\x63\xe5\x58\x35\x56\x8f\ +\x35\x63\x1d\x58\x37\x76\x15\x1b\xc0\x9e\x61\xef\x08\x24\x02\x8b\ +\x80\x13\xec\x08\x5e\x84\x10\xc2\x6c\x82\x90\x90\x47\x58\x4c\x58\ +\x43\xa8\x25\xec\x23\xb4\x12\xba\x08\x57\x09\x83\x84\x31\xc2\x27\ +\x22\x93\xa8\x4f\xb4\x25\x7a\x12\xf9\xc4\x78\x62\x3a\xb1\x90\x58\ +\x46\xac\x26\xee\x21\x1e\x21\x9e\x25\x5e\x27\x0e\x13\x5f\x93\x48\ +\x24\x0e\xc9\x92\xe4\x4e\x0a\x21\x25\x90\x32\x49\x0b\x49\x6b\x48\ +\xdb\x48\x2d\xa4\x53\xa4\x3e\xd2\x10\x69\x9c\x4c\x26\xeb\x90\x6d\ +\xc9\xde\xe4\x08\xb2\x80\xac\x20\x97\x91\xb7\x90\x0f\x90\x4f\x92\ +\xfb\xc9\xc3\xe4\xb7\x14\x3a\xc5\x88\xe2\x4c\x09\xa2\x24\x52\xa4\ +\x94\x12\x4a\x35\x65\x3f\xe5\x04\xa5\x9f\x32\x42\x99\xa0\xaa\x51\ +\xcd\xa9\x9e\xd4\x08\xaa\x88\x3a\x9f\x5a\x49\x6d\xa0\x76\x50\x2f\ +\x53\x87\xa9\x13\x34\x75\x9a\x25\xcd\x9b\x16\x43\xcb\xa4\x2d\xa3\ +\xd5\xd0\x9a\x69\x67\x69\xf7\x68\x2f\xe9\x74\xba\x09\xdd\x83\x1e\ +\x45\x97\xd0\x97\xd2\x6b\xe8\x07\xe9\xe7\xe9\x83\xf4\x77\x0c\x0d\ +\x86\x0d\x83\xc7\x48\x62\x28\x19\x6b\x19\x7b\x19\xa7\x18\xb7\x19\ +\x2f\x99\x4c\xa6\x05\xd3\x97\x99\xc8\x54\x30\xd7\x32\x1b\x99\x67\ +\x98\x0f\x98\x6f\x55\x58\x2a\xf6\x2a\x7c\x15\x91\xca\x12\x95\x3a\ +\x95\x56\x95\x7e\x95\xe7\xaa\x54\x55\x73\x55\x3f\xd5\x79\xaa\x0b\ +\x54\xab\x55\x0f\xab\x5e\x56\x7d\xa6\x46\x55\xb3\x50\xe3\xa9\x09\ +\xd4\x16\xab\xd5\xa9\x1d\x55\xbb\xa9\x36\xae\xce\x52\x77\x52\x8f\ +\x50\xcf\x51\x5f\xa3\xbe\x5f\xfd\x82\xfa\x63\x0d\xb2\x86\x85\x46\ +\xa0\x86\x48\xa3\x54\x63\xb7\xc6\x19\x8d\x21\x16\xc6\x32\x65\xf1\ +\x58\x42\xd6\x72\x56\x03\xeb\x2c\x6b\x98\x4d\x62\x5b\xb2\xf9\xec\ +\x4c\x76\x05\xfb\x1b\x76\x2f\x7b\x4c\x53\x43\x73\xaa\x66\xac\x66\ +\x91\x66\x9d\xe6\x71\xcd\x01\x0e\xc6\xb1\xe0\xf0\x39\xd9\x9c\x4a\ +\xce\x21\xce\x0d\xce\x7b\x2d\x03\x2d\x3f\x2d\xb1\xd6\x6a\xad\x66\ +\xad\x7e\xad\x37\xda\x7a\xda\xbe\xda\x62\xed\x72\xed\x16\xed\xeb\ +\xda\xef\x75\x70\x9d\x40\x9d\x2c\x9d\xf5\x3a\x6d\x3a\xf7\x75\x09\ +\xba\x36\xba\x51\xba\x85\xba\xdb\x75\xcf\xea\x3e\xd3\x63\xeb\x79\ +\xe9\x09\xf5\xca\xf5\x0e\xe9\xdd\xd1\x47\xf5\x6d\xf4\xa3\xf5\x17\ +\xea\xef\xd6\xef\xd1\x1f\x37\x30\x34\x08\x36\x90\x19\x6c\x31\x38\ +\x63\xf0\xcc\x90\x63\xe8\x6b\x98\x69\xb8\xd1\xf0\x84\xe1\xa8\x11\ +\xcb\x68\xba\x91\xc4\x68\xa3\xd1\x49\xa3\x27\xb8\x26\xee\x87\x67\ +\xe3\x35\x78\x17\x3e\x66\xac\x6f\x1c\x62\xac\x34\xde\x65\xdc\x6b\ +\x3c\x61\x62\x69\x32\xdb\xa4\xc4\xa4\xc5\xe4\xbe\x29\xcd\x94\x6b\ +\x9a\x66\xba\xd1\xb4\xd3\x74\xcc\xcc\xc8\x2c\xdc\xac\xd8\xac\xc9\ +\xec\x8e\x39\xd5\x9c\x6b\x9e\x61\xbe\xd9\xbc\xdb\xfc\x8d\x85\xa5\ +\x45\x9c\xc5\x4a\x8b\x36\x8b\xc7\x96\xda\x96\x7c\xcb\x05\x96\x4d\ +\x96\xf7\xac\x98\x56\x3e\x56\x79\x56\xf5\x56\xd7\xac\x49\xd6\x5c\ +\xeb\x2c\xeb\x6d\xd6\x57\x6c\x50\x1b\x57\x9b\x0c\x9b\x3a\x9b\xcb\ +\xb6\xa8\xad\x9b\xad\xc4\x76\x9b\x6d\xdf\x14\xe2\x14\x8f\x29\xd2\ +\x29\xf5\x53\x6e\xda\x31\xec\xfc\xec\x0a\xec\x9a\xec\x06\xed\x39\ +\xf6\x61\xf6\x25\xf6\x6d\xf6\xcf\x1d\xcc\x1c\x12\x1d\xd6\x3b\x74\ +\x3b\x7c\x72\x74\x75\xcc\x76\x6c\x70\xbc\xeb\xa4\xe1\x34\xc3\xa9\ +\xc4\xa9\xc3\xe9\x57\x67\x1b\x67\xa1\x73\x9d\xf3\x35\x17\xa6\x4b\ +\x90\xcb\x12\x97\x76\x97\x17\x53\x6d\xa7\x8a\xa7\x6e\x9f\x7a\xcb\ +\x95\xe5\x1a\xee\xba\xd2\xb5\xd3\xf5\xa3\x9b\xbb\x9b\xdc\xad\xd9\ +\x6d\xd4\xdd\xcc\x3d\xc5\x7d\xab\xfb\x4d\x2e\x9b\x1b\xc9\x5d\xc3\ +\x3d\xef\x41\xf4\xf0\xf7\x58\xe2\x71\xcc\xe3\x9d\xa7\x9b\xa7\xc2\ +\xf3\x90\xe7\x2f\x5e\x76\x5e\x59\x5e\xfb\xbd\x1e\x4f\xb3\x9c\x26\ +\x9e\xd6\x30\x6d\xc8\xdb\xc4\x5b\xe0\xbd\xcb\x7b\x60\x3a\x3e\x3d\ +\x65\xfa\xce\xe9\x03\x3e\xc6\x3e\x02\x9f\x7a\x9f\x87\xbe\xa6\xbe\ +\x22\xdf\x3d\xbe\x23\x7e\xd6\x7e\x99\x7e\x07\xfc\x9e\xfb\x3b\xfa\ +\xcb\xfd\x8f\xf8\xbf\xe1\x79\xf2\x16\xf1\x4e\x05\x60\x01\xc1\x01\ +\xe5\x01\xbd\x81\x1a\x81\xb3\x03\x6b\x03\x1f\x04\x99\x04\xa5\x07\ +\x35\x05\x8d\x05\xbb\x06\x2f\x0c\x3e\x15\x42\x0c\x09\x0d\x59\x1f\ +\x72\x93\x6f\xc0\x17\xf2\x1b\xf9\x63\x33\xdc\x67\x2c\x9a\xd1\x15\ +\xca\x08\x9d\x15\x5a\x1b\xfa\x30\xcc\x26\x4c\x1e\xd6\x11\x8e\x86\ +\xcf\x08\xdf\x10\x7e\x6f\xa6\xf9\x4c\xe9\xcc\xb6\x08\x88\xe0\x47\ +\x6c\x88\xb8\x1f\x69\x19\x99\x17\xf9\x7d\x14\x29\x2a\x32\xaa\x2e\ +\xea\x51\xb4\x53\x74\x71\x74\xf7\x2c\xd6\xac\xe4\x59\xfb\x67\xbd\ +\x8e\xf1\x8f\xa9\x8c\xb9\x3b\xdb\x6a\xb6\x72\x76\x67\xac\x6a\x6c\ +\x52\x6c\x63\xec\x9b\xb8\x80\xb8\xaa\xb8\x81\x78\x87\xf8\x45\xf1\ +\x97\x12\x74\x13\x24\x09\xed\x89\xe4\xc4\xd8\xc4\x3d\x89\xe3\x73\ +\x02\xe7\x6c\x9a\x33\x9c\xe4\x9a\x54\x96\x74\x63\xae\xe5\xdc\xa2\ +\xb9\x17\xe6\xe9\xce\xcb\x9e\x77\x3c\x59\x35\x59\x90\x7c\x38\x85\ +\x98\x12\x97\xb2\x3f\xe5\x83\x20\x42\x50\x2f\x18\x4f\xe5\xa7\x6e\ +\x4d\x1d\x13\xf2\x84\x9b\x85\x4f\x45\xbe\xa2\x8d\xa2\x51\xb1\xb7\ +\xb8\x4a\x3c\x92\xe6\x9d\x56\x95\xf6\x38\xdd\x3b\x7d\x43\xfa\x68\ +\x86\x4f\x46\x75\xc6\x33\x09\x4f\x52\x2b\x79\x91\x19\x92\xb9\x23\ +\xf3\x4d\x56\x44\xd6\xde\xac\xcf\xd9\x71\xd9\x2d\x39\x94\x9c\x94\ +\x9c\xa3\x52\x0d\x69\x96\xb4\x2b\xd7\x30\xb7\x28\xb7\x4f\x66\x2b\ +\x2b\x93\x0d\xe4\x79\xe6\x6d\xca\x1b\x93\x87\xca\xf7\xe4\x23\xf9\ +\x73\xf3\xdb\x15\x6c\x85\x4c\xd1\xa3\xb4\x52\xae\x50\x0e\x16\x4c\ +\x2f\xa8\x2b\x78\x5b\x18\x5b\x78\xb8\x48\xbd\x48\x5a\xd4\x33\xdf\ +\x66\xfe\xea\xf9\x23\x0b\x82\x16\x7c\xbd\x90\xb0\x50\xb8\xb0\xb3\ +\xd8\xb8\x78\x59\xf1\xe0\x22\xbf\x45\xbb\x16\x23\x8b\x53\x17\x77\ +\x2e\x31\x5d\x52\xba\x64\x78\x69\xf0\xd2\x7d\xcb\x68\xcb\xb2\x96\ +\xfd\x50\xe2\x58\x52\x55\xf2\x6a\x79\xdc\xf2\x8e\x52\x83\xd2\xa5\ +\xa5\x43\x2b\x82\x57\x34\x95\xa9\x94\xc9\xcb\x6e\xae\xf4\x5a\xb9\ +\x63\x15\x61\x95\x64\x55\xef\x6a\x97\xd5\x5b\x56\x7f\x2a\x17\x95\ +\x5f\xac\x70\xac\xa8\xae\xf8\xb0\x46\xb8\xe6\xe2\x57\x4e\x5f\xd5\ +\x7c\xf5\x79\x6d\xda\xda\xde\x4a\xb7\xca\xed\xeb\x48\xeb\xa4\xeb\ +\x6e\xac\xf7\x59\xbf\xaf\x4a\xbd\x6a\x41\xd5\xd0\x86\xf0\x0d\xad\ +\x1b\xf1\x8d\xe5\x1b\x5f\x6d\x4a\xde\x74\xa1\x7a\x6a\xf5\x8e\xcd\ +\xb4\xcd\xca\xcd\x03\x35\x61\x35\xed\x5b\xcc\xb6\xac\xdb\xf2\xa1\ +\x36\xa3\xf6\x7a\x9d\x7f\x5d\xcb\x56\xfd\xad\xab\xb7\xbe\xd9\x26\ +\xda\xd6\xbf\xdd\x77\x7b\xf3\x0e\x83\x1d\x15\x3b\xde\xef\x94\xec\ +\xbc\xb5\x2b\x78\x57\x6b\xbd\x45\x7d\xf5\x6e\xd2\xee\x82\xdd\x8f\ +\x1a\x62\x1b\xba\xbf\xe6\x7e\xdd\xb8\x47\x77\x4f\xc5\x9e\x8f\x7b\ +\xa5\x7b\x07\xf6\x45\xef\xeb\x6a\x74\x6f\x6c\xdc\xaf\xbf\xbf\xb2\ +\x09\x6d\x52\x36\x8d\x1e\x48\x3a\x70\xe5\x9b\x80\x6f\xda\x9b\xed\ +\x9a\x77\xb5\x70\x5a\x2a\x0e\xc2\x41\xe5\xc1\x27\xdf\xa6\x7c\x7b\ +\xe3\x50\xe8\xa1\xce\xc3\xdc\xc3\xcd\xdf\x99\x7f\xb7\xf5\x08\xeb\ +\x48\x79\x2b\xd2\x3a\xbf\x75\xac\x2d\xa3\x6d\xa0\x3d\xa1\xbd\xef\ +\xe8\x8c\xa3\x9d\x1d\x5e\x1d\x47\xbe\xb7\xff\x7e\xef\x31\xe3\x63\ +\x75\xc7\x35\x8f\x57\x9e\xa0\x9d\x28\x3d\xf1\xf9\xe4\x82\x93\xe3\ +\xa7\x64\xa7\x9e\x9d\x4e\x3f\x3d\xd4\x99\xdc\x79\xf7\x4c\xfc\x99\ +\x6b\x5d\x51\x5d\xbd\x67\x43\xcf\x9e\x3f\x17\x74\xee\x4c\xb7\x5f\ +\xf7\xc9\xf3\xde\xe7\x8f\x5d\xf0\xbc\x70\xf4\x22\xf7\x62\xdb\x25\ +\xb7\x4b\xad\x3d\xae\x3d\x47\x7e\x70\xfd\xe1\x48\xaf\x5b\x6f\xeb\ +\x65\xf7\xcb\xed\x57\x3c\xae\x74\xf4\x4d\xeb\x3b\xd1\xef\xd3\x7f\ +\xfa\x6a\xc0\xd5\x73\xd7\xf8\xd7\x2e\x5d\x9f\x79\xbd\xef\xc6\xec\ +\x1b\xb7\x6e\x26\xdd\x1c\xb8\x25\xba\xf5\xf8\x76\xf6\xed\x17\x77\ +\x0a\xee\x4c\xdc\x5d\x7a\x8f\x78\xaf\xfc\xbe\xda\xfd\xea\x07\xfa\ +\x0f\xea\x7f\xb4\xfe\xb1\x65\xc0\x6d\xe0\xf8\x60\xc0\x60\xcf\xc3\ +\x59\x0f\xef\x0e\x09\x87\x9e\xfe\x94\xff\xd3\x87\xe1\xd2\x47\xcc\ +\x47\xd5\x23\x46\x23\x8d\x8f\x9d\x1f\x1f\x1b\x0d\x1a\xbd\xf2\x64\ +\xce\x93\xe1\xa7\xb2\xa7\x13\xcf\xca\x7e\x56\xff\x79\xeb\x73\xab\ +\xe7\xdf\xfd\xe2\xfb\x4b\xcf\x58\xfc\xd8\xf0\x0b\xf9\x8b\xcf\xbf\ +\xae\x79\xa9\xf3\x72\xef\xab\xa9\xaf\x3a\xc7\x23\xc7\x1f\xbc\xce\ +\x79\x3d\xf1\xa6\xfc\xad\xce\xdb\x7d\xef\xb8\xef\xba\xdf\xc7\xbd\ +\x1f\x99\x28\xfc\x40\xfe\x50\xf3\xd1\xfa\x63\xc7\xa7\xd0\x4f\xf7\ +\x3e\xe7\x7c\xfe\xfc\x2f\xf7\x84\xf3\xfb\x25\xd2\x9f\x33\x00\x00\ +\x00\x20\x63\x48\x52\x4d\x00\x00\x7a\x25\x00\x00\x80\x83\x00\x00\ +\xf9\xff\x00\x00\x80\xe9\x00\x00\x75\x30\x00\x00\xea\x60\x00\x00\ +\x3a\x98\x00\x00\x17\x6f\x92\x5f\xc5\x46\x00\x00\x06\x13\x49\x44\ +\x41\x54\x78\xda\x7c\x95\x5b\x6c\x5c\x57\x19\x85\xbf\x7d\x3b\xb7\ +\xb9\x7a\x3c\x71\xaa\x26\xb1\x6b\xc2\xa5\xa1\x4d\x2a\x54\x4b\x95\ +\x10\x08\x51\x84\x28\x7d\xa0\x34\x41\x7e\x2b\x3c\x52\x21\x52\x11\ +\x10\x15\xb4\x95\x08\xe2\x01\x41\xa1\xa5\x7d\xe4\x52\x40\x3c\x04\ +\x90\x0a\x52\x40\x02\xb5\x2f\xa4\xa2\x28\xa1\x42\x4a\x1c\x27\x6d\ +\x73\xa9\x62\xc7\x1e\xcf\xd8\x33\xbe\xcc\x78\xae\xe7\xec\xb3\x79\ +\x18\x7b\x92\x12\x89\x2d\x6d\xe9\x3c\x9c\xad\xf5\xff\x6b\xfd\x6b\ +\xfd\xc2\x39\x87\xb5\x96\xdd\xe3\x9c\x43\x08\x31\xfa\xde\x3d\x52\ +\x4a\x1a\x8d\xc6\x43\xef\xbe\x7b\xf5\x68\x92\xd8\x87\x8a\xc5\xc2\ +\x5a\x71\xac\x70\xae\xbc\xa7\xfc\x7a\x26\x8a\x2e\xa4\x36\xbd\xfd\ +\x7f\x01\x38\xad\x35\xe2\xff\x00\x08\xb7\xf3\x42\x29\x45\xbd\xde\ +\xf8\xfc\xdc\xdc\xc5\x57\xb7\xdb\xed\x70\x7c\xbc\xc4\x78\x79\x9c\ +\x6c\x36\x83\x92\xb2\xab\x95\xfe\x45\x18\x86\x2f\x46\x51\x74\x03\ +\x20\x4d\x1d\xe0\x30\xc6\x20\xb9\xf3\x08\x40\x03\x52\x08\x81\x10\ +\x02\xe7\x9c\x59\xa9\x56\x9f\x4b\x6c\x1a\x4e\x4c\xec\xa5\x58\x2c\ +\x12\x46\x01\x41\x18\x52\x2e\xef\x09\x4b\xa5\xd2\x53\xfd\x7e\xff\ +\xad\x46\xa3\xf1\xc5\xe1\x1b\xe4\x0e\x09\xef\x07\x70\xce\x69\x60\ +\x1f\xe0\x00\xbb\x5b\xfd\x8d\x85\xc5\xef\xdd\x5c\xaa\x7c\x3c\x9b\ +\xcd\x10\x45\x21\x41\x10\x20\x85\x04\xe7\xb0\x49\x82\x94\x92\x72\ +\xb9\x5c\x0e\x82\xe0\x4f\x2b\x2b\x2b\x3f\x91\x52\xa6\x4a\xca\x3b\ +\x00\x84\x10\xc2\x17\x42\xb4\x00\xeb\x9c\x43\x29\xc5\x5a\xbd\xf1\ +\xa9\x77\xde\xb9\xf2\x74\x14\x06\xe4\xf3\x79\xc2\xc8\xc7\x78\x1a\ +\xcf\xf3\xf0\x7d\x1f\xad\x35\x00\x49\x92\x90\xcb\xe5\xc4\xe2\xd2\ +\xd2\xb7\xae\x5c\xbd\xf6\x0d\x21\xd5\x9d\x1d\x00\x1d\x60\x6b\x57\ +\x87\x5e\xaf\x97\x9b\xbf\x38\xff\xb2\x94\xc2\x64\xb3\x59\x84\x04\ +\x63\x0c\xc6\x98\x21\x80\xe7\xa1\x94\xc2\x39\x47\x1c\xc7\xdc\x58\ +\x58\xa0\xd7\xeb\x71\xfd\xfa\x7b\xcf\x76\xbb\xbd\xc9\xff\x05\x70\ +\x3b\x97\x5d\x6a\xae\x5d\x7f\xef\xfb\xcd\x56\xeb\x48\xa1\x50\xc0\ +\xf7\x7d\x7c\xdf\x1b\x01\xf8\x9e\x8f\x94\x0a\x21\x04\xd6\x5a\xda\ +\x9d\x36\xd5\x5a\x95\x24\xb1\x08\x21\xcb\x95\x4a\xe5\x33\xec\x88\ +\xf9\x7e\x85\x85\x18\x52\xb3\x56\x7f\xe4\xda\xb5\xeb\x27\x4a\xa5\ +\x12\xb9\x5c\x0e\xdf\xf7\xf1\x3c\x8d\xd2\x0a\xcf\xf3\x30\x5a\xa1\ +\x84\x20\x4d\x53\xfa\xfd\x3e\xb5\xd5\x55\x3a\x9d\x1e\x02\x41\x36\ +\x9b\xa1\xd5\xda\x9e\x1a\x01\xec\xce\xfd\x2e\x97\xfd\x41\xfc\xc9\ +\xf9\xf9\x4b\xbf\x33\xc6\x50\x28\x14\x50\x5a\xa1\x8d\x1c\x55\xee\ +\x1b\x83\x94\x0a\x84\x60\x10\x0f\x68\xac\x37\x58\x5d\x5d\x03\x37\ +\xec\x3c\x8a\x22\xca\xe5\xf2\xcd\x11\x40\xb5\x56\xbb\x8d\x27\xe7\ +\x2d\x2e\x2c\x3d\xbb\xd5\xdc\x2e\xef\xdf\x7f\x37\x9e\x19\x72\x6d\ +\x3c\x83\x67\x0c\xbe\xf1\x50\x4a\x23\x85\xc0\xa6\x29\x9d\x76\x87\ +\x6a\x75\x95\xd4\x0e\xfd\xa4\xb5\x62\xcf\x44\x79\xe9\xae\xbb\x26\ +\x5e\x1b\x01\xac\xaf\x6f\x8d\xe8\x89\x93\xc1\xf3\xb5\x5a\xed\x73\ +\xa5\x52\x89\x4c\x14\x61\x3c\x8d\x36\x0a\xa3\x0d\xc6\x78\x68\xa5\ +\x87\xde\x00\xe2\xc1\x80\x5a\xad\x46\xb3\xd5\x44\x20\xd1\x5a\x33\ +\x5e\x1e\xa7\x58\x2c\x7c\xcd\xc1\xcd\x91\xc8\xa9\x4d\x48\x6d\x82\ +\xc0\xcd\x34\xea\x8d\x27\x83\x20\xa0\x90\x2f\x20\x94\x44\x1b\x85\ +\xd6\x1a\x6d\x0c\xc6\x68\xa4\x92\x23\x61\x37\xb7\x36\xa9\xd5\x56\ +\xc1\x0d\xb5\x88\xa2\x08\xa3\xcd\x1b\xdd\x6e\xf7\x2f\xeb\xeb\xeb\ +\x8c\x3a\x70\xc2\x11\x85\x21\x83\xfe\xe0\xbb\x42\xe0\x4d\x4e\x1e\ +\x60\x6c\xac\x88\x90\x02\xe7\x86\x96\xf7\xb4\x41\x2a\x35\xd2\xab\ +\xd7\xef\xb1\x52\xad\xd2\xeb\x75\x11\x42\x12\x86\x11\x52\xca\x7e\ +\xab\xd5\x7c\xc6\x91\x62\xad\x65\xef\xc4\xc4\x10\x20\xf4\x43\xde\ +\xbe\x7c\xe5\x3b\x67\xce\x9c\x39\x6a\xd3\x94\xa9\xc9\x03\x1c\x7e\ +\xe0\x7e\x66\x66\x1e\x24\x9f\xcb\xe3\xd2\x61\x90\x39\xe7\x10\x08\ +\xe2\x38\x66\xad\xd1\x60\x6d\xb5\x0e\x48\x7c\x3f\xa4\x50\x28\xd0\ +\x6c\x6d\xfd\x2c\x4d\xed\x9b\xad\xed\x16\x61\x18\xde\xea\xe0\xfc\ +\xf9\x0b\xc7\x5f\xf8\xe9\x4b\x3f\xdc\xdc\xdc\xa0\xdb\xeb\xd2\xeb\ +\xf5\x30\xc6\xe3\xde\x43\xf7\xf2\x95\x2f\x3f\xc1\xec\xec\x31\x7c\ +\xdf\x03\x20\x8e\x63\x9a\xad\x26\x37\x17\x6f\x32\x18\xc4\x04\x41\ +\x40\x26\x13\xd1\xef\x77\xaf\x77\xbb\x9d\x17\xd3\x34\x25\x97\xcb\ +\x31\x3d\x3d\x7d\xcb\xc9\x57\xaf\x5c\xfd\x6c\x92\xc4\xe4\x72\x39\ +\xf2\xb9\x3c\xc5\x62\x11\xe3\x19\xe6\xe6\xe6\xf8\xfa\xf1\xa7\x38\ +\x7a\x6c\x96\x0b\x73\x17\x87\x15\x69\x4d\xad\xb6\xca\xe6\xc6\x26\ +\x9e\xe7\x91\xcf\xe7\xf1\x03\x8f\xed\x76\xfb\x47\x40\x2d\x08\x02\ +\xa6\xa6\xa6\x50\xea\xb6\xa8\xc8\xe7\x0b\xe7\xb4\xd6\x23\x97\x9a\ +\x9d\x71\xcc\xe7\xf3\x8c\x97\x4a\x9c\x3b\xf7\x16\x8f\x3f\xfe\x25\ +\x4e\x9d\xfa\x03\xdd\x5e\x8f\xed\xf6\x36\x41\x10\x92\xcd\xe5\x88\ +\x32\x01\xed\xed\xf6\x1b\xd6\xa6\xbf\x14\x42\xb1\x6f\xdf\x7e\x32\ +\x99\x88\x34\x4d\x6f\x51\x34\x39\x35\xf9\xdb\x7d\xfb\xee\x3e\xbe\ +\xbc\x5c\xd9\xeb\xfb\x3e\xce\x39\xa4\x90\x28\x6b\x49\x55\x4a\x79\ +\xdc\xa3\x37\x18\x70\xe2\x9b\xdf\xe6\xfc\xdc\x05\x1e\x7b\xec\x0b\ +\x3b\xd4\x84\x58\x6b\xf9\xfb\xd8\xbf\xff\x75\x63\x72\xb5\xd6\xb0\ +\x5b\x7b\x36\x92\x26\x9b\xf5\xe6\xe6\x4a\x52\x7f\xba\x5b\xeb\xfd\ +\x4a\x9d\x3c\x79\x92\xa5\x4a\xa5\x99\xcd\x64\xe3\xcb\x97\x2e\x3f\ +\xb2\x9b\xa2\xbb\x91\x21\xa5\x44\x0a\x85\xb7\x33\xa6\x73\x17\x2e\ +\x72\x60\xff\x7e\x3e\xf4\x91\x0f\x12\x45\x21\xad\x56\xeb\xb5\x3f\ +\x97\xde\x9c\x5d\x88\x57\x0a\x57\x06\x37\xa8\x26\x75\x36\xd2\x56\ +\xb0\xdd\x6b\x3f\x9a\xb4\x06\x6f\xcb\x9d\x45\xc0\xa1\xfb\x0e\xbd\ +\xf4\xf0\xc3\x9f\x7e\x41\xed\x04\x58\x10\x04\xb7\x5d\x1f\xdf\xf3\ +\xc8\x84\x19\x02\x3f\xe0\xf4\xe9\xbf\xa2\xb5\x26\x4d\xd3\x41\x92\ +\x24\x3f\x58\x8c\xab\xe1\xb5\xc1\x22\x95\x78\x8d\x8d\xb4\xc9\x76\ +\x7f\x9b\xb4\x3e\x50\x54\xe3\xa3\x7a\xb4\x26\x81\x07\x67\x66\x9e\ +\x03\x91\x39\x7b\xf6\xec\x57\x3b\x9d\xce\x50\x0b\xdf\xc7\x5a\x4b\ +\x6a\x2d\x36\x4d\x31\x9e\xa1\xbe\xd6\x60\x7e\x6e\x9e\xfb\x0e\x7f\ +\xf4\x37\xc6\x98\x7f\x2e\xda\x15\xb6\x5c\x7b\xb8\x0b\x01\x3a\x29\ +\x54\x63\xd8\xb0\x6a\x98\xa6\x4e\xe0\x1c\x44\x61\xd8\x3d\x7c\xf8\ +\xfe\x27\x33\xd9\xcc\x3f\xe6\xe7\xe7\x7f\x5c\xab\xd6\x0e\x24\x49\ +\x82\xe7\x79\x23\xf7\x0e\x23\x45\xb2\x5c\xa9\xb4\x1e\xf8\xd8\x91\ +\x97\x33\x99\x0c\xf5\x66\x63\xc0\xc0\x79\x08\x01\xb1\x83\xa5\x3e\ +\x2c\xf4\xc1\x72\x5a\x8f\x36\x01\xa0\x94\x94\x99\x6c\xc6\xdd\x33\ +\x7d\xcf\xef\xc7\x4a\x63\x7f\x5b\x5e\x5e\x3e\x56\xa9\x54\x9e\x68\ +\x34\xd6\x3f\xd1\xe9\x74\x34\x42\x80\x73\x4c\x1f\x9c\xee\x1c\x39\ +\x72\xf8\x84\x10\xe2\x12\x00\xf5\xe4\x38\xeb\xf6\x79\x7a\x69\x9e\ +\x96\x85\x5a\xdc\x21\x71\xcf\x10\xc8\x53\xc3\xb8\x96\x6e\x37\x49\ +\x85\xe7\x69\xa1\xb5\xb6\xc5\x62\x71\x2b\x08\x82\x57\xc6\xc6\xc6\ +\x5e\xe9\xf7\x07\x33\x4a\xc9\x47\x85\xe4\xc3\xc5\x42\x71\xf3\xe0\ +\xc1\x0f\xfc\xba\x38\x56\xf8\x4f\x62\x13\xb4\xd2\x60\xf9\x39\x6d\ +\xbb\xca\x5a\x3c\x4b\xd3\x2a\xfa\xee\x55\x3c\xf1\x47\x80\xff\x0e\ +\x00\x69\xa1\xc1\x0e\x0c\xfa\xb2\x23\x00\x00\x00\x00\x49\x45\x4e\ +\x44\xae\x42\x60\x82\ +\x00\x00\x0d\x2a\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\ +\x01\x00\x9a\x9c\x18\x00\x00\x0a\x4f\x69\x43\x43\x50\x50\x68\x6f\ +\x74\x6f\x73\x68\x6f\x70\x20\x49\x43\x43\x20\x70\x72\x6f\x66\x69\ +\x6c\x65\x00\x00\x78\xda\x9d\x53\x67\x54\x53\xe9\x16\x3d\xf7\xde\ +\xf4\x42\x4b\x88\x80\x94\x4b\x6f\x52\x15\x08\x20\x52\x42\x8b\x80\ +\x14\x91\x26\x2a\x21\x09\x10\x4a\x88\x21\xa1\xd9\x15\x51\xc1\x11\ +\x45\x45\x04\x1b\xc8\xa0\x88\x03\x8e\x8e\x80\x8c\x15\x51\x2c\x0c\ +\x8a\x0a\xd8\x07\xe4\x21\xa2\x8e\x83\xa3\x88\x8a\xca\xfb\xe1\x7b\ +\xa3\x6b\xd6\xbc\xf7\xe6\xcd\xfe\xb5\xd7\x3e\xe7\xac\xf3\x9d\xb3\ +\xcf\x07\xc0\x08\x0c\x96\x48\x33\x51\x35\x80\x0c\xa9\x42\x1e\x11\ +\xe0\x83\xc7\xc4\xc6\xe1\xe4\x2e\x40\x81\x0a\x24\x70\x00\x10\x08\ +\xb3\x64\x21\x73\xfd\x23\x01\x00\xf8\x7e\x3c\x3c\x2b\x22\xc0\x07\ +\xbe\x00\x01\x78\xd3\x0b\x08\x00\xc0\x4d\x9b\xc0\x30\x1c\x87\xff\ +\x0f\xea\x42\x99\x5c\x01\x80\x84\x01\xc0\x74\x91\x38\x4b\x08\x80\ +\x14\x00\x40\x7a\x8e\x42\xa6\x00\x40\x46\x01\x80\x9d\x98\x26\x53\ +\x00\xa0\x04\x00\x60\xcb\x63\x62\xe3\x00\x50\x2d\x00\x60\x27\x7f\ +\xe6\xd3\x00\x80\x9d\xf8\x99\x7b\x01\x00\x5b\x94\x21\x15\x01\xa0\ +\x91\x00\x20\x13\x65\x88\x44\x00\x68\x3b\x00\xac\xcf\x56\x8a\x45\ +\x00\x58\x30\x00\x14\x66\x4b\xc4\x39\x00\xd8\x2d\x00\x30\x49\x57\ +\x66\x48\x00\xb0\xb7\x00\xc0\xce\x10\x0b\xb2\x00\x08\x0c\x00\x30\ +\x51\x88\x85\x29\x00\x04\x7b\x00\x60\xc8\x23\x23\x78\x00\x84\x99\ +\x00\x14\x46\xf2\x57\x3c\xf1\x2b\xae\x10\xe7\x2a\x00\x00\x78\x99\ +\xb2\x3c\xb9\x24\x39\x45\x81\x5b\x08\x2d\x71\x07\x57\x57\x2e\x1e\ +\x28\xce\x49\x17\x2b\x14\x36\x61\x02\x61\x9a\x40\x2e\xc2\x79\x99\ +\x19\x32\x81\x34\x0f\xe0\xf3\xcc\x00\x00\xa0\x91\x15\x11\xe0\x83\ +\xf3\xfd\x78\xce\x0e\xae\xce\xce\x36\x8e\xb6\x0e\x5f\x2d\xea\xbf\ +\x06\xff\x22\x62\x62\xe3\xfe\xe5\xcf\xab\x70\x40\x00\x00\xe1\x74\ +\x7e\xd1\xfe\x2c\x2f\xb3\x1a\x80\x3b\x06\x80\x6d\xfe\xa2\x25\xee\ +\x04\x68\x5e\x0b\xa0\x75\xf7\x8b\x66\xb2\x0f\x40\xb5\x00\xa0\xe9\ +\xda\x57\xf3\x70\xf8\x7e\x3c\x3c\x45\xa1\x90\xb9\xd9\xd9\xe5\xe4\ +\xe4\xd8\x4a\xc4\x42\x5b\x61\xca\x57\x7d\xfe\x67\xc2\x5f\xc0\x57\ +\xfd\x6c\xf9\x7e\x3c\xfc\xf7\xf5\xe0\xbe\xe2\x24\x81\x32\x5d\x81\ +\x47\x04\xf8\xe0\xc2\xcc\xf4\x4c\xa5\x1c\xcf\x92\x09\x84\x62\xdc\ +\xe6\x8f\x47\xfc\xb7\x0b\xff\xfc\x1d\xd3\x22\xc4\x49\x62\xb9\x58\ +\x2a\x14\xe3\x51\x12\x71\x8e\x44\x9a\x8c\xf3\x32\xa5\x22\x89\x42\ +\x92\x29\xc5\x25\xd2\xff\x64\xe2\xdf\x2c\xfb\x03\x3e\xdf\x35\x00\ +\xb0\x6a\x3e\x01\x7b\x91\x2d\xa8\x5d\x63\x03\xf6\x4b\x27\x10\x58\ +\x74\xc0\xe2\xf7\x00\x00\xf2\xbb\x6f\xc1\xd4\x28\x08\x03\x80\x68\ +\x83\xe1\xcf\x77\xff\xef\x3f\xfd\x47\xa0\x25\x00\x80\x66\x49\x92\ +\x71\x00\x00\x5e\x44\x24\x2e\x54\xca\xb3\x3f\xc7\x08\x00\x00\x44\ +\xa0\x81\x2a\xb0\x41\x1b\xf4\xc1\x18\x2c\xc0\x06\x1c\xc1\x05\xdc\ +\xc1\x0b\xfc\x60\x36\x84\x42\x24\xc4\xc2\x42\x10\x42\x0a\x64\x80\ +\x1c\x72\x60\x29\xac\x82\x42\x28\x86\xcd\xb0\x1d\x2a\x60\x2f\xd4\ +\x40\x1d\x34\xc0\x51\x68\x86\x93\x70\x0e\x2e\xc2\x55\xb8\x0e\x3d\ +\x70\x0f\xfa\x61\x08\x9e\xc1\x28\xbc\x81\x09\x04\x41\xc8\x08\x13\ +\x61\x21\xda\x88\x01\x62\x8a\x58\x23\x8e\x08\x17\x99\x85\xf8\x21\ +\xc1\x48\x04\x12\x8b\x24\x20\xc9\x88\x14\x51\x22\x4b\x91\x35\x48\ +\x31\x52\x8a\x54\x20\x55\x48\x1d\xf2\x3d\x72\x02\x39\x87\x5c\x46\ +\xba\x91\x3b\xc8\x00\x32\x82\xfc\x86\xbc\x47\x31\x94\x81\xb2\x51\ +\x3d\xd4\x0c\xb5\x43\xb9\xa8\x37\x1a\x84\x46\xa2\x0b\xd0\x64\x74\ +\x31\x9a\x8f\x16\xa0\x9b\xd0\x72\xb4\x1a\x3d\x8c\x36\xa1\xe7\xd0\ +\xab\x68\x0f\xda\x8f\x3e\x43\xc7\x30\xc0\xe8\x18\x07\x33\xc4\x6c\ +\x30\x2e\xc6\xc3\x42\xb1\x38\x2c\x09\x93\x63\xcb\xb1\x22\xac\x0c\ +\xab\xc6\x1a\xb0\x56\xac\x03\xbb\x89\xf5\x63\xcf\xb1\x77\x04\x12\ +\x81\x45\xc0\x09\x36\x04\x77\x42\x20\x61\x1e\x41\x48\x58\x4c\x58\ +\x4e\xd8\x48\xa8\x20\x1c\x24\x34\x11\xda\x09\x37\x09\x03\x84\x51\ +\xc2\x27\x22\x93\xa8\x4b\xb4\x26\xba\x11\xf9\xc4\x18\x62\x32\x31\ +\x87\x58\x48\x2c\x23\xd6\x12\x8f\x13\x2f\x10\x7b\x88\x43\xc4\x37\ +\x24\x12\x89\x43\x32\x27\xb9\x90\x02\x49\xb1\xa4\x54\xd2\x12\xd2\ +\x46\xd2\x6e\x52\x23\xe9\x2c\xa9\x9b\x34\x48\x1a\x23\x93\xc9\xda\ +\x64\x6b\xb2\x07\x39\x94\x2c\x20\x2b\xc8\x85\xe4\x9d\xe4\xc3\xe4\ +\x33\xe4\x1b\xe4\x21\xf2\x5b\x0a\x9d\x62\x40\x71\xa4\xf8\x53\xe2\ +\x28\x52\xca\x6a\x4a\x19\xe5\x10\xe5\x34\xe5\x06\x65\x98\x32\x41\ +\x55\xa3\x9a\x52\xdd\xa8\xa1\x54\x11\x35\x8f\x5a\x42\xad\xa1\xb6\ +\x52\xaf\x51\x87\xa8\x13\x34\x75\x9a\x39\xcd\x83\x16\x49\x4b\xa5\ +\xad\xa2\x95\xd3\x1a\x68\x17\x68\xf7\x69\xaf\xe8\x74\xba\x11\xdd\ +\x95\x1e\x4e\x97\xd0\x57\xd2\xcb\xe9\x47\xe8\x97\xe8\x03\xf4\x77\ +\x0c\x0d\x86\x15\x83\xc7\x88\x67\x28\x19\x9b\x18\x07\x18\x67\x19\ +\x77\x18\xaf\x98\x4c\xa6\x19\xd3\x8b\x19\xc7\x54\x30\x37\x31\xeb\ +\x98\xe7\x99\x0f\x99\x6f\x55\x58\x2a\xb6\x2a\x7c\x15\x91\xca\x0a\ +\x95\x4a\x95\x26\x95\x1b\x2a\x2f\x54\xa9\xaa\xa6\xaa\xde\xaa\x0b\ +\x55\xf3\x55\xcb\x54\x8f\xa9\x5e\x53\x7d\xae\x46\x55\x33\x53\xe3\ +\xa9\x09\xd4\x96\xab\x55\xaa\x9d\x50\xeb\x53\x1b\x53\x67\xa9\x3b\ +\xa8\x87\xaa\x67\xa8\x6f\x54\x3f\xa4\x7e\x59\xfd\x89\x06\x59\xc3\ +\x4c\xc3\x4f\x43\xa4\x51\xa0\xb1\x5f\xe3\xbc\xc6\x20\x0b\x63\x19\ +\xb3\x78\x2c\x21\x6b\x0d\xab\x86\x75\x81\x35\xc4\x26\xb1\xcd\xd9\ +\x7c\x76\x2a\xbb\x98\xfd\x1d\xbb\x8b\x3d\xaa\xa9\xa1\x39\x43\x33\ +\x4a\x33\x57\xb3\x52\xf3\x94\x66\x3f\x07\xe3\x98\x71\xf8\x9c\x74\ +\x4e\x09\xe7\x28\xa7\x97\xf3\x7e\x8a\xde\x14\xef\x29\xe2\x29\x1b\ +\xa6\x34\x4c\xb9\x31\x65\x5c\x6b\xaa\x96\x97\x96\x58\xab\x48\xab\ +\x51\xab\x47\xeb\xbd\x36\xae\xed\xa7\x9d\xa6\xbd\x45\xbb\x59\xfb\ +\x81\x0e\x41\xc7\x4a\x27\x5c\x27\x47\x67\x8f\xce\x05\x9d\xe7\x53\ +\xd9\x53\xdd\xa7\x0a\xa7\x16\x4d\x3d\x3a\xf5\xae\x2e\xaa\x6b\xa5\ +\x1b\xa1\xbb\x44\x77\xbf\x6e\xa7\xee\x98\x9e\xbe\x5e\x80\x9e\x4c\ +\x6f\xa7\xde\x79\xbd\xe7\xfa\x1c\x7d\x2f\xfd\x54\xfd\x6d\xfa\xa7\ +\xf5\x47\x0c\x58\x06\xb3\x0c\x24\x06\xdb\x0c\xce\x18\x3c\xc5\x35\ +\x71\x6f\x3c\x1d\x2f\xc7\xdb\xf1\x51\x43\x5d\xc3\x40\x43\xa5\x61\ +\x95\x61\x97\xe1\x84\x91\xb9\xd1\x3c\xa3\xd5\x46\x8d\x46\x0f\x8c\ +\x69\xc6\x5c\xe3\x24\xe3\x6d\xc6\x6d\xc6\xa3\x26\x06\x26\x21\x26\ +\x4b\x4d\xea\x4d\xee\x9a\x52\x4d\xb9\xa6\x29\xa6\x3b\x4c\x3b\x4c\ +\xc7\xcd\xcc\xcd\xa2\xcd\xd6\x99\x35\x9b\x3d\x31\xd7\x32\xe7\x9b\ +\xe7\x9b\xd7\x9b\xdf\xb7\x60\x5a\x78\x5a\x2c\xb6\xa8\xb6\xb8\x65\ +\x49\xb2\xe4\x5a\xa6\x59\xee\xb6\xbc\x6e\x85\x5a\x39\x59\xa5\x58\ +\x55\x5a\x5d\xb3\x46\xad\x9d\xad\x25\xd6\xbb\xad\xbb\xa7\x11\xa7\ +\xb9\x4e\x93\x4e\xab\x9e\xd6\x67\xc3\xb0\xf1\xb6\xc9\xb6\xa9\xb7\ +\x19\xb0\xe5\xd8\x06\xdb\xae\xb6\x6d\xb6\x7d\x61\x67\x62\x17\x67\ +\xb7\xc5\xae\xc3\xee\x93\xbd\x93\x7d\xba\x7d\x8d\xfd\x3d\x07\x0d\ +\x87\xd9\x0e\xab\x1d\x5a\x1d\x7e\x73\xb4\x72\x14\x3a\x56\x3a\xde\ +\x9a\xce\x9c\xee\x3f\x7d\xc5\xf4\x96\xe9\x2f\x67\x58\xcf\x10\xcf\ +\xd8\x33\xe3\xb6\x13\xcb\x29\xc4\x69\x9d\x53\x9b\xd3\x47\x67\x17\ +\x67\xb9\x73\x83\xf3\x88\x8b\x89\x4b\x82\xcb\x2e\x97\x3e\x2e\x9b\ +\x1b\xc6\xdd\xc8\xbd\xe4\x4a\x74\xf5\x71\x5d\xe1\x7a\xd2\xf5\x9d\ +\x9b\xb3\x9b\xc2\xed\xa8\xdb\xaf\xee\x36\xee\x69\xee\x87\xdc\x9f\ +\xcc\x34\x9f\x29\x9e\x59\x33\x73\xd0\xc3\xc8\x43\xe0\x51\xe5\xd1\ +\x3f\x0b\x9f\x95\x30\x6b\xdf\xac\x7e\x4f\x43\x4f\x81\x67\xb5\xe7\ +\x23\x2f\x63\x2f\x91\x57\xad\xd7\xb0\xb7\xa5\x77\xaa\xf7\x61\xef\ +\x17\x3e\xf6\x3e\x72\x9f\xe3\x3e\xe3\x3c\x37\xde\x32\xde\x59\x5f\ +\xcc\x37\xc0\xb7\xc8\xb7\xcb\x4f\xc3\x6f\x9e\x5f\x85\xdf\x43\x7f\ +\x23\xff\x64\xff\x7a\xff\xd1\x00\xa7\x80\x25\x01\x67\x03\x89\x81\ +\x41\x81\x5b\x02\xfb\xf8\x7a\x7c\x21\xbf\x8e\x3f\x3a\xdb\x65\xf6\ +\xb2\xd9\xed\x41\x8c\xa0\xb9\x41\x15\x41\x8f\x82\xad\x82\xe5\xc1\ +\xad\x21\x68\xc8\xec\x90\xad\x21\xf7\xe7\x98\xce\x91\xce\x69\x0e\ +\x85\x50\x7e\xe8\xd6\xd0\x07\x61\xe6\x61\x8b\xc3\x7e\x0c\x27\x85\ +\x87\x85\x57\x86\x3f\x8e\x70\x88\x58\x1a\xd1\x31\x97\x35\x77\xd1\ +\xdc\x43\x73\xdf\x44\xfa\x44\x96\x44\xde\x9b\x67\x31\x4f\x39\xaf\ +\x2d\x4a\x35\x2a\x3e\xaa\x2e\x6a\x3c\xda\x37\xba\x34\xba\x3f\xc6\ +\x2e\x66\x59\xcc\xd5\x58\x9d\x58\x49\x6c\x4b\x1c\x39\x2e\x2a\xae\ +\x36\x6e\x6c\xbe\xdf\xfc\xed\xf3\x87\xe2\x9d\xe2\x0b\xe3\x7b\x17\ +\x98\x2f\xc8\x5d\x70\x79\xa1\xce\xc2\xf4\x85\xa7\x16\xa9\x2e\x12\ +\x2c\x3a\x96\x40\x4c\x88\x4e\x38\x94\xf0\x41\x10\x2a\xa8\x16\x8c\ +\x25\xf2\x13\x77\x25\x8e\x0a\x79\xc2\x1d\xc2\x67\x22\x2f\xd1\x36\ +\xd1\x88\xd8\x43\x5c\x2a\x1e\x4e\xf2\x48\x2a\x4d\x7a\x92\xec\x91\ +\xbc\x35\x79\x24\xc5\x33\xa5\x2c\xe5\xb9\x84\x27\xa9\x90\xbc\x4c\ +\x0d\x4c\xdd\x9b\x3a\x9e\x16\x9a\x76\x20\x6d\x32\x3d\x3a\xbd\x31\ +\x83\x92\x91\x90\x71\x42\xaa\x21\x4d\x93\xb6\x67\xea\x67\xe6\x66\ +\x76\xcb\xac\x65\x85\xb2\xfe\xc5\x6e\x8b\xb7\x2f\x1e\x95\x07\xc9\ +\x6b\xb3\x90\xac\x05\x59\x2d\x0a\xb6\x42\xa6\xe8\x54\x5a\x28\xd7\ +\x2a\x07\xb2\x67\x65\x57\x66\xbf\xcd\x89\xca\x39\x96\xab\x9e\x2b\ +\xcd\xed\xcc\xb3\xca\xdb\x90\x37\x9c\xef\x9f\xff\xed\x12\xc2\x12\ +\xe1\x92\xb6\xa5\x86\x4b\x57\x2d\x1d\x58\xe6\xbd\xac\x6a\x39\xb2\ +\x3c\x71\x79\xdb\x0a\xe3\x15\x05\x2b\x86\x56\x06\xac\x3c\xb8\x8a\ +\xb6\x2a\x6d\xd5\x4f\xab\xed\x57\x97\xae\x7e\xbd\x26\x7a\x4d\x6b\ +\x81\x5e\xc1\xca\x82\xc1\xb5\x01\x6b\xeb\x0b\x55\x0a\xe5\x85\x7d\ +\xeb\xdc\xd7\xed\x5d\x4f\x58\x2f\x59\xdf\xb5\x61\xfa\x86\x9d\x1b\ +\x3e\x15\x89\x8a\xae\x14\xdb\x17\x97\x15\x7f\xd8\x28\xdc\x78\xe5\ +\x1b\x87\x6f\xca\xbf\x99\xdc\x94\xb4\xa9\xab\xc4\xb9\x64\xcf\x66\ +\xd2\x66\xe9\xe6\xde\x2d\x9e\x5b\x0e\x96\xaa\x97\xe6\x97\x0e\x6e\ +\x0d\xd9\xda\xb4\x0d\xdf\x56\xb4\xed\xf5\xf6\x45\xdb\x2f\x97\xcd\ +\x28\xdb\xbb\x83\xb6\x43\xb9\xa3\xbf\x3c\xb8\xbc\x65\xa7\xc9\xce\ +\xcd\x3b\x3f\x54\xa4\x54\xf4\x54\xfa\x54\x36\xee\xd2\xdd\xb5\x61\ +\xd7\xf8\x6e\xd1\xee\x1b\x7b\xbc\xf6\x34\xec\xd5\xdb\x5b\xbc\xf7\ +\xfd\x3e\xc9\xbe\xdb\x55\x01\x55\x4d\xd5\x66\xd5\x65\xfb\x49\xfb\ +\xb3\xf7\x3f\xae\x89\xaa\xe9\xf8\x96\xfb\x6d\x5d\xad\x4e\x6d\x71\ +\xed\xc7\x03\xd2\x03\xfd\x07\x23\x0e\xb6\xd7\xb9\xd4\xd5\x1d\xd2\ +\x3d\x54\x52\x8f\xd6\x2b\xeb\x47\x0e\xc7\x1f\xbe\xfe\x9d\xef\x77\ +\x2d\x0d\x36\x0d\x55\x8d\x9c\xc6\xe2\x23\x70\x44\x79\xe4\xe9\xf7\ +\x09\xdf\xf7\x1e\x0d\x3a\xda\x76\x8c\x7b\xac\xe1\x07\xd3\x1f\x76\ +\x1d\x67\x1d\x2f\x6a\x42\x9a\xf2\x9a\x46\x9b\x53\x9a\xfb\x5b\x62\ +\x5b\xba\x4f\xcc\x3e\xd1\xd6\xea\xde\x7a\xfc\x47\xdb\x1f\x0f\x9c\ +\x34\x3c\x59\x79\x4a\xf3\x54\xc9\x69\xda\xe9\x82\xd3\x93\x67\xf2\ +\xcf\x8c\x9d\x95\x9d\x7d\x7e\x2e\xf9\xdc\x60\xdb\xa2\xb6\x7b\xe7\ +\x63\xce\xdf\x6a\x0f\x6f\xef\xba\x10\x74\xe1\xd2\x45\xff\x8b\xe7\ +\x3b\xbc\x3b\xce\x5c\xf2\xb8\x74\xf2\xb2\xdb\xe5\x13\x57\xb8\x57\ +\x9a\xaf\x3a\x5f\x6d\xea\x74\xea\x3c\xfe\x93\xd3\x4f\xc7\xbb\x9c\ +\xbb\x9a\xae\xb9\x5c\x6b\xb9\xee\x7a\xbd\xb5\x7b\x66\xf7\xe9\x1b\ +\x9e\x37\xce\xdd\xf4\xbd\x79\xf1\x16\xff\xd6\xd5\x9e\x39\x3d\xdd\ +\xbd\xf3\x7a\x6f\xf7\xc5\xf7\xf5\xdf\x16\xdd\x7e\x72\x27\xfd\xce\ +\xcb\xbb\xd9\x77\x27\xee\xad\xbc\x4f\xbc\x5f\xf4\x40\xed\x41\xd9\ +\x43\xdd\x87\xd5\x3f\x5b\xfe\xdc\xd8\xef\xdc\x7f\x6a\xc0\x77\xa0\ +\xf3\xd1\xdc\x47\xf7\x06\x85\x83\xcf\xfe\x91\xf5\x8f\x0f\x43\x05\ +\x8f\x99\x8f\xcb\x86\x0d\x86\xeb\x9e\x38\x3e\x39\x39\xe2\x3f\x72\ +\xfd\xe9\xfc\xa7\x43\xcf\x64\xcf\x26\x9e\x17\xfe\xa2\xfe\xcb\xae\ +\x17\x16\x2f\x7e\xf8\xd5\xeb\xd7\xce\xd1\x98\xd1\xa1\x97\xf2\x97\ +\x93\xbf\x6d\x7c\xa5\xfd\xea\xc0\xeb\x19\xaf\xdb\xc6\xc2\xc6\x1e\ +\xbe\xc9\x78\x33\x31\x5e\xf4\x56\xfb\xed\xc1\x77\xdc\x77\x1d\xef\ +\xa3\xdf\x0f\x4f\xe4\x7c\x20\x7f\x28\xff\x68\xf9\xb1\xf5\x53\xd0\ +\xa7\xfb\x93\x19\x93\x93\xff\x04\x03\x98\xf3\xfc\x63\x33\x2d\xdb\ +\x00\x00\x00\x20\x63\x48\x52\x4d\x00\x00\x7a\x25\x00\x00\x80\x83\ +\x00\x00\xf9\xff\x00\x00\x80\xe9\x00\x00\x75\x30\x00\x00\xea\x60\ +\x00\x00\x3a\x98\x00\x00\x17\x6f\x92\x5f\xc5\x46\x00\x00\x02\x55\ +\x49\x44\x41\x54\x78\xda\x5c\x91\xcd\x8a\x1d\x55\x14\x85\xbf\x53\ +\x55\xdd\xe9\x4e\x2e\x69\xcc\x03\x04\x04\xa1\x1d\x18\x15\x32\x15\ +\x1c\x05\xae\xa3\x8c\x7c\x82\x40\xc4\x51\xa4\x85\xbc\x82\x23\x07\ +\xa2\x20\x08\x3e\x84\x8e\x04\x47\x82\xe0\x30\x12\x47\x81\x06\x21\ +\xf3\x98\xee\x7b\xab\xea\xfc\xee\xbd\x1c\x54\x75\xfb\x73\xe0\x70\ +\xd8\x83\xb5\xd6\xb7\xd7\x09\xef\x9e\x7f\xcc\x9d\xfe\x36\x27\xdd\ +\x86\x5b\xdd\x31\x87\xe1\xe0\x34\x10\x1e\x1b\xfe\x61\x51\x7d\x3f\ +\xab\x10\x3d\xff\x3e\x79\xfc\x65\xf2\xf8\xed\xe8\xf3\xf9\xac\xc4\ +\xec\x89\xac\x42\x77\xd2\x6d\x38\xe9\x36\x6c\xba\x9b\xdc\x08\x87\ +\x67\x5d\xe8\x9e\x19\x7e\x96\x55\xee\x47\x4f\xfd\xec\xa9\x4f\xca\ +\xf7\x8b\xea\x59\xa5\x3d\x37\xfc\xcc\x64\x38\x8e\x10\xc3\xa6\xbb\ +\xc9\x71\x77\x74\xd8\x87\xfe\x07\xc7\xb7\xcd\x8d\xa8\xcc\xe4\x91\ +\xe8\x99\xac\x42\x55\xa3\xa8\x92\x94\x8f\xb3\xca\x97\x59\xf5\x41\ +\x51\x7d\x58\x54\xcb\x70\x18\x06\x80\xaf\x9b\xda\xb6\xaa\x11\x95\ +\xd9\xfb\xc4\xe8\x91\xe4\x99\xa2\x8a\xe1\x98\x6c\x35\x29\x44\xcf\ +\xdb\xe4\xf9\x0b\x9a\x3e\x1f\x04\xf7\x8a\xea\x23\xd3\x92\x7c\x69\ +\x23\x97\x3e\x32\xfa\x4c\x56\xc1\xb4\xa0\x0a\x61\x72\x92\x32\xd5\ +\x2a\x44\x3d\xa1\xfa\x77\x43\x51\x7d\x5c\xd5\xfa\xb4\x26\xbf\xb6\ +\x3d\x17\xb6\x23\x2a\x53\xd5\x10\x02\x20\x10\x68\x32\x54\x1d\xb2\ +\x43\xf4\x9e\xa2\x4f\x87\xd9\xd3\xd6\x30\xa2\x2f\x06\x7b\x9f\x98\ +\x95\x28\xaa\xb8\x16\xf1\xd5\x51\x5b\xc5\xb3\x43\x74\xc8\xfa\x60\ +\x18\x7d\xbe\xdb\x30\xa2\xa7\xeb\xe2\xaa\x1a\x2e\x5d\xa7\x2f\x6a\ +\xa0\x09\x8a\x20\x39\x4c\x0e\xc9\xef\x0e\x7b\x9f\x68\x18\xd9\x0b\ +\x69\x6d\xfc\x6a\xef\xff\x88\x5d\x8b\x41\x5a\xd3\x67\x83\x28\x86\ +\xbd\xcf\x2f\xab\xea\x5b\x55\x8d\x4a\xa3\x52\xd1\xff\xd0\xd1\x2a\ +\xce\x5a\xf0\xc7\xf5\x16\xbd\xec\x76\x3e\xfe\x74\xe9\x23\x3b\x9f\ +\x88\x2d\x61\xd5\xa0\x0a\x4c\xff\xa4\x96\x55\xb8\x37\xb8\x34\xb8\ +\x6c\x30\x1a\x64\xff\x35\xf0\xfc\xbd\x7b\x14\x7f\x46\x53\x4f\x13\ +\xd7\xe4\x61\x7d\x9d\xa5\xb8\xc9\xe1\xa2\xc1\x85\x2d\x46\x55\xc6\ +\x41\x78\x7b\x20\xf9\x1f\x64\xff\x86\xac\x27\xb4\x35\xf1\xca\xc4\ +\x56\x92\xb4\x22\xef\xae\xc5\x10\xf8\x8a\xa3\xee\x7c\x58\x77\x7b\ +\x4a\xf4\x53\x92\x6f\xa9\x2b\xba\xfd\xab\xf5\xec\x0b\xf2\xec\xcb\ +\x0c\x3f\x72\xdc\x3d\xe5\x46\xa0\xc3\x05\xae\x42\xd3\x47\x14\xff\ +\x8c\xd9\x47\x76\x06\xaf\x1b\xfc\xd5\xe0\x55\x85\x57\x6d\xd9\x3d\ +\xfa\x88\xeb\x13\x0e\xc2\x43\x6e\x75\xc6\xed\x9e\xc0\x6f\xef\x2c\ +\xdf\x32\x1a\xec\x1d\x26\x7b\x93\xd9\x1f\x31\xfb\x03\x92\x9f\x52\ +\x05\x55\x2f\x70\xfd\x4c\x17\xbe\xe7\x28\xfc\xc9\xa6\x87\x3b\x03\ +\xbc\x31\xf0\xf7\x00\x1f\xdf\x11\x08\xaa\x52\x16\x46\x00\x00\x00\ +\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +" + +qt_resource_name = "\ +\x00\x09\ +\x0c\x78\x54\x88\ +\x00\x6e\ +\x00\x65\x00\x77\x00\x50\x00\x72\x00\x65\x00\x66\x00\x69\x00\x78\ +\x00\x06\ +\x07\x03\x7d\xc3\ +\x00\x69\ +\x00\x6d\x00\x61\x00\x67\x00\x65\x00\x73\ +\x00\x18\ +\x02\x47\xd6\x47\ +\x00\x63\ +\x00\x61\x00\x6e\x00\x2d\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x2d\x00\x32\x00\x34\x00\x70\x00\x78\x00\x2d\x00\x79\x00\x65\x00\x6c\ +\x00\x6c\x00\x6f\x00\x77\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x08\ +\x0c\x57\x58\x67\ +\x00\x73\ +\x00\x65\x00\x6e\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x0e\ +\x02\x47\x93\x47\ +\x00\x79\ +\x00\x65\x00\x6c\x00\x6c\x00\x6f\x00\x77\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x08\ +\x0c\x47\x58\x67\ +\x00\x73\ +\x00\x65\x00\x6e\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x0f\ +\x05\x46\x9a\xc7\ +\x00\x61\ +\x00\x64\x00\x64\x00\x72\x00\x65\x00\x73\x00\x73\x00\x62\x00\x6f\x00\x6f\x00\x6b\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x11\ +\x03\x89\x73\x27\ +\x00\x63\ +\x00\x61\x00\x6e\x00\x2d\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x2d\x00\x31\x00\x36\x00\x70\x00\x78\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\ +\x00\x09\ +\x09\x6b\xb7\xc7\ +\x00\x69\ +\x00\x6e\x00\x62\x00\x6f\x00\x78\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x11\ +\x02\xa0\x44\xa7\ +\x00\x73\ +\x00\x75\x00\x62\x00\x73\x00\x63\x00\x72\x00\x69\x00\x70\x00\x74\x00\x69\x00\x6f\x00\x6e\x00\x73\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\ +\x00\x15\ +\x0c\xfc\x45\x87\ +\x00\x63\ +\x00\x61\x00\x6e\x00\x2d\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x2d\x00\x32\x00\x34\x00\x70\x00\x78\x00\x2d\x00\x72\x00\x65\x00\x64\ +\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x11\ +\x05\x89\x73\x07\ +\x00\x63\ +\x00\x61\x00\x6e\x00\x2d\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x2d\x00\x32\x00\x34\x00\x70\x00\x78\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\ +\x00\x0e\ +\x0a\x51\x2d\xe7\ +\x00\x69\ +\x00\x64\x00\x65\x00\x6e\x00\x74\x00\x69\x00\x74\x00\x69\x00\x65\x00\x73\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x0b\ +\x0a\xd0\x22\xa7\ +\x00\x72\ +\x00\x65\x00\x64\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x0d\ +\x02\xe8\x12\x87\ +\x00\x62\ +\x00\x6c\x00\x61\x00\x63\x00\x6b\x00\x6c\x00\x69\x00\x73\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x11\ +\x07\x34\x2d\xc7\ +\x00\x6e\ +\x00\x65\x00\x74\x00\x77\x00\x6f\x00\x72\x00\x6b\x00\x73\x00\x74\x00\x61\x00\x74\x00\x75\x00\x73\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\ +\x00\x17\ +\x00\xd3\x62\xc7\ +\x00\x63\ +\x00\x61\x00\x6e\x00\x2d\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x2d\x00\x32\x00\x34\x00\x70\x00\x78\x00\x2d\x00\x67\x00\x72\x00\x65\ +\x00\x65\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x0d\ +\x07\x76\xdf\x07\ +\x00\x67\ +\x00\x72\x00\x65\x00\x65\x00\x6e\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ +" + +qt_resource_struct = "\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ +\x00\x00\x00\x18\x00\x02\x00\x00\x00\x10\x00\x00\x00\x03\ +\x00\x00\x02\x18\x00\x00\x00\x00\x00\x01\x00\x00\x70\x57\ +\x00\x00\x00\x76\x00\x00\x00\x00\x00\x01\x00\x00\x13\x07\ +\x00\x00\x00\x2a\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x01\x12\x00\x00\x00\x00\x00\x01\x00\x00\x36\x91\ +\x00\x00\x01\xd0\x00\x00\x00\x00\x00\x01\x00\x00\x6a\xea\ +\x00\x00\x00\xd2\x00\x00\x00\x00\x00\x01\x00\x00\x25\xc8\ +\x00\x00\x00\xae\x00\x00\x00\x00\x00\x01\x00\x00\x23\x23\ +\x00\x00\x01\x6a\x00\x00\x00\x00\x00\x01\x00\x00\x49\xf5\ +\x00\x00\x01\xf0\x00\x00\x00\x00\x00\x01\x00\x00\x6d\xde\ +\x00\x00\x02\x4c\x00\x00\x00\x00\x00\x01\x00\x00\x81\x41\ +\x00\x00\x00\xfa\x00\x00\x00\x00\x00\x01\x00\x00\x33\xde\ +\x00\x00\x01\x92\x00\x00\x00\x00\x00\x01\x00\x00\x5a\x9d\ +\x00\x00\x01\xb4\x00\x00\x00\x00\x00\x01\x00\x00\x5e\x07\ +\x00\x00\x00\x98\x00\x00\x00\x00\x00\x01\x00\x00\x1f\xe8\ +\x00\x00\x00\x60\x00\x00\x00\x00\x00\x01\x00\x00\x10\xdf\ +\x00\x00\x01\x3a\x00\x00\x00\x00\x00\x01\x00\x00\x39\x0c\ +" + +def qInitResources(): + QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) + +def qCleanupResources(): + QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) + +qInitResources() diff --git a/src/bitmessagecli.py b/src/bitmessagecli.py deleted file mode 100644 index 84c618af..00000000 --- a/src/bitmessagecli.py +++ /dev/null @@ -1,1887 +0,0 @@ -#!/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. - -This is an example of a daemon client for PyBitmessage 0.6.2, by .dok (Version 0.3.1) , modified - -TODO: fix the following (currently ignored) violations: - -""" - -import datetime -import imghdr -import json -import ntpath -import os -import socket -import sys -import time -import xmlrpclib - -from bmconfigparser import config - - -api = '' -keysName = 'keys.dat' -keysPath = 'keys.dat' -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""" - - global usrPrompt - - print('\n' + message) - uInput = raw_input('> ') - - 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) - - else: - return uInput - - -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""" - - APPNAME = "PyBitmessage" - if sys.platform == 'darwin': - if "HOME" in os.environ: - dataFolder = os.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) - - elif 'win32' in sys.platform or 'win64' in sys.platform: - dataFolder = os.path.join(os.environ['APPDATA'], APPNAME) + '\\' - else: - dataFolder = os.path.expanduser(os.path.join("~", ".config/" + APPNAME + "/")) - return dataFolder - - -def configInit(): - """Initialised the configuration""" - - config.add_section('bitmessagesettings') - # Sets the bitmessage port to stop the warning about the api not properly - # being setup. This is in the event that the keys.dat is in a different - # directory or is created locally to connect to a machine remotely. - config.set('bitmessagesettings', 'port', '8444') - config.set('bitmessagesettings', 'apienabled', 'true') # Sets apienabled to true in keys.dat - - 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') - - -def apiInit(apiEnabled): - """Initialise the API""" - - global usrPrompt - config.read(keysPath) - - if apiEnabled is 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 - with open(keysPath, 'wb') as configfile: - config.write(configfile) - - print('Done') - restartBmNotify() - return True - - elif uInput == "n": - print(' \n************************************************************') - print(' Daemon will not work when the API is disabled. ') - print(' Please refer to the Bitmessage Wiki on how to setup the API.') - print(' ************************************************************\n') - usrPrompt = 1 - main() - - else: - print('\n Invalid Entry\n') - usrPrompt = 1 - main() - - elif apiEnabled: # 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') - 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(' ') - - apiUsr = userInput("API Username") - apiPwd = userInput("API Password") - 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') - uInput = 1 - main() - - print(' -----------------------------------\n') - - # sets the bitmessage port to stop the warning about the api not properly - # being setup. This is in the event that the keys.dat is in a different - # directory or is created locally to connect to a machine remotely. - config.set('bitmessagesettings', 'port', '8444') - config.set('bitmessagesettings', 'apienabled', 'true') - config.set('bitmessagesettings', 'apiport', apiPort) - config.set('bitmessagesettings', 'apiinterface', '127.0.0.1') - config.set('bitmessagesettings', 'apiusername', apiUsr) - config.set('bitmessagesettings', 'apipassword', apiPwd) - config.set('bitmessagesettings', 'daemon', daemon) - with open(keysPath, 'wb') as configfile: - config.write(configfile) - - print('\n Finished configuring the keys.dat file with API information.\n') - restartBmNotify() - return True - - elif uInput == "n": - print('\n ***********************************************************') - print(' Please refer to the Bitmessage Wiki on how to setup the API.') - print(' ***********************************************************\n') - usrPrompt = 1 - main() - else: - 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 - - try: - 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. - appDataFolder = lookupAppdataFolder() - keysPath = appDataFolder + keysPath - 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') - - 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"): - configInit() - keysPath = keysName - usrPrompt = 0 - main() - elif uInput in ("n", "no"): - print('\n Trying Again.\n') - usrPrompt = 0 - main() - else: - print('\n Invalid Input.\n') - - usrPrompt = 1 - main() - - try: # checks to make sure that everyting is configured correctly. Excluding apiEnabled, it is checked after - config.get('bitmessagesettings', 'apiport') - config.get('bitmessagesettings', 'apiinterface') - config.get('bitmessagesettings', 'apiusername') - config.get('bitmessagesettings', 'apipassword') - - 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(config.safeGetBoolean('bitmessagesettings','apienabled')) - # #if false it will prompt the user, if true it will return true - - config.read(keysPath) # read again since changes have been made - apiPort = int(config.get('bitmessagesettings', 'apiport')) - apiInterface = config.get('bitmessagesettings', 'apiinterface') - apiUsername = config.get('bitmessagesettings', 'apiusername') - apiPassword = config.get('bitmessagesettings', 'apipassword') - - print('\n API data successfully imported.\n') - - # Build the api credentials - return "http://" + apiUsername + ":" + apiPassword + "@" + apiInterface + ":" + str(apiPort) + "/" - - -# End keys.dat interactions - - -def apiTest(): - """Tests the API connection to bitmessage. Returns true if it is connected.""" - - try: - result = api.add(2, 3) - except: # noqa:E722 - return False - - return result == 5 - - -def bmSettings(): - """Allows the viewing and modification of keys.dat settings.""" - - global keysPath - global usrPrompt - - keysPath = 'keys.dat' - - config.read(keysPath) # Read the keys.dat - try: - port = config.get('bitmessagesettings', 'port') - except: # noqa:E722 - 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') - defaultnoncetrialsperbyte = config.get('bitmessagesettings', 'defaultnoncetrialsperbyte') - defaultpayloadlengthextrabytes = config.get('bitmessagesettings', 'defaultpayloadlengthextrabytes') - daemon = config.safeGetBoolean('bitmessagesettings', 'daemon') - - socksproxytype = config.get('bitmessagesettings', 'socksproxytype') - sockshostname = config.get('bitmessagesettings', 'sockshostname') - socksport = config.get('bitmessagesettings', 'socksport') - socksauthentication = config.safeGetBoolean('bitmessagesettings', 'socksauthentication') - socksusername = config.get('bitmessagesettings', 'socksusername') - sockspassword = config.get('bitmessagesettings', 'sockspassword') - - print('\n -----------------------------------') - print(' | Current Bitmessage Settings |') - print(' -----------------------------------') - print(' port = ' + port) - print(' startonlogon = ' + str(startonlogon)) - print(' minimizetotray = ' + str(minimizetotray)) - print(' showtraynotifications = ' + str(showtraynotifications)) - print(' startintray = ' + str(startintray)) - print(' defaultnoncetrialsperbyte = ' + defaultnoncetrialsperbyte) - print(' defaultpayloadlengthextrabytes = ' + defaultpayloadlengthextrabytes) - print(' daemon = ' + str(daemon)) - print('\n ------------------------------------') - print(' | Current Connection Settings |') - print(' -----------------------------------') - print(' socksproxytype = ' + socksproxytype) - print(' sockshostname = ' + sockshostname) - print(' socksport = ' + socksport) - print(' socksauthentication = ' + str(socksauthentication)) - print(' socksusername = ' + socksusername) - print(' sockspassword = ' + sockspassword) - print(' ') - - 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' - invalidInput = False - uInput = userInput("What setting would you like to modify?").lower() - print(' ') - - if uInput == "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)) - uInput = userInput("Enter the new status.") - config.set('bitmessagesettings', 'startonlogon', str(uInput)) - elif uInput == "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)) - uInput = userInput("Enter the new status.") - config.set('bitmessagesettings', 'showtraynotifications', str(uInput)) - elif uInput == "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) - uInput = userInput("Enter the new defaultnoncetrialsperbyte.") - config.set('bitmessagesettings', 'defaultnoncetrialsperbyte', str(uInput)) - elif uInput == "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)) - 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'.") - uInput = userInput("Enter the new socksproxytype.") - config.set('bitmessagesettings', 'socksproxytype', str(uInput)) - elif uInput == "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) - uInput = userInput("Enter the new socksport.") - config.set('bitmessagesettings', 'socksport', str(uInput)) - elif uInput == "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) - uInput = userInput("Enter the new socksusername.") - config.set('bitmessagesettings', 'socksusername', str(uInput)) - elif uInput == "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") - invalidInput = True - - if invalidInput is not 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') - with open(keysPath, 'wb') as configfile: - config.write(configfile) - restartBmNotify() - break - - elif uInput == "n": - usrPrompt = 1 - main() - else: - print("Invalid input.") - usrPrompt = 1 - main() - - -def validAddress(address): - """Predicate to test address validity""" - address_information = json.loads(api.decodeAddress(address)) - - 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 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') - 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') - - -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') - else: - break - - userInput("Are you sure, (Y)es or (N)o?").lower() # uInput = - - api.deleteSubscription(address) - print('\n You are now unsubscribed from: ' + address + '\n') - - -def listSubscriptions(): - """List subscriptions""" - - global usrPrompt - print('\nLabel, Address, Enabled\n') - try: - print(api.listSubscriptions()) - except: # noqa:E722 - print('\n Connection Error\n') - usrPrompt = 0 - main() - 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') - 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') - 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') - 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') - else: - break - - try: - print(api.leaveChan(address)) - except: # noqa:E722 - print('\n Connection Error\n') - usrPrompt = 0 - main() - - -def listAdd(): - """List 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') - 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 - address = str(jsonAddresses['addresses'][addNum]['address']) - stream = str(jsonAddresses['addresses'][addNum]['stream']) - enabled = str(jsonAddresses['addresses'][addNum]['enabled']) - - if len(label) > 19: - label = label[:16] + '...' - - 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""" - - global usrPrompt - - if deterministic is 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') - usrPrompt = 0 - main() - - return generatedAddress - - elif deterministic: # 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') - usrPrompt = 0 - main() - return generatedAddress - - return 'Entry Error' - - -def saveFile(fileName, fileData): - """Allows attachments and messages/broadcats to be saved""" - - # 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(":", "~") - fileName = fileName.replace("*", "~") - fileName = fileName.replace("?", "~") - fileName = fileName.replace('"', "~") - fileName = fileName.replace("<", "~") - fileName = fileName.replace(">", "~") - fileName = fileName.replace("|", "~") - - directory = os.path.abspath('attachments') - - if not os.path.exists(directory): - os.makedirs(directory) - - 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') - - -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.') - - try: - with open(filePath): - break - except IOError: - print('\n %s was not found on your filesystem or can not be opened.\n' % filePath) - - # 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 - - 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' - ])) - 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') - return '' - 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 - 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') - 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. - data = data.encode("base64") - - if isImage: # If it is an image, include image tags in the message - theAttachment = """ - - - -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. - theAttachment = """ - - - -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'): - 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. - """ - - global usrPrompt - if validAddress(toAddress) is False: - while True: - toAddress = userInput("What is the To Address?") - - if toAddress == "c": - usrPrompt = 1 - print(' ') - main() - elif validAddress(toAddress) is False: - print('\n Invalid Address. "c" to cancel. Please try again.\n') - else: - break - - if validAddress(fromAddress) is False: - try: - jsonAddresses = json.loads(api.listAddresses()) - numAddresses = len(jsonAddresses['addresses']) # Number of addresses - except: # noqa:E722 - print('\n Connection Error\n') - usrPrompt = 0 - main() - - if numAddresses > 1: # Ask what address to send from if multiple addresses - found = False - while True: - 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 - label = jsonAddresses['addresses'][addNum]['label'] - address = jsonAddresses['addresses'][addNum]['address'] - 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') - - else: - for addNum in range(0, numAddresses): # processes all of the addresses - address = jsonAddresses['addresses'][addNum]['address'] - 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') - 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.") - - 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') - usrPrompt = 0 - main() - - -def sendBrd(fromAddress, subject, message): - """Send a broadcast""" - - global usrPrompt - if not fromAddress: - - try: - jsonAddresses = json.loads(api.listAddresses()) - numAddresses = len(jsonAddresses['addresses']) # Number of addresses - except: # noqa:E722 - print('\n Connection Error\n') - usrPrompt = 0 - main() - - 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.") - - if fromAddress == "exit": - usrPrompt = 1 - main() - - 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 - fromAddress = address - found = True - break - - if found is False: - if validAddress(fromAddress) is False: - print('\n Invalid Address. Please try again.\n') - - else: - for addNum in range(0, numAddresses): # processes all of the addresses - address = jsonAddresses['addresses'][addNum]['address'] - 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') - 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.") - - 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') - usrPrompt = 0 - main() - - -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') - usrPrompt = 0 - main() - - messagesPrinted = 0 - messagesUnread = 0 - 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'), - ])) - 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') - - -def outbox(): - """TBC""" - - global usrPrompt - try: - outboxMessages = json.loads(api.getAllSentMessages()) - numMessages = len(outboxMessages['sentMessages']) - except: # noqa:E722 - 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) - - # print(''.join([ - # ' Last Action Time:', - # datetime.datetime.fromtimestamp( - # float(outboxMessages['sentMessages'][msgNum]['lastActionTime'])).strftime('%Y-%m-%d %H:%M:%S'), - # ])) - print(''.join([ - ' Last Action Time:', - datetime.datetime.fromtimestamp( - float(outboxMessages['sentMessages'][msgNum]['lastActionTime'])).strftime('%Y-%m-%d %H:%M:%S'), - ])) - - if msgNum % 20 == 0 and msgNum != 0: - userInput('(Press Enter to continue or type (Exit) to return to the main menu.)').lower() # uInput = - - 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') - usrPrompt = 0 - main() - - print(' ') - - if msgNum >= numMessages: - print('\n Invalid Message Number.\n') - main() - - # 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 - - 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'): - - this_attachment = message[attPos + 9:attEndPos] - saveFile(fileName, this_attachment) - - message = message[:fnPos] + '~~' + message[(attEndPos + 4):] - - else: - break - - # 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""" - global usrPrompt - try: - inboxMessages = json.loads(api.getAllInboxMessages()) - numMessages = len(inboxMessages['inboxMessages']) - except: # noqa:E722 - print('\n Connection Error\n') - usrPrompt = 0 - main() - - if msgNum >= numMessages: - print('\n Invalid Message Number.\n') - main() - - # 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 - - 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'): - - this_attachment = message[attPos + 9:attEndPos] - saveFile(fileName, this_attachment) - - 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(' ') - 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.""" - - global usrPrompt - forwardORreply = forwardORreply.lower() # makes it lowercase - try: - inboxMessages = json.loads(api.getAllInboxMessages()) - except: # noqa:E722 - 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. - - 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 - subject = "Re: " + subject - - elif forwardORreply == 'forward': - subject = "Fwd: " + subject - - while True: - toAdd = userInput("What is the To Address?") - - if toAdd == "c": - usrPrompt = 1 - print(' ') - main() - elif validAddress(toAdd) is False: - print('\n Invalid Address. "c" to cancel. Please try again.\n') - else: - break - else: - 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""" - - global usrPrompt - try: - inboxMessages = json.loads(api.getAllInboxMessages()) - # gets the message ID via the message index number - msgId = inboxMessages['inboxMessages'][int(msgNum)]['msgid'] - - msgAck = api.trashMessage(msgId) - except: # noqa:E722 - print('\n Connection Error\n') - usrPrompt = 0 - main() - - return msgAck - - -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'] - msgAck = api.trashSentMessage(msgId) - except: # noqa:E722 - print('\n Connection Error\n') - usrPrompt = 0 - main() - - return msgAck - - -def getLabelForAddress(address): - """Get label for an address""" - - if address in knownAddresses: - return knownAddresses[address] - else: - buildKnownAddresses() - if address in knownAddresses: - return knownAddresses[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 - 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') - usrPrompt = 0 - main() - - # add from my addresses - 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['address'] not in knownAddresses: - knownAddresses[entry['address']] = "%s (%s)" % (entry['label'].decode('base64'), entry['address']) - except: # noqa:E722 - 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(' |--------------------|---------------------------------------|') - 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') - 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') - 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') - 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') - 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') - 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') - 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') - 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') - 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") - - -def shutdown(): - """Shutdown the API""" - - try: - api.shutdown() - except socket.error: - pass - print("\nShutdown command relayed\n") - - -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(' ') - main() - - elif usrInput == "apitest": # tests the API Connection. - if apiTest(): - print('\n API connection test has: PASSED\n') - else: - 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)) - - 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'])) - else: - print(' Invalid Address !') - - print('------------------------------\n') - main() - - elif usrInput == "bmsettings": # tests the API Connection. - bmSettings() - 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 - listAdd() - main() - - 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 - deterministic = True - - lbl = '' - passphrase = userInput('Enter the Passphrase.') # .encode('base64') - numOfAdd = int(userInput('How many addresses would you like to generate?')) - 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)) - main() - elif isRipe == "n": - ripe = False - print(genAdd(lbl, deterministic, passphrase, numOfAdd, addVNum, streamNum, ripe)) - main() - elif isRipe == "exit": - usrPrompt = 1 - main() - else: - print('\n Invalid input\n') - main() - - 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)) - main() - - else: - print('\n Invalid input\n') - main() - - 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') - usrPrompt = 1 - main() - - elif usrInput == "subscribe": # Subsribe to an address - subscribe() - usrPrompt = 1 - main() - - elif usrInput == "unsubscribe": # Unsubscribe from an address - unsubscribe() - usrPrompt = 1 - main() - - elif usrInput == "listsubscriptions": # Unsubscribe from an address - listSubscriptions() - usrPrompt = 1 - main() - - elif usrInput == "create": - createChan() - usrPrompt = 1 - main() - - elif usrInput == "join": - joinChan() - usrPrompt = 1 - main() - - elif usrInput == "leave": - leaveChan() - usrPrompt = 1 - main() - - elif usrInput == "inbox": - print('\n Loading...\n') - inbox() - main() - - elif usrInput == "unread": - print('\n Loading...\n') - inbox(True) - main() - - elif usrInput == "outbox": - print('\n Loading...\n') - outbox() - main() - - 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'): - null = '' - sendMsg(null, null, null, null) - main() - elif uInput in ('b', 'broadcast'): - null = '' - sendBrd(null, null, null) - main() - - 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') - 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') - 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'): - 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') - 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 - - if uInput == "y": - delMsg(msgNum) - print('\n Message Deleted.\n') - usrPrompt = 1 - else: - usrPrompt = 1 - else: - print('\n Invalid entry\n') - usrPrompt = 1 - - elif uInput in ('o', '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() - - if uInput in ("d", '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') - usrPrompt = 1 - else: - usrPrompt = 1 - else: - 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') - usrPrompt = 1 - main() - - if uInput in ('i', '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') - 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': - 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') - 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) - - usrPrompt = 1 - main() - - 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'): - 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() - - if msgNum == 'a' or msgNum == 'all': - break - 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 - - 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) - delMsg(0) - - print('\n Inbox is empty.') - usrPrompt = 1 - else: - delMsg(int(msgNum)) - - print('\n Notice: Message numbers may have changed.\n') - main() - else: - usrPrompt = 1 - - elif uInput in ('o', '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() - - if msgNum in ('a', 'all'): - break - 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 - - 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) - delSentMsg(0) - - print('\n Outbox is empty.') - usrPrompt = 1 - else: - delSentMsg(int(msgNum)) - print('\n Notice: Message numbers may have changed.\n') - main() - else: - usrPrompt = 1 - else: - print('\n Invalid Entry.\n') - usrPrompt = 1 - main() - - elif usrInput == "exit": - print('\n You are already at the main menu. Use "quit" to quit.\n') - usrPrompt = 1 - main() - - elif usrInput == "listaddressbookentries": - res = listAddressBookEntries() - if res == 20: - print('\n Error: API function not supported.\n') - usrPrompt = 1 - main() - - elif usrInput == "addaddressbookentry": - 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') - usrPrompt = 1 - main() - - elif usrInput == "deleteaddressbookentry": - address = userInput('Enter address') - res = deleteAddressFromAddressBook(address) - if res == 20: - print('\n Error: API function not supported.\n') - usrPrompt = 1 - main() - - elif usrInput == "markallmessagesread": - markAllMessagesRead() - usrPrompt = 1 - main() - - elif usrInput == "markallmessagesunread": - markAllMessagesUnread() - usrPrompt = 1 - main() - - elif usrInput == "status": - clientStatus() - usrPrompt = 1 - main() - - elif usrInput == "shutdown": - shutdown() - usrPrompt = 1 - main() - - else: - print('\n "', usrInput, '" is not a command.\n') - usrPrompt = 1 - main() - - -def main(): - """Entrypoint for the CLI app""" - - global api - global usrPrompt - - if usrPrompt == 0: - print('\n ------------------------------') - print(' | Bitmessage Daemon by .dok |') - print(' | Version 0.3.1 for BM 0.6.2 |') - print(' ------------------------------') - api = xmlrpclib.ServerProxy(apiData()) # Connect to BitMessage using these api credentials - - if 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) - usrPrompt = 2 - - elif usrPrompt == 1: - print('\nType (H)elp for a list of commands.') # Startup message) - usrPrompt = 2 - - try: - UI((raw_input('>').lower()).replace(" ", "")) - except EOFError: - UI("quit") - - -if __name__ == "__main__": - main() diff --git a/src/bitmessagecurses/__init__.py b/src/bitmessagecurses/__init__.py deleted file mode 100644 index 542b8e53..00000000 --- a/src/bitmessagecurses/__init__.py +++ /dev/null @@ -1,1238 +0,0 @@ -""" -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 time -from textwrap import fill -from threading import Timer - -from dialog import Dialog -import helper_sent -import l10n -import network.stats -import queues -import shared -import shutdown -import state - -from addresses import addBMIfNotPresent, decodeAddress -from bmconfigparser import config -from helper_sql import sqlExecute, sqlQuery -from inventory import Inventory - -# pylint: disable=global-statement - - -quit_ = False -menutab = 1 -menu = ["Inbox", "Send", "Sent", "Your Identities", "Subscriptions", "Address Book", "Blacklist", "Network Status"] -naptime = 100 -log = "" -logpad = None -inventorydata = 0 -startuptime = time.time() - -inbox = [] -inboxcur = 0 -sentbox = [] -sentcur = 0 -addresses = [] -addrcur = 0 -addrcopy = 0 -subscriptions = [] -subcur = 0 -addrbook = [] -abookcur = 0 -blacklist = [] -blackcur = 0 -bwtype = "black" - -BROADCAST_STR = "[Broadcast subscribers]" - - -class printLog(object): - """Printing logs""" - # pylint: disable=no-self-use - - 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 - - def write(self, output): - """Write error logs""" - global log - 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): - 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: - menustr = menustr[:-1] - menustr += "[" - menustr += str(i + 1) + menu[i] - if menutab == i + 1: - menustr += "] " - 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 - 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") - - -def resetlookups(): - """Reset the Inventory Lookups""" - global inventorydata - inventorydata = Inventory().numberOfInventoryLookupsPerformed - Inventory().numberOfInventoryLookupsPerformed = 0 - Timer(1, resetlookups, ()).start() - - -def drawtab(stdscr): - """Method for drawing different tabs""" - # pylint: disable=too-many-branches, too-many-statements - if menutab in range(1, len(menu) + 1): - if menutab == 1: # Inbox - 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: - a = 0 - 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 - 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(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: - a = 0 - 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(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: - a = 0 - 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 - 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(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: - a = 0 - 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 - 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(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: - a = 0 - 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(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: - a = 0 - 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 - 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 - # Connection data - connected_hosts = network.stats.connectedHostsList() - stdscr.addstr( - 4, 5, "Total Connections: " + - str(len(connected_hosts)).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: - if stream >= len(streamcount): - streamcount.append(1) - else: - streamcount[stream] += 1 - for i, item in enumerate(streamcount): - if i < 4: - if i == 0: - stdscr.addstr(8 + i, 6, "?") - else: - 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.") - - # Inventory data - 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') - if n > 512: - del lg[:(n - 256)] - logpad.erase() - n = len(lg) - for i, item in enumerate(lg): - a = 0 - if item 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) - 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 chr(c) in '12345678': - global menutab - menutab = int(chr(c)) - elif chr(c) == 'q': - 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"), - ("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 - ret = sqlQuery("SELECT message FROM inbox WHERE msgid=?", inbox[inboxcur][0]) - 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" - 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 - sqlExecute("UPDATE inbox SET read=0 WHERE msgid=?", inbox[inboxcur][0]) - inbox[inboxcur][7] = 0 - elif t == "3": # Reply - curses.curs_set(1) - m = inbox[inboxcur] - fromaddr = m[4] - ischan = False - for i, item in enumerate(addresses): - 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.")) - return - toaddr = m[2] - if ischan: - toaddr = fromaddr - - subject = m[5] - if not m[5][:4] == "Re: ": - 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 - 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 r == d.DIALOG_OK: - label = t - sqlExecute("INSERT INTO addressbook VALUES (?,?)", label, addr) - # Prepend entry - addrbook.reverse() - addrbook.append([label, addr]) - 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") - 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.write(msg) - fh.close() - else: - scrollbox(d, unicode("Could not fetch message.")) - 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.")) - elif menutab == 2: - a = "" - 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"), - ("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] + - "\"") - data = "" - 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" - 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]) - 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.")) - 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")]) - else: - 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"), - ("5", "Disable"), - ("6", "Delete"), - ("7", "Special address behavior")]) - if r == d.DIALOG_OK: - 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"), - ("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: - 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)")]) - if r == d.DIALOG_OK: - if t == "1": - stream = 1 - elif t == "2": - addrs = [] - for i, item in enumerate(addresses): - addrs.append([str(i), item[2]]) - r, t = d.menu("Choose an existing address's stream", choices=addrs) - 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)]) - if r == d.DIALOG_OK and "1" in t: - shorten = True - queues.addressGeneratorQueue.put(( - "createRandomAddress", - 4, - stream, - label, - 1, - "", - shorten)) - elif t == "2": - set_background_title(d, "Make deterministic addresses") - r, t = d.passwordform( - "Enter passphrase", - [ - ("Passphrase", 1, 1, "", 2, 1, 64, 128), - ("Confirm passphrase", 3, 1, "", 4, 1, 64, 128)], - 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) - 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)]) - 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)) - else: - scrollbox(d, unicode("Passphrases do not match")) - elif t == "2": # Send a message - a = "" - 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 - 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) - # Write config - config.save() - addresses[addrcur][0] = label - elif t == "4": # Enable address - a = addresses[addrcur][2] - config.set(a, "enabled", "true") # Set config - # Write config - config.save() - # Change color - if config.safeGetBoolean(a, 'chan'): - addresses[addrcur][3] = 9 # orange - elif config.safeGetBoolean(a, 'mailinglist'): - addresses[addrcur][3] = 5 # magenta - else: - addresses[addrcur][3] = 0 # black - addresses[addrcur][1] = True - 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 - # Write config - config.save() - addresses[addrcur][1] = False - 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 - 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.")) - else: - m = config.safeGetBoolean(a, "mailinglist") - r, t = d.radiolist( - "Select address behavior", - choices=[ - ("1", "Behave as a normal address", not m), - ("2", "Behave as a pseudo-mailing-list address", m)]) - if r == d.DIALOG_OK: - if t == "1" and m: - config.set(a, "mailinglist", "false") - if addresses[addrcur][1]: - addresses[addrcur][3] = 0 # Set color to black - else: - addresses[addrcur][3] = 8 # Set color to gray - elif t == "2" and m is False: - try: - mn = config.get(a, "mailinglistname") - except ConfigParser.NoOptionError: - 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 - # Write config - config.save() - elif menutab == 5: - set_background_title(d, "Subscriptions Dialog Box") - if len(subscriptions) <= subcur: - r, t = d.menu( - "Do what with subscription to \"" + subscriptions[subcur][0] + "\"?", - choices=[ - ("1", "Add new subscription")]) - else: - 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")]) - if r == d.DIALOG_OK: - if t == "1": - r, t = d.inputbox("New subscription address") - if r == d.DIALOG_OK: - addr = addBMIfNotPresent(t) - if not shared.isAddressInMySubscriptionsList(addr): - r, t = d.inputbox("New subscription label") - if r == d.DIALOG_OK: - label = t - # Prepend entry - subscriptions.reverse() - subscriptions.append([label, addr, True]) - subscriptions.reverse() - - sqlExecute("INSERT INTO subscriptions VALUES (?,?,?)", label, addr, True) - shared.reloadBroadcastSendersForWhichImWatching() - elif t == "2": - r, t = d.inputbox("Type in \"I want to delete this subscription\"") - 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] - elif t == "3": - 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]) - 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?", - 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"), - ("2", "Subscribe to this address"), - ("3", "Add new address to Address Book"), - ("4", "Delete this address")]) - if r == d.DIALOG_OK: - if t == "1": - sendMessage(recv=addrbook[abookcur][1]) - elif t == "2": - r, t = d.inputbox("New subscription label") - if r == d.DIALOG_OK: - label = t - # Prepend entry - subscriptions.reverse() - subscriptions.append([label, addr, True]) - subscriptions.reverse() - - sqlExecute("INSERT INTO subscriptions VALUES (?,?,?)", label, addr, 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 r == d.DIALOG_OK: - sqlExecute("INSERT INTO addressbook VALUES (?,?)", t, addr) - # Prepend entry - addrbook.reverse() - addrbook.append([t, addr]) - addrbook.reverse() - else: - scrollbox(d, unicode("The selected address is already in the Address Book.")) - 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]) - 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"), - ("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]) - del blacklist[blackcur] - elif t == "2": - 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]) - blacklist[blackcur][2] = False - dialogreset(stdscr) - else: - if c == curses.KEY_UP: - if menutab == 1 and inboxcur > 0: - inboxcur -= 1 - if (menutab == 2 or menutab == 4) and addrcur > 0: - addrcur -= 1 - if menutab == 3 and sentcur > 0: - sentcur -= 1 - if menutab == 5 and subcur > 0: - subcur -= 1 - if menutab == 6 and abookcur > 0: - abookcur -= 1 - if menutab == 7 and blackcur > 0: - blackcur -= 1 - elif c == curses.KEY_DOWN: - if menutab == 1 and inboxcur < len(inbox) - 1: - inboxcur += 1 - if (menutab == 2 or menutab == 4) and addrcur < len(addresses) - 1: - addrcur += 1 - if menutab == 3 and sentcur < len(sentbox) - 1: - sentcur += 1 - if menutab == 5 and subcur < len(subscriptions) - 1: - subcur += 1 - if menutab == 6 and abookcur < len(addrbook) - 1: - abookcur += 1 - if menutab == 7 and blackcur < len(blacklist) - 1: - blackcur += 1 - elif c == curses.KEY_HOME: - if menutab == 1: - inboxcur = 0 - if menutab == 2 or menutab == 4: - addrcur = 0 - if menutab == 3: - sentcur = 0 - if menutab == 5: - subcur = 0 - if menutab == 6: - abookcur = 0 - if menutab == 7: - blackcur = 0 - elif c == curses.KEY_END: - if menutab == 1: - inboxcur = len(inbox) - 1 - if menutab == 2 or menutab == 4: - addrcur = len(addresses) - 1 - if menutab == 3: - sentcur = len(sentbox) - 1 - if menutab == 5: - subcur = len(subscriptions) - 1 - if menutab == 6: - abookcur = len(addrbook) - 1 - if menutab == 7: - 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) - 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), - ("2", "Broadcast to everyone who is subscribed to your address", 0)]) - if r != d.DIALOG_OK: - return - broadcast = False - if t == "2": # Broadcast - broadcast = True - if subject == "" or reply: - r, t = d.inputbox("Message subject", width=60, init=subject) - if r != d.DIALOG_OK: - return - subject = t - if body == "" or reply: - r, t = d.inputbox("Message body", 10, 80, init=body) - if r != d.DIALOG_OK: - return - body = t - body = body.replace("\\n", "\n").replace("\\t", "\t") - - if not broadcast: - recvlist = [] - for _, item in enumerate(recv.replace(",", ";").split(";")): - recvlist.append(item.strip()) - list(set(recvlist)) # Remove exact duplicates - for addr in recvlist: - if addr != "": - status, version, stream = decodeAddress(addr)[:3] - if status != "success": - set_background_title(d, "Recipient address error") - err = "Could not decode" + addr + " : " + status + "\n\n" - if status == "missingbm": - err += "Bitmessage addresses should start with \"BM-\"." - elif status == "checksumfailed": - err += "The address was not typed or copied correctly." - 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.") - elif status == "ripetooshort": - 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.") - elif status == "varintmalformed": - 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)) - else: - 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 + - ".")) - 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 + ".")) - continue - if not network.stats.connectedHostsList(): - 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 - 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', '')) - - -# 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) - 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 = "" - 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 - qr = sqlQuery("SELECT label FROM addressbook WHERE address=?", fromaddr) - if qr != []: - for r in qr: - fromlabel, = r - if fromlabel == "": # Check Subscriptions - qr = sqlQuery("SELECT label FROM subscriptions WHERE address=?", fromaddr) - if qr != []: - for r in qr: - fromlabel, = r - if fromlabel == "": - fromlabel = fromaddr - fromlabel = shared.fixPotentiallyInvalidUTF8Data(fromlabel) - - # Load into array - inbox.append([ - msgid, tolabel, toaddr, fromlabel, fromaddr, subject, - l10n.formatTimestamp(received), 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) - 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) - if qr != []: - for r in qr: - tolabel, = r - if tolabel == "": - qr = sqlQuery("SELECT label FROM subscriptions WHERE address=?", toaddr) - if qr != []: - for r in qr: - tolabel, = r - if tolabel == "": - if config.has_section(toaddr): - tolabel = 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 fromlabel == "": - fromlabel = fromaddr - - # Set status string - if status == "awaitingpubkey": - statstr = "Waiting for their public key. Will request it again soon" - elif status == "doingpowforpubkey": - statstr = "Encryption key request queued" - elif status == "msgqueued": - statstr = "Message queued" - elif status == "msgsent": - t = l10n.formatTimestamp(lastactiontime) - statstr = "Message sent at " + t + ".Waiting for acknowledgement." - elif status == "msgsentnoackexpected": - t = l10n.formatTimestamp(lastactiontime) - 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 == "broadcastqueued": - statstr = "Broadcast queued." - elif status == "broadcastsent": - t = l10n.formatTimestamp(lastactiontime) - statstr = "Broadcast sent at " + t + "." - elif status == "forcepow": - statstr = "Forced difficulty override. Message will start sending soon." - elif status == "badkey": - statstr = "Warning: Could not encrypt message because the recipient's encryption key is no good." - 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 + "." - - # Load into array - sentbox.append([ - tolabel, - toaddr, - fromlabel, - fromaddr, - subject, - statstr, - ackdata, - l10n.formatTimestamp(lastactiontime)]) - sentbox.reverse() - - -def loadAddrBook(): - """Load address book""" - sys.stdout = sys.__stdout__ - print("Loading address book...") - sys.stdout = printlog - - ret = sqlQuery("SELECT label, address FROM addressbook") - for row in ret: - label, addr = row - label = shared.fixPotentiallyInvalidUTF8Data(label) - addrbook.append([label, addr]) - addrbook.reverse() - - -def loadSubscriptions(): - """Load subscription functionality""" - ret = sqlQuery("SELECT label, address, enabled FROM subscriptions") - for row in ret: - label, address, enabled = row - subscriptions.append([label, address, enabled]) - subscriptions.reverse() - - -def loadBlackWhiteList(): - """load black/white list""" - global bwtype - bwtype = config.get("bitmessagesettings", "blackwhitelist") - if bwtype == "black": - ret = sqlQuery("SELECT label, address, enabled FROM blacklist") - else: - ret = sqlQuery("SELECT label, address, enabled FROM whitelist") - for row in ret: - label, address, enabled = row - blacklist.append([label, address, enabled]) - blacklist.reverse() - - -def runwrapper(): - """Main method""" - sys.stdout = printlog - # sys.stderr = errlog - - loadInbox() - loadSent() - loadAddrBook() - loadSubscriptions() - loadBlackWhiteList() - - stdscr = curses.initscr() - - global logpad - logpad = curses.newpad(1024, curses.COLS) - - stdscr.nodelay(0) - curses.curs_set(0) - stdscr.timeout(1000) - - curses.wrapper(run) - doShutdown() - - -def run(stdscr): - """Main loop""" - # Schedule inventory lookup data - resetlookups() - - # Init color pairs - if curses.has_colors(): - curses.init_pair(1, curses.COLOR_RED, curses.COLOR_BLACK) # red - curses.init_pair(2, curses.COLOR_GREEN, curses.COLOR_BLACK) # green - curses.init_pair(3, curses.COLOR_YELLOW, curses.COLOR_BLACK) # yellow - curses.init_pair(4, curses.COLOR_BLUE, curses.COLOR_BLACK) # blue - curses.init_pair(5, curses.COLOR_MAGENTA, curses.COLOR_BLACK) # magenta - curses.init_pair(6, curses.COLOR_CYAN, curses.COLOR_BLACK) # cyan - curses.init_pair(7, curses.COLOR_WHITE, curses.COLOR_BLACK) # white - if curses.can_change_color(): - curses.init_color(8, 500, 500, 500) # gray - curses.init_pair(8, 8, 0) - 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 - - # Init list of address in 'Your Identities' tab - configSections = config.addresses() - 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 - addresses.reverse() - - stdscr.clear() - redraw(stdscr) - while quit_ is False: - drawtab(stdscr) - handlech(stdscr.getch(), stdscr) - - -def doShutdown(): - """Shutting the app down""" - sys.stdout = sys.__stdout__ - print("Shutting down...") - sys.stdout = printlog - shutdown.doCleanShutdown() - sys.stdout = sys.__stdout__ - sys.stderr = sys.__stderr__ - os._exit(0) # pylint: disable=protected-access diff --git a/src/bitmessagekivy/__init__.py b/src/bitmessagekivy/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/bitmessagekivy/base_navigation.py b/src/bitmessagekivy/base_navigation.py deleted file mode 100644 index 5f6b1aa5..00000000 --- a/src/bitmessagekivy/base_navigation.py +++ /dev/null @@ -1,110 +0,0 @@ -# pylint: disable=unused-argument, no-name-in-module, too-few-public-methods -""" - Base class for Navigation Drawer -""" - -from kivy.lang import Observable - -from kivy.properties import ( - BooleanProperty, - NumericProperty, - StringProperty -) -from kivy.metrics import dp -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.spinner import Spinner - -from kivy.clock import Clock -from kivy.core.window import Window - -from kivymd.uix.list import ( - OneLineAvatarIconListItem, - OneLineListItem -) - -from pybitmessage.bmconfigparser import config - - -class BaseLanguage(Observable): - """UI Language""" - observers = [] - lang = None - - def __init__(self, defaultlang): - super(BaseLanguage, self).__init__() - self.ugettext = None - self.lang = defaultlang - - @staticmethod - def _(text): - return text - - -class BaseNavigationItem(OneLineAvatarIconListItem): - """NavigationItem class for kivy Ui""" - badge_text = StringProperty() - icon = StringProperty() - active = BooleanProperty(False) - - def currentlyActive(self): - """Currenly active""" - for nav_obj in self.parent.children: - nav_obj.active = False - self.active = True - - -class BaseNavigationDrawerDivider(OneLineListItem): - """ - A small full-width divider that can be placed - in the :class:`MDNavigationDrawer` - """ - - disabled = True - divider = None - _txt_top_pad = NumericProperty(dp(8)) - _txt_bot_pad = NumericProperty(dp(8)) - - def __init__(self, **kwargs): - super(BaseNavigationDrawerDivider, self).__init__(**kwargs) - self.height = dp(16) - - -class BaseNavigationDrawerSubheader(OneLineListItem): - """ - A subheader for separating content in :class:`MDNavigationDrawer` - - Works well alongside :class:`NavigationDrawerDivider` - """ - - disabled = True - divider = None - theme_text_color = 'Secondary' - - -class BaseContentNavigationDrawer(BoxLayout): - """ContentNavigationDrawer class for kivy Uir""" - - def __init__(self, *args, **kwargs): - """Method used for contentNavigationDrawer""" - super(BaseContentNavigationDrawer, self).__init__(*args, **kwargs) - Clock.schedule_once(self.init_ui, 0) - - def init_ui(self, dt=0): - """Clock Schdule for class contentNavigationDrawer""" - self.ids.scroll_y.bind(scroll_y=self.check_scroll_y) - - def check_scroll_y(self, instance, somethingelse): - """show data on scroll down""" - if self.ids.identity_dropdown.is_open: - self.ids.identity_dropdown.is_open = False - - -class BaseIdentitySpinner(Spinner): - """Base Class for Identity Spinner(Dropdown)""" - - def __init__(self, *args, **kwargs): - """Method used for setting size of spinner""" - super(BaseIdentitySpinner, self).__init__(*args, **kwargs) - self.dropdown_cls.max_height = Window.size[1] / 3 - self.values = list(addr for addr in config.addresses() - if config.getboolean(str(addr), 'enabled')) diff --git a/src/bitmessagekivy/baseclass/__init__.py b/src/bitmessagekivy/baseclass/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/bitmessagekivy/baseclass/addressbook.py b/src/bitmessagekivy/baseclass/addressbook.py deleted file mode 100644 index f18a0142..00000000 --- a/src/bitmessagekivy/baseclass/addressbook.py +++ /dev/null @@ -1,164 +0,0 @@ -# pylint: disable=unused-argument, consider-using-f-string, import-error -# pylint: disable=unnecessary-comprehension, no-member, no-name-in-module - -""" -addressbook.py -============== - -All saved addresses are managed in Addressbook - -""" - -import os -import logging -from functools import partial - -from kivy.properties import ( - ListProperty, - StringProperty -) -from kivy.uix.screenmanager import Screen -from kivy.app import App - -from pybitmessage.bitmessagekivy.get_platform import platform -from pybitmessage.bitmessagekivy import kivy_helper_search -from pybitmessage.bitmessagekivy.baseclass.common import ( - avatar_image_first_letter, toast, empty_screen_label, - ThemeClsColor, SwipeToDeleteItem, kivy_state_variables -) -from pybitmessage.bitmessagekivy.baseclass.popup import SavedAddressDetailPopup -from pybitmessage.bitmessagekivy.baseclass.addressbook_widgets import HelperAddressBook -from pybitmessage.helper_sql import sqlExecute - -logger = logging.getLogger('default') - - -class AddressBook(Screen, HelperAddressBook): - """AddressBook Screen class for kivy Ui""" - - queryreturn = ListProperty() - has_refreshed = True - address_label = StringProperty() - address = StringProperty() - label_str = "No contact Address found yet......" - no_search_res_found = "No search result found" - - def __init__(self, *args, **kwargs): - """Getting AddressBook Details""" - super(AddressBook, self).__init__(*args, **kwargs) - self.addbook_popup = None - self.kivy_state = kivy_state_variables() - - def loadAddresslist(self, account, where="", what=""): - """Clock Schdule for method AddressBook""" - if self.kivy_state.searching_text: - self.ids.scroll_y.scroll_y = 1.0 - where = ['label', 'address'] - what = self.kivy_state.searching_text - xAddress = '' - self.ids.tag_label.text = '' - self.queryreturn = kivy_helper_search.search_sql( - xAddress, account, "addressbook", where, what, False) - self.queryreturn = [obj for obj in reversed(self.queryreturn)] - if self.queryreturn: - self.ids.tag_label.text = 'Address Book' - self.has_refreshed = True - self.set_mdList(0, 20) - self.ids.scroll_y.bind(scroll_y=self.check_scroll_y) - else: - self.ids.ml.add_widget(empty_screen_label(self.label_str, self.no_search_res_found)) - - def set_mdList(self, start_index, end_index): - """Creating the mdList""" - for item in self.queryreturn[start_index:end_index]: - message_row = SwipeToDeleteItem( - text=item[0], - ) - listItem = message_row.ids.content - listItem.secondary_text = item[1] - listItem.theme_text_color = "Custom" - listItem.text_color = ThemeClsColor - image = os.path.join( - self.kivy_state.imageDir, "text_images", "{}.png".format(avatar_image_first_letter(item[0].strip())) - ) - message_row.ids.avater_img.source = image - listItem.bind(on_release=partial( - self.addBook_detail, item[1], item[0], message_row)) - message_row.ids.delete_msg.bind(on_press=partial(self.delete_address, item[1])) - self.ids.ml.add_widget(message_row) - - def check_scroll_y(self, instance, somethingelse): - """Load data on scroll""" - if self.ids.scroll_y.scroll_y <= -0.0 and self.has_refreshed: - self.ids.scroll_y.scroll_y = 0.06 - exist_addresses = len(self.ids.ml.children) - if exist_addresses != len(self.queryreturn): - self.update_addressBook_on_scroll(exist_addresses) - self.has_refreshed = ( - True if exist_addresses != len(self.queryreturn) else False - ) - - def update_addressBook_on_scroll(self, exist_addresses): - """Load more data on scroll down""" - self.set_mdList(exist_addresses, exist_addresses + 5) - - @staticmethod - def refreshs(*args): - """Refresh the Widget""" - - # @staticmethod - def addBook_detail(self, address, label, instance, *args): - """Addressbook details""" - if instance.state == 'closed': - instance.ids.delete_msg.disabled = True - if instance.open_progress == 0.0: - obj = SavedAddressDetailPopup() - self.address_label = obj.address_label = label - self.address = obj.address = address - width = .9 if platform == 'android' else .8 - self.addbook_popup = self.address_detail_popup( - obj, self.send_message_to, self.update_addbook_label, - self.close_pop, width) - self.addbook_popup.auto_dismiss = False - self.addbook_popup.open() - else: - instance.ids.delete_msg.disabled = False - - def delete_address(self, address, instance, *args): - """Delete inbox mail from inbox listing""" - self.ids.ml.remove_widget(instance.parent.parent) - # if len(self.ids.ml.children) == 0: - if self.ids.ml.children is not None: - self.ids.tag_label.text = '' - sqlExecute( - "DELETE FROM addressbook WHERE address = ?", address) - toast('Address Deleted') - - def close_pop(self, instance): - """Pop is Canceled""" - self.addbook_popup.dismiss() - toast('Canceled') - - def update_addbook_label(self, instance): - """Updating the label of address book address""" - address_list = kivy_helper_search.search_sql(folder="addressbook") - stored_labels = [labels[0] for labels in address_list] - add_dict = dict(address_list) - label = str(self.addbook_popup.content_cls.ids.add_label.text) - if label in stored_labels and self.address == add_dict[label]: - stored_labels.remove(label) - if label and label not in stored_labels: - sqlExecute(""" - UPDATE addressbook - SET label = ? - WHERE address = ?""", label, self.addbook_popup.content_cls.address) - App.get_running_app().root.ids.id_addressbook.ids.ml.clear_widgets() - App.get_running_app().root.ids.id_addressbook.loadAddresslist(None, 'All', '') - self.addbook_popup.dismiss() - toast('Saved') - - def send_message_to(self, instance): - """Method used to fill to_address of composer autofield""" - App.get_running_app().set_navbar_for_composer() - self.compose_message(None, self.address) - self.addbook_popup.dismiss() diff --git a/src/bitmessagekivy/baseclass/addressbook_widgets.py b/src/bitmessagekivy/baseclass/addressbook_widgets.py deleted file mode 100644 index 3654dfa3..00000000 --- a/src/bitmessagekivy/baseclass/addressbook_widgets.py +++ /dev/null @@ -1,50 +0,0 @@ -# pylint: disable=no-member, too-many-arguments, too-few-public-methods -""" -Addressbook widgets are here. -""" - -from kivy.app import App -from kivymd.uix.button import MDRaisedButton -from kivymd.uix.dialog import MDDialog - - -class HelperAddressBook(object): - """Widget used in Addressbook are here""" - - @staticmethod - def address_detail_popup(obj, send_message, update_address, close_popup, width): - """This function shows the address's details and opens the popup.""" - show_dialogue = MDDialog( - type="custom", - size_hint=(width, .25), - content_cls=obj, - buttons=[ - MDRaisedButton( - text="Send message to", - on_release=send_message, - ), - MDRaisedButton( - text="Save", - on_release=update_address, - ), - MDRaisedButton( - text="Cancel", - on_release=close_popup, - ), - ], - ) - return show_dialogue - - @staticmethod - def compose_message(from_addr=None, to_addr=None): - """This UI independent method for message sending to reciever""" - window_obj = App.get_runnint_app().root.ids - if to_addr: - window_obj.id_create.children[1].ids.txt_input.text = to_addr - if from_addr: - window_obj.id_create.children[1].ids.txt_input.text = from_addr - window_obj.id_create.children[1].ids.ti.text = '' - window_obj.id_create.children[1].ids.composer_dropdown.text = 'Select' - window_obj.id_create.children[1].ids.subject.text = '' - window_obj.id_create.children[1].ids.body.text = '' - window_obj.scr_mngr.current = 'create' diff --git a/src/bitmessagekivy/baseclass/allmail.py b/src/bitmessagekivy/baseclass/allmail.py deleted file mode 100644 index d30310d8..00000000 --- a/src/bitmessagekivy/baseclass/allmail.py +++ /dev/null @@ -1,67 +0,0 @@ -# pylint: disable=import-error, no-name-in-module -# pylint: disable=unused-argument, no-member, attribute-defined-outside-init - -""" -allmail.py -============== - -All mails are managed in allmail screen - -""" - -from kivy.clock import Clock -from kivy.properties import ( - ListProperty, - StringProperty -) -from kivy.uix.screenmanager import Screen -from kivy.app import App - -from pybitmessage.bitmessagekivy.baseclass.common import ( - show_limited_cnt, empty_screen_label, kivy_state_variables, -) - -import logging -logger = logging.getLogger('default') - - -class Allmails(Screen): - """Allmails Screen for kivy Ui""" - data = ListProperty() - has_refreshed = True - all_mails = ListProperty() - account = StringProperty() - label_str = 'yet no message for this account!!!!!!!!!!!!!' - - def __init__(self, *args, **kwargs): - """Method Parsing the address""" - super(Allmails, self).__init__(*args, **kwargs) - self.kivy_state = kivy_state_variables() - if self.kivy_state.selected_address == '': - if App.get_running_app().identity_list: - self.kivy_state.selected_address = App.get_running_app().identity_list[0] - Clock.schedule_once(self.init_ui, 0) - - def init_ui(self, dt=0): - """Clock Schdule for method all mails""" - self.loadMessagelist() - logger.debug(dt) - - def loadMessagelist(self): - """Load Inbox, Sent anf Draft list of messages""" - self.account = self.kivy_state.selected_address - self.ids.tag_label.text = '' - if self.all_mails: - self.ids.tag_label.text = 'All Mails' - self.kivy_state.all_count = str( - int(self.kivy_state.sent_count) + int(self.kivy_state.inbox_count)) - self.set_AllmailCnt(self.kivy_state.all_count) - else: - self.set_AllmailCnt('0') - self.ids.ml.add_widget(empty_screen_label(self.label_str)) - - @staticmethod - def set_AllmailCnt(Count): - """This method is used to set allmails message count""" - allmailCnt_obj = App.get_running_app().root.ids.content_drawer.ids.allmail_cnt - allmailCnt_obj.ids.badge_txt.text = show_limited_cnt(int(Count)) diff --git a/src/bitmessagekivy/baseclass/chat.py b/src/bitmessagekivy/baseclass/chat.py deleted file mode 100644 index c5f94b8a..00000000 --- a/src/bitmessagekivy/baseclass/chat.py +++ /dev/null @@ -1,11 +0,0 @@ -# pylint: disable=import-error, no-name-in-module, too-few-public-methods, too-many-ancestors - -''' - Chats are managed in this screen -''' - -from kivy.uix.screenmanager import Screen - - -class Chat(Screen): - """Chat Screen class for kivy Ui""" diff --git a/src/bitmessagekivy/baseclass/common.py b/src/bitmessagekivy/baseclass/common.py deleted file mode 100644 index 2cf4d76b..00000000 --- a/src/bitmessagekivy/baseclass/common.py +++ /dev/null @@ -1,236 +0,0 @@ -# pylint: disable=no-name-in-module, attribute-defined-outside-init, import-error, unused-argument -""" - All Common widgets of kivy are managed here. -""" - -import os -from datetime import datetime - -from kivy.core.window import Window -from kivy.metrics import dp -from kivy.uix.image import Image -from kivy.properties import ( - NumericProperty, - StringProperty, - ListProperty -) -from kivy.app import App - -from kivymd.uix.list import ( - ILeftBody, - IRightBodyTouch, -) -from kivymd.uix.label import MDLabel -from kivymd.toast import kivytoast -from kivymd.uix.card import MDCardSwipe -from kivymd.uix.chip import MDChip -from kivymd.uix.dialog import MDDialog -from kivymd.uix.button import MDFlatButton - -from pybitmessage.bitmessagekivy.get_platform import platform -from pybitmessage.bmconfigparser import config - -ThemeClsColor = [0.12, 0.58, 0.95, 1] - - -data_screens = { - "MailDetail": { - "kv_string": "maildetail", - "Factory": "MailDetail()", - "name_screen": "mailDetail", - "object": 0, - "Import": "from pybitmessage.bitmessagekivy.baseclass.maildetail import MailDetail", - }, -} - - -def load_image_path(): - """Return the path of kivy images""" - image_path = os.path.abspath(os.path.join('pybitmessage', 'images', 'kivy')) - return image_path - - -def get_identity_list(): - """Get list of identities and access 'identity_list' variable in .kv file""" - identity_list = ListProperty( - addr for addr in config.addresses() if config.getboolean(str(addr), 'enabled') - ) - return identity_list - - -def kivy_state_variables(): - """Return kivy_state variable""" - kivy_running_app = App.get_running_app() - kivy_state = kivy_running_app.kivy_state_obj - return kivy_state - - -def chip_tag(text): - """Create a new ChipTag""" - obj = MDChip() - # obj.size_hint = (None, None) - obj.size_hint = (0.16 if platform == "android" else 0.08, None) - obj.text = text - obj.icon = "" - obj.pos_hint = { - "center_x": 0.91 if platform == "android" else 0.94, - "center_y": 0.3 - } - obj.height = dp(18) - obj.text_color = (1, 1, 1, 1) - obj.radius = [8] - return obj - - -def toast(text): - """Method will display the toast message""" - kivytoast.toast(text) - - -def show_limited_cnt(total_msg): - """This method set the total count limit in badge_text""" - max_msg_count = '99+' - total_msg_limit = 99 - return max_msg_count if total_msg > total_msg_limit else str(total_msg) - - -def avatar_image_first_letter(letter_string): - """Returns first letter for the avatar image""" - try: - image_letter = letter_string.title()[0] - if image_letter.isalnum(): - return image_letter - return '!' - except IndexError: - return '!' - - -def add_time_widget(time): # pylint: disable=redefined-outer-name, W0201 - """This method is used to create TimeWidget""" - action_time = TimeTagRightSampleWidget( - text=str(show_time_history(time)), - font_style="Caption", - size=[120, 140] if platform == "android" else [64, 80], - ) - action_time.font_size = "11sp" - return action_time - - -def show_time_history(act_time): - """This method is used to return the message sent or receive time""" - action_time = datetime.fromtimestamp(int(act_time)) - crnt_date = datetime.now() - duration = crnt_date - action_time - if duration.days < 1: - return action_time.strftime("%I:%M %p") - if duration.days < 365: - return action_time.strftime("%d %b") - return action_time.strftime("%d/%m/%Y") - - -# pylint: disable=too-few-public-methods -class AvatarSampleWidget(ILeftBody, Image): - """AvatarSampleWidget class for kivy Ui""" - - -class TimeTagRightSampleWidget(IRightBodyTouch, MDLabel): - """TimeTagRightSampleWidget class for Ui""" - - -class SwipeToDeleteItem(MDCardSwipe): - """Swipe delete class for App UI""" - text = StringProperty() - cla = Window.size[0] / 2 - # cla = 800 - swipe_distance = NumericProperty(cla) - opening_time = NumericProperty(0.5) - - -class CustomSwipeToDeleteItem(MDCardSwipe): - """Custom swipe delete class for App UI""" - text = StringProperty() - cla = Window.size[0] / 2 - swipe_distance = NumericProperty(cla) - opening_time = NumericProperty(0.5) - - -def empty_screen_label(label_str=None, no_search_res_found=None): - """Returns default text on screen when no address is there.""" - kivy_state = kivy_state_variables() - content = MDLabel( - font_style='Caption', - theme_text_color='Primary', - text=no_search_res_found if kivy_state.searching_text else label_str, - halign='center', - size_hint_y=None, - valign='top') - return content - - -def retrieve_secondary_text(mail): - """Retriving mail details""" - secondary_txt_len = 10 - third_txt_len = 25 - dot_str = '...........' - dot_str2 = '...!' - third_text = mail[3].replace('\n', ' ') - - if len(third_text) > third_txt_len: - if len(mail[2]) > secondary_txt_len: # pylint: disable=no-else-return - return mail[2][:secondary_txt_len] + dot_str - else: - return mail[2] + '\n' + " " + (third_text[:third_txt_len] + dot_str2) - else: - return third_text - - -def set_mail_details(mail): - """Setting mail details""" - mail_details_data = { - 'text': mail[1].strip(), - 'secondary_text': retrieve_secondary_text(mail), - 'ackdata': mail[5], - 'senttime': mail[6] - } - return mail_details_data - - -def mdlist_message_content(queryreturn, data): - """Set Mails details in MD_list""" - for mail in queryreturn: - mdlist_data = set_mail_details(mail) - data.append(mdlist_data) - - -def msg_content_length(body, subject, max_length=50): - """This function concatinate body and subject if len(subject) > 50""" - continue_str = '........' - if len(subject) >= max_length: - subject = subject[:max_length] + continue_str - else: - subject = ((subject + ',' + body)[0:50] + continue_str).replace('\t', '').replace(' ', '') - return subject - - -def composer_common_dialog(alert_msg): - """Common alert popup for message composer""" - is_android_width = .8 - other_platform_width = .55 - dialog_height = .25 - width = is_android_width if platform == 'android' else other_platform_width - - dialog_box = MDDialog( - text=alert_msg, - size_hint=(width, dialog_height), - buttons=[ - MDFlatButton( - text="Ok", on_release=lambda x: callback_for_menu_items("Ok") - ), - ], - ) - dialog_box.open() - - def callback_for_menu_items(text_item, *arg): - """Callback of alert box""" - dialog_box.dismiss() - toast(text_item) diff --git a/src/bitmessagekivy/baseclass/common_mail_detail.py b/src/bitmessagekivy/baseclass/common_mail_detail.py deleted file mode 100644 index 78ae88d5..00000000 --- a/src/bitmessagekivy/baseclass/common_mail_detail.py +++ /dev/null @@ -1,22 +0,0 @@ -# pylint: disable=no-name-in-module, attribute-defined-outside-init, import-error -""" - All Common widgets of kivy are managed here. -""" - -from pybitmessage.bitmessagekivy.baseclass.maildetail import MailDetail -from pybitmessage.bitmessagekivy.baseclass.common import kivy_state_variables - - -def mail_detail_screen(screen_name, msg_id, instance, folder, *args): # pylint: disable=unused-argument - """Common function for all screens to open Mail detail.""" - kivy_state = kivy_state_variables() - if instance.open_progress == 0.0: - kivy_state.detail_page_type = folder - kivy_state.mail_id = msg_id - if screen_name.manager: - src_mng_obj = screen_name.manager - else: - src_mng_obj = screen_name.parent.parent - src_mng_obj.screens[11].clear_widgets() - src_mng_obj.screens[11].add_widget(MailDetail()) - src_mng_obj.current = "mailDetail" diff --git a/src/bitmessagekivy/baseclass/draft.py b/src/bitmessagekivy/baseclass/draft.py deleted file mode 100644 index 1b3ddf21..00000000 --- a/src/bitmessagekivy/baseclass/draft.py +++ /dev/null @@ -1,58 +0,0 @@ -# pylint: disable=unused-argument, import-error, too-many-arguments -# pylint: disable=unnecessary-comprehension, no-member, no-name-in-module - -""" -draft.py -============== - -Draft screen - -""" -from kivy.clock import Clock -from kivy.properties import ( - ListProperty, - StringProperty -) -from kivy.uix.screenmanager import Screen -from kivy.app import App -from pybitmessage.bitmessagekivy.baseclass.common import ( - show_limited_cnt, empty_screen_label, - kivy_state_variables -) -import logging -logger = logging.getLogger('default') - - -class Draft(Screen): - """Draft screen class for kivy Ui""" - - data = ListProperty() - account = StringProperty() - queryreturn = ListProperty() - has_refreshed = True - label_str = "yet no message for this account!!!!!!!!!!!!!" - - def __init__(self, *args, **kwargs): - """Method used for storing draft messages""" - super(Draft, self).__init__(*args, **kwargs) - self.kivy_state = kivy_state_variables() - if self.kivy_state.selected_address == '': - if App.get_running_app().identity_list: - self.kivy_state.selected_address = App.get_running_app().identity_list[0] - Clock.schedule_once(self.init_ui, 0) - - def init_ui(self, dt=0): - """Clock Schedule for method draft accounts""" - self.load_draft() - logger.debug(dt) - - def load_draft(self, where="", what=""): - """Load draft list for Draft messages""" - self.set_draft_count('0') - self.ids.ml.add_widget(empty_screen_label(self.label_str)) - - @staticmethod - def set_draft_count(Count): - """Set the count of draft mails""" - draftCnt_obj = App.get_running_app().root.ids.content_drawer.ids.draft_cnt - draftCnt_obj.ids.badge_txt.text = show_limited_cnt(int(Count)) diff --git a/src/bitmessagekivy/baseclass/inbox.py b/src/bitmessagekivy/baseclass/inbox.py deleted file mode 100644 index 2e145871..00000000 --- a/src/bitmessagekivy/baseclass/inbox.py +++ /dev/null @@ -1,64 +0,0 @@ -# pylint: disable=unused-import, too-many-public-methods, unused-variable, too-many-ancestors -# pylint: disable=no-name-in-module, too-few-public-methods, import-error, unused-argument, too-many-arguments -# pylint: disable=attribute-defined-outside-init, global-variable-not-assigned, too-many-instance-attributes - -""" -Kivy UI for inbox screen -""" -from kivy.clock import Clock -from kivy.properties import ( - ListProperty, - StringProperty -) -from kivy.app import App -from kivy.uix.screenmanager import Screen - -from pybitmessage.bitmessagekivy.baseclass.common import kivy_state_variables, load_image_path - - -class Inbox(Screen): - """Inbox Screen class for kivy Ui""" - - queryreturn = ListProperty() - has_refreshed = True - account = StringProperty() - no_search_res_found = "No search result found" - label_str = "Yet no message for this account!" - - def __init__(self, *args, **kwargs): - """Initialize kivy variables""" - super(Inbox, self).__init__(*args, **kwargs) - self.kivy_running_app = App.get_running_app() - self.kivy_state = kivy_state_variables() - self.image_dir = load_image_path() - Clock.schedule_once(self.init_ui, 0) - - def set_defaultAddress(self): - """Set default address""" - if self.kivy_state.selected_address == "": - if self.kivy_running_app.identity_list: - self.kivy_state.selected_address = self.kivy_running_app.identity_list[0] - - def init_ui(self, dt=0): - """loadMessagelist() call at specific interval""" - self.loadMessagelist() - - def loadMessagelist(self, where="", what=""): - """Load inbox list for inbox messages""" - self.set_defaultAddress() - self.account = self.kivy_state.selected_address - - def refresh_callback(self, *args): - """Load inbox messages while wating-loader spins & called in inbox.kv""" - - def refresh_on_scroll_down(interval): - """Reset fields and load data on scrolling upside down""" - self.kivy_state.searching_text = "" - self.children[2].children[1].ids.search_field.text = "" - self.ids.ml.clear_widgets() - self.loadMessagelist(self.kivy_state.selected_address) - self.has_refreshed = True - self.ids.refresh_layout.refresh_done() - self.tick = 0 - - Clock.schedule_once(refresh_on_scroll_down, 1) diff --git a/src/bitmessagekivy/baseclass/login.py b/src/bitmessagekivy/baseclass/login.py deleted file mode 100644 index c5dd9ef4..00000000 --- a/src/bitmessagekivy/baseclass/login.py +++ /dev/null @@ -1,97 +0,0 @@ -# pylint: disable=no-member, too-many-arguments, too-few-public-methods -# pylint: disable=no-name-in-module, unused-argument, arguments-differ - -""" -Login screen appears when the App is first time starts and when new Address is generated. -""" - - -from kivy.clock import Clock -from kivy.properties import StringProperty, BooleanProperty -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.screenmanager import Screen -from kivy.app import App - -from kivymd.uix.behaviors.elevation import RectangularElevationBehavior - -from pybitmessage.backend.address_generator import AddressGenerator -from pybitmessage.bitmessagekivy.baseclass.common import toast -from pybitmessage.bmconfigparser import config - - -class Login(Screen): - """Login Screeen class for kivy Ui""" - log_text1 = ( - 'You may generate addresses by using either random numbers' - ' or by using a passphrase If you use a passphrase, the address' - ' is called a deterministic; address The Random Number option is' - ' selected by default but deterministic addresses have several pros' - ' and cons:') - log_text2 = ('If talk about pros You can recreate your addresses on any computer' - ' from memory, You need-not worry about backing up your keys.dat file' - ' as long as you can remember your passphrase and aside talk about cons' - ' You must remember (or write down) your You must remember the address' - ' version number and the stream number along with your passphrase If you' - ' choose a weak passphrase and someone on the Internet can brute-force it,' - ' they can read your messages and send messages as you') - - -class Random(Screen): - """Random Screen class for Ui""" - - is_active = BooleanProperty(False) - checked = StringProperty("") - - def generateaddress(self): - """Method for Address Generator""" - entered_label = str(self.ids.add_random_bx.children[0].ids.lab.text).strip() - if not entered_label: - self.ids.add_random_bx.children[0].ids.lab.focus = True - is_address = AddressGenerator.random_address_generation( - entered_label, streamNumberForAddress=1, eighteenByteRipe=False, - nonceTrialsPerByte=1000, payloadLengthExtraBytes=1000 - ) - if is_address: - toast('Creating New Address ...') - self.parent.parent.ids.toolbar.opacity = 1 - self.parent.parent.ids.toolbar.disabled = False - App.get_running_app().loadMyAddressScreen(True) - self.manager.current = 'myaddress' - Clock.schedule_once(self.address_created_callback, 6) - - def address_created_callback(self, dt=0): - """New address created""" - App.get_running_app().loadMyAddressScreen(False) - App.get_running_app().root.ids.id_myaddress.ids.ml.clear_widgets() - App.get_running_app().root.ids.id_myaddress.is_add_created = True - App.get_running_app().root.ids.id_myaddress.init_ui() - self.reset_address_spinner() - toast('New address created') - - def reset_address_spinner(self): - """reseting spinner address and UI""" - addresses = [addr for addr in config.addresses() - if config.getboolean(str(addr), 'enabled')] - self.manager.parent.ids.content_drawer.ids.identity_dropdown.values = [] - self.manager.parent.ids.content_drawer.ids.identity_dropdown.values = addresses - self.manager.parent.ids.id_create.children[1].ids.composer_dropdown.values = [] - self.manager.parent.ids.id_create.children[1].ids.composer_dropdown.values = addresses - - @staticmethod - def add_validation(instance): - """Retrieve created labels and validate""" - entered_label = str(instance.text.strip()) - AddressGenerator.address_validation(instance, entered_label) - - def reset_address_label(self): - """Resetting address labels""" - if not self.ids.add_random_bx.children: - self.ids.add_random_bx.add_widget(RandomBoxlayout()) - - -class InfoLayout(BoxLayout, RectangularElevationBehavior): - """InfoLayout class for kivy Ui""" - - -class RandomBoxlayout(BoxLayout): - """RandomBoxlayout class for BoxLayout behaviour""" diff --git a/src/bitmessagekivy/baseclass/maildetail.py b/src/bitmessagekivy/baseclass/maildetail.py deleted file mode 100644 index 6ddf322d..00000000 --- a/src/bitmessagekivy/baseclass/maildetail.py +++ /dev/null @@ -1,242 +0,0 @@ -# pylint: disable=unused-argument, consider-using-f-string, import-error, attribute-defined-outside-init -# pylint: disable=unnecessary-comprehension, no-member, no-name-in-module, too-few-public-methods - -""" -Maildetail screen for inbox, sent, draft and trash. -""" - -import os -from datetime import datetime - -from kivy.core.clipboard import Clipboard -from kivy.clock import Clock -from kivy.properties import ( - StringProperty, - NumericProperty -) -from kivy.uix.screenmanager import Screen -from kivy.factory import Factory -from kivy.app import App - -from kivymd.uix.button import MDFlatButton, MDIconButton -from kivymd.uix.dialog import MDDialog -from kivymd.uix.list import ( - OneLineListItem, - IRightBodyTouch -) - -from pybitmessage.bitmessagekivy.baseclass.common import ( - toast, avatar_image_first_letter, show_time_history, kivy_state_variables -) -from pybitmessage.bitmessagekivy.baseclass.popup import SenderDetailPopup -from pybitmessage.bitmessagekivy.get_platform import platform -from pybitmessage.helper_sql import sqlQuery - - -class OneLineListTitle(OneLineListItem): - """OneLineListTitle class for kivy Ui""" - __events__ = ('on_long_press', ) - long_press_time = NumericProperty(1) - - def on_state(self, instance, value): - """On state""" - if value == 'down': - lpt = self.long_press_time - self._clockev = Clock.schedule_once(self._do_long_press, lpt) - else: - self._clockev.cancel() - - def _do_long_press(self, dt): - """Do long press""" - self.dispatch('on_long_press') - - def on_long_press(self, *largs): - """On long press""" - self.copymessageTitle(self.text) - - def copymessageTitle(self, title_text): - """this method is for displaying dialog box""" - self.title_text = title_text - width = .8 if platform == 'android' else .55 - self.dialog_box = MDDialog( - text=title_text, - size_hint=(width, .25), - buttons=[ - MDFlatButton( - text="Copy", on_release=self.callback_for_copy_title - ), - MDFlatButton( - text="Cancel", on_release=self.callback_for_copy_title, - ), - ],) - self.dialog_box.open() - - def callback_for_copy_title(self, instance): - """Callback of alert box""" - if instance.text == 'Copy': - Clipboard.copy(self.title_text) - self.dialog_box.dismiss() - toast(instance.text) - - -class IconRightSampleWidget(IRightBodyTouch, MDIconButton): - """IconRightSampleWidget class for kivy Ui""" - - -class MailDetail(Screen): # pylint: disable=too-many-instance-attributes - """MailDetail Screen class for kivy Ui""" - - to_addr = StringProperty() - from_addr = StringProperty() - subject = StringProperty() - message = StringProperty() - status = StringProperty() - page_type = StringProperty() - time_tag = StringProperty() - avatarImg = StringProperty() - no_subject = '(no subject)' - - def __init__(self, *args, **kwargs): - """Mail Details method""" - super(MailDetail, self).__init__(*args, **kwargs) - self.kivy_state = kivy_state_variables() - Clock.schedule_once(self.init_ui, 0) - - def init_ui(self, dt=0): - """Clock Schdule for method MailDetail mails""" - self.page_type = self.kivy_state.detail_page_type if self.kivy_state.detail_page_type else '' - try: - if self.kivy_state.detail_page_type in ('sent', 'draft'): - App.get_running_app().set_mail_detail_header() - elif self.kivy_state.detail_page_type == 'inbox': - data = sqlQuery( - "select toaddress, fromaddress, subject, message, received from inbox" - " where msgid = ?", self.kivy_state.mail_id) - self.assign_mail_details(data) - App.get_running_app().set_mail_detail_header() - except Exception as e: # pylint: disable=unused-variable - print('Something wents wrong!!') - - def assign_mail_details(self, data): - """Assigning mail details""" - subject = data[0][2].decode() if isinstance(data[0][2], bytes) else data[0][2] - body = data[0][3].decode() if isinstance(data[0][2], bytes) else data[0][3] - self.to_addr = data[0][0] if len(data[0][0]) > 4 else ' ' - self.from_addr = data[0][1] - - self.subject = subject.capitalize( - ) if subject.capitalize() else self.no_subject - self.message = body - if len(data[0]) == 7: - self.status = data[0][4] - self.time_tag = show_time_history(data[0][4]) if self.kivy_state.detail_page_type == 'inbox' \ - else show_time_history(data[0][6]) - self.avatarImg = os.path.join(self.kivy_state.imageDir, 'draft-icon.png') \ - if self.kivy_state.detail_page_type == 'draft' \ - else (os.path.join(self.kivy_state.imageDir, 'text_images', '{0}.png'.format(avatar_image_first_letter( - self.subject.strip())))) - self.timeinseconds = data[0][4] if self.kivy_state.detail_page_type == 'inbox' else data[0][6] - - def delete_mail(self): - """Method for mail delete""" - msg_count_objs = App.get_running_app().root.ids.content_drawer.ids - self.kivy_state.searching_text = '' - self.children[0].children[0].active = True - if self.kivy_state.detail_page_type == 'sent': - App.get_running_app().root.ids.id_sent.ids.sent_search.ids.search_field.text = '' - msg_count_objs.send_cnt.ids.badge_txt.text = str(int(self.kivy_state.sent_count) - 1) - self.kivy_state.sent_count = str(int(self.kivy_state.sent_count) - 1) - self.parent.screens[2].ids.ml.clear_widgets() - self.parent.screens[2].loadSent(self.kivy_state.selected_address) - elif self.kivy_state.detail_page_type == 'inbox': - App.get_running_app().root.ids.id_inbox.ids.inbox_search.ids.search_field.text = '' - msg_count_objs.inbox_cnt.ids.badge_txt.text = str( - int(self.kivy_state.inbox_count) - 1) - self.kivy_state.inbox_count = str(int(self.kivy_state.inbox_count) - 1) - self.parent.screens[0].ids.ml.clear_widgets() - self.parent.screens[0].loadMessagelist(self.kivy_state.selected_address) - - elif self.kivy_state.detail_page_type == 'draft': - msg_count_objs.draft_cnt.ids.badge_txt.text = str( - int(self.kivy_state.draft_count) - 1) - self.kivy_state.draft_count = str(int(self.kivy_state.draft_count) - 1) - self.parent.screens[13].clear_widgets() - self.parent.screens[13].add_widget(Factory.Draft()) - - if self.kivy_state.detail_page_type != 'draft': - msg_count_objs.trash_cnt.ids.badge_txt.text = str( - int(self.kivy_state.trash_count) + 1) - msg_count_objs.allmail_cnt.ids.badge_txt.text = str( - int(self.kivy_state.all_count) - 1) - self.kivy_state.trash_count = str(int(self.kivy_state.trash_count) + 1) - self.kivy_state.all_count = str(int(self.kivy_state.all_count) - 1) if \ - int(self.kivy_state.all_count) else '0' - self.parent.screens[3].clear_widgets() - self.parent.screens[3].add_widget(Factory.Trash()) - self.parent.screens[14].clear_widgets() - self.parent.screens[14].add_widget(Factory.Allmails()) - Clock.schedule_once(self.callback_for_delete, 4) - - def callback_for_delete(self, dt=0): - """Delete method from allmails""" - if self.kivy_state.detail_page_type: - self.children[0].children[0].active = False - App.get_running_app().set_common_header() - self.parent.current = 'allmails' \ - if self.kivy_state.is_allmail else self.kivy_state.detail_page_type - self.kivy_state.detail_page_type = '' - toast('Deleted') - - def get_message_details_to_reply(self, data): - """Getting message details and fill into fields when reply""" - sender_address = ' wrote:--------------\n' - message_time = '\n\n --------------On ' - composer_obj = self.parent.screens[1].children[1].ids - composer_obj.ti.text = data[0][0] - composer_obj.composer_dropdown.text = data[0][0] - composer_obj.txt_input.text = data[0][1] - split_subject = data[0][2].split('Re:', 1) - composer_obj.subject.text = 'Re: ' + (split_subject[1] if len(split_subject) > 1 else split_subject[0]) - time_obj = datetime.fromtimestamp(int(data[0][4])) - time_tag = time_obj.strftime("%d %b %Y, %I:%M %p") - sender_name = data[0][1] - composer_obj.body.text = ( - message_time + time_tag + ', ' + sender_name + sender_address + data[0][3]) - composer_obj.body.focus = True - composer_obj.body.cursor = (0, 0) - - def inbox_reply(self): - """Reply inbox messages""" - self.kivy_state.in_composer = True - App.get_running_app().root.ids.id_create.children[1].ids.rv.data = '' - App.get_running_app().root.ids.sc3.children[1].ids.rv.data = '' - self.parent.current = 'create' - App.get_running_app().set_navbar_for_composer() - - def get_message_details_for_draft_reply(self, data): - """Getting and setting message details fill into fields when draft reply""" - composer_ids = ( - self.parent.parent.ids.id_create.children[1].ids) - composer_ids.ti.text = data[0][1] - composer_ids.btn.text = data[0][1] - composer_ids.txt_input.text = data[0][0] - composer_ids.subject.text = data[0][2] if data[0][2] != self.no_subject else '' - composer_ids.body.text = data[0][3] - - def write_msg(self, navApp): - """Write on draft mail""" - self.kivy_state.send_draft_mail = self.kivy_state.mail_id - self.parent.current = 'create' - navApp.set_navbar_for_composer() - - def detailedPopup(self): - """Detailed popup""" - obj = SenderDetailPopup() - obj.open() - arg = (self.to_addr, self.from_addr, self.timeinseconds) - obj.assignDetail(*arg) - - @staticmethod - def callback_for_menu_items(text_item, *arg): - """Callback of alert box""" - toast(text_item) diff --git a/src/bitmessagekivy/baseclass/msg_composer.py b/src/bitmessagekivy/baseclass/msg_composer.py deleted file mode 100644 index a36996e0..00000000 --- a/src/bitmessagekivy/baseclass/msg_composer.py +++ /dev/null @@ -1,188 +0,0 @@ -# pylint: disable=unused-argument, consider-using-f-string, too-many-ancestors -# pylint: disable=no-member, no-name-in-module, too-few-public-methods, no-name-in-module -""" - Message composer screen UI -""" - -import logging - -from kivy.app import App -from kivy.properties import ( - BooleanProperty, - ListProperty, - NumericProperty, - ObjectProperty, -) -from kivy.uix.behaviors import FocusBehavior -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.label import Label -from kivy.uix.recycleview import RecycleView -from kivy.uix.recycleboxlayout import RecycleBoxLayout -from kivy.uix.recycleview.layout import LayoutSelectionBehavior -from kivy.uix.recycleview.views import RecycleDataViewBehavior -from kivy.uix.screenmanager import Screen - -from kivymd.uix.textfield import MDTextField - -from pybitmessage import state -from pybitmessage.bitmessagekivy.get_platform import platform -from pybitmessage.bitmessagekivy.baseclass.common import ( - toast, kivy_state_variables, composer_common_dialog -) - -logger = logging.getLogger('default') - - -class Create(Screen): - """Creates Screen class for kivy Ui""" - - def __init__(self, **kwargs): - """Getting Labels and address from addressbook""" - super(Create, self).__init__(**kwargs) - self.kivy_running_app = App.get_running_app() - self.kivy_state = kivy_state_variables() - self.dropdown_widget = DropDownWidget() - self.dropdown_widget.ids.txt_input.starting_no = 2 - self.add_widget(self.dropdown_widget) - self.children[0].ids.id_scroll.bind(scroll_y=self.check_scroll_y) - - def check_scroll_y(self, instance, somethingelse): # pylint: disable=unused-argument - """show data on scroll down""" - if self.children[1].ids.composer_dropdown.is_open: - self.children[1].ids.composer_dropdown.is_open = False - - -class RV(RecycleView): - """Recycling View class for kivy Ui""" - - def __init__(self, **kwargs): - """Recycling Method""" - super(RV, self).__init__(**kwargs) - - -class SelectableRecycleBoxLayout( - FocusBehavior, LayoutSelectionBehavior, RecycleBoxLayout -): - """Adds selection and focus behaviour to the view""" - # pylint: disable = duplicate-bases - - -class DropDownWidget(BoxLayout): - """DropDownWidget class for kivy Ui""" - - # pylint: disable=too-many-statements - - txt_input = ObjectProperty() - rv = ObjectProperty() - - def __init__(self, **kwargs): - super(DropDownWidget, self).__init__(**kwargs) - self.kivy_running_app = App.get_running_app() - self.kivy_state = kivy_state_variables() - - @staticmethod - def callback_for_msgsend(dt=0): # pylint: disable=unused-argument - """Callback method for messagesend""" - state.kivyapp.root.ids.id_create.children[0].active = False - state.in_sent_method = True - state.kivyapp.back_press() - toast("sent") - - def reset_composer(self): - """Method will reset composer""" - self.ids.ti.text = "" - self.ids.composer_dropdown.text = "Select" - self.ids.txt_input.text = "" - self.ids.subject.text = "" - self.ids.body.text = "" - toast("Reset message") - - def auto_fill_fromaddr(self): - """Fill the text automatically From Address""" - self.ids.ti.text = self.ids.composer_dropdown.text - self.ids.ti.focus = True - - def is_camara_attached(self): - """Checks the camera availability in device""" - self.parent.parent.parent.ids.id_scanscreen.check_camera() - is_available = self.parent.parent.parent.ids.id_scanscreen.camera_available - return is_available - - @staticmethod - def camera_alert(): - """Show camera availability alert message""" - feature_unavailable = 'Currently this feature is not available!' - cam_not_available = 'Camera is not available!' - alert_text = feature_unavailable if platform == 'android' else cam_not_available - composer_common_dialog(alert_text) - - -class MyTextInput(MDTextField): - """MyTextInput class for kivy Ui""" - - txt_input = ObjectProperty() - flt_list = ObjectProperty() - word_list = ListProperty() - starting_no = NumericProperty(3) - suggestion_text = '' - - def __init__(self, **kwargs): - """Getting Text Input.""" - super(MyTextInput, self).__init__(**kwargs) - self.__lineBreak__ = 0 - - def on_text(self, instance, value): # pylint: disable=unused-argument - """Find all the occurrence of the word""" - self.parent.parent.parent.parent.parent.ids.rv.data = [] - max_recipient_len = 10 - box_height = 250 - box_max_height = 400 - - matches = [self.word_list[i] for i in range( - len(self.word_list)) if self.word_list[ - i][:self.starting_no] == value[:self.starting_no]] - display_data = [] - for i in matches: - display_data.append({'text': i}) - self.parent.parent.parent.parent.parent.ids.rv.data = display_data - if len(matches) <= max_recipient_len: - self.parent.height = (box_height + (len(matches) * 20)) - else: - self.parent.height = box_max_height - - def keyboard_on_key_down(self, window, keycode, text, modifiers): - """Keyboard on key Down""" - if self.suggestion_text and keycode[1] == 'tab' and modifiers is None: - self.insert_text(self.suggestion_text + ' ') - return True - return super(MyTextInput, self).keyboard_on_key_down( - window, keycode, text, modifiers) - - -class SelectableLabel(RecycleDataViewBehavior, Label): - """Add selection support to the Label""" - - index = None - selected = BooleanProperty(False) - selectable = BooleanProperty(True) - - def refresh_view_attrs(self, rv, index, data): - """Catch and handle the view changes""" - self.index = index - return super(SelectableLabel, self).refresh_view_attrs(rv, index, data) - - def on_touch_down(self, touch): # pylint: disable=inconsistent-return-statements - """Add selection on touch down""" - if super(SelectableLabel, self).on_touch_down(touch): - return True - if self.collide_point(*touch.pos) and self.selectable: - return self.parent.select_with_touch(self.index, touch) - - def apply_selection(self, rv, index, is_selected): - """Respond to the selection of items in the view""" - self.selected = is_selected - if is_selected: - logger.debug("selection changed to %s", rv.data[index]) - rv.parent.txt_input.text = rv.parent.txt_input.text.replace( - rv.parent.txt_input.text, rv.data[index]["text"] - ) diff --git a/src/bitmessagekivy/baseclass/myaddress.py b/src/bitmessagekivy/baseclass/myaddress.py deleted file mode 100644 index 0a46bae9..00000000 --- a/src/bitmessagekivy/baseclass/myaddress.py +++ /dev/null @@ -1,230 +0,0 @@ -# pylint: disable=unused-argument, import-error, no-member, attribute-defined-outside-init -# pylint: disable=no-name-in-module, too-few-public-methods, too-many-instance-attributes - -""" -myaddress.py -============== -All generated addresses are managed in MyAddress -""" - -import os -from functools import partial - -from kivy.clock import Clock -from kivy.properties import ( - ListProperty, - StringProperty -) -from kivy.uix.screenmanager import Screen, ScreenManagerException -from kivy.app import App - -from kivymd.uix.list import ( - IRightBodyTouch, - TwoLineAvatarIconListItem, -) -from kivymd.uix.selectioncontrol import MDSwitch - -from pybitmessage.bmconfigparser import config - -from pybitmessage.bitmessagekivy.get_platform import platform -from pybitmessage.bitmessagekivy.baseclass.common import ( - avatar_image_first_letter, AvatarSampleWidget, ThemeClsColor, - toast, empty_screen_label, load_image_path -) - -from pybitmessage.bitmessagekivy.baseclass.popup import MyaddDetailPopup -from pybitmessage.bitmessagekivy.baseclass.myaddress_widgets import HelperMyAddress - - -class ToggleBtn(IRightBodyTouch, MDSwitch): - """ToggleBtn class for kivy UI""" - - -class CustomTwoLineAvatarIconListItem(TwoLineAvatarIconListItem): - """CustomTwoLineAvatarIconListItem class for kivy Ui""" - - -class MyAddress(Screen, HelperMyAddress): - """MyAddress screen class for kivy Ui""" - - address_label = StringProperty() - text_address = StringProperty() - addresses_list = ListProperty() - has_refreshed = True - is_add_created = False - label_str = "Yet no address is created by user!!!!!!!!!!!!!" - no_search_res_found = "No search result found" - min_scroll_y_limit = -0.0 - scroll_y_step = 0.06 - number_of_addresses = 20 - addresses_at_a_time = 15 - canvas_color_black = [0, 0, 0, 0] - canvas_color_gray = [0.5, 0.5, 0.5, 0.5] - is_android_width = .9 - other_platform_width = .6 - disabled_addr_width = .8 - other_platform_disabled_addr_width = .55 - max_scroll_limit = 1.0 - - def __init__(self, *args, **kwargs): - """Clock schdule for method Myaddress accounts""" - super(MyAddress, self).__init__(*args, **kwargs) - self.image_dir = load_image_path() - self.kivy_running_app = App.get_running_app() - self.kivy_state = self.kivy_running_app.kivy_state_obj - - Clock.schedule_once(self.init_ui, 0) - - def init_ui(self, dt=0): - """Clock schdule for method Myaddress accounts""" - self.addresses_list = config.addresses() - if self.kivy_state.searching_text: - self.ids.refresh_layout.scroll_y = self.max_scroll_limit - filtered_list = [ - x for x in config.addresses() - if self.filter_address(x) - ] - self.addresses_list = filtered_list - self.addresses_list = [obj for obj in reversed(self.addresses_list)] - self.ids.tag_label.text = '' - if self.addresses_list: - self.ids.tag_label.text = 'My Addresses' - self.has_refreshed = True - self.set_mdList(0, self.addresses_at_a_time) - self.ids.refresh_layout.bind(scroll_y=self.check_scroll_y) - else: - self.ids.ml.add_widget(empty_screen_label(self.label_str, self.no_search_res_found)) - if not self.kivy_state.searching_text and not self.is_add_created: - try: - self.manager.current = 'login' - except ScreenManagerException: - pass - - def get_address_list(self, first_index, last_index, data): - """Getting address and append to the list""" - for address in self.addresses_list[first_index:last_index]: - data.append({ - 'text': config.get(address, 'label'), - 'secondary_text': address} - ) - return data - - def set_address_to_widget(self, item): - """Setting address to the widget""" - is_enable = config.getboolean(item['secondary_text'], 'enabled') - meny = CustomTwoLineAvatarIconListItem( - text=item['text'], secondary_text=item['secondary_text'], - theme_text_color='Custom' if is_enable else 'Primary', - text_color=ThemeClsColor,) - meny.canvas.children[3].rgba = \ - self.canvas_color_black if is_enable else self.canvas_color - meny.add_widget(AvatarSampleWidget( - source=os.path.join( - self.image_dir, "text_images", "{}.png".format(avatar_image_first_letter( - item["text"].strip()))) - )) - meny.bind(on_press=partial( - self.myadd_detail, item['secondary_text'], item['text'])) - self.set_address_status(item, meny, is_enable) - - def set_address_status(self, item, meny, is_enable): - """Setting the identity status enable/disable on UI""" - if self.kivy_state.selected_address == item['secondary_text'] and is_enable: - meny.add_widget(self.is_active_badge()) - else: - meny.add_widget(ToggleBtn(active=True if is_enable else False)) - self.ids.ml.add_widget(meny) - - def set_mdList(self, first_index, last_index): - """Creating the mdlist""" - data = [] - self.get_address_list(first_index, last_index, data) - for item in data: - self.set_address_to_widget(item) - - def check_scroll_y(self, instance, somethingelse): - """Load data on Scroll down""" - if self.ids.refresh_layout.scroll_y <= self.min_scroll_y_limit and self.has_refreshed: - self.ids.refresh_layout.scroll_y = self.scroll_y_step - my_addresses = len(self.ids.ml.children) - if my_addresses != len(self.addresses_list): - self.update_addressBook_on_scroll(my_addresses) - self.has_refreshed = ( - True if my_addresses != len(self.addresses_list) else False - ) - - def update_addressBook_on_scroll(self, my_addresses): - """Loads more data on scroll down""" - self.set_mdList(my_addresses, my_addresses + self.number_of_addresses) - - def myadd_detail(self, fromaddress, label, *args): - """Load myaddresses details""" - if config.getboolean(fromaddress, 'enabled'): - obj = MyaddDetailPopup() - self.address_label = obj.address_label = label - self.text_address = obj.address = fromaddress - width = self.is_android_width if platform == 'android' else self.other_platform_width - self.myadddetail_popup = self.myaddress_detail_popup(obj, width) - self.myadddetail_popup.auto_dismiss = False - self.myadddetail_popup.open() - else: - width = self.disabled_addr_width if platform == 'android' else self.other_platform_disabled_addr_width - self.dialog_box = self.inactive_address_popup(width, self.callback_for_menu_items) - self.dialog_box.open() - - def callback_for_menu_items(self, text_item, *arg): - """Callback of inactive address alert box""" - self.dialog_box.dismiss() - toast(text_item) - - def refresh_callback(self, *args): - """Method updates the state of application, - While the spinner remains on the screen""" - def refresh_callback(interval): - """Method used for loading the myaddress screen data""" - self.kivy_state.searching_text = '' - self.ids.search_bar.ids.search_field.text = '' - self.has_refreshed = True - self.ids.ml.clear_widgets() - self.init_ui() - self.ids.refresh_layout.refresh_done() - Clock.schedule_once(self.address_permision_callback, 0) - Clock.schedule_once(refresh_callback, 1) - - @staticmethod - def filter_address(address): - """It will return True if search is matched""" - searched_text = App.get_running_app().kivy_state_obj.searching_text.lower() - return bool(config.search_addresses(address, searched_text)) - - def disable_address_ui(self, address, instance): - """This method is used to disable addresses from UI""" - config.disable_address(address) - instance.parent.parent.theme_text_color = 'Primary' - instance.parent.parent.canvas.children[3].rgba = MyAddress.canvas_color_gray - toast('Address disabled') - Clock.schedule_once(self.address_permision_callback, 0) - - def enable_address_ui(self, address, instance): - """This method is used to enable addresses from UI""" - config.enable_address(address) - instance.parent.parent.theme_text_color = 'Custom' - instance.parent.parent.canvas.children[3].rgba = MyAddress.canvas_color_black - toast('Address Enabled') - Clock.schedule_once(self.address_permision_callback, 0) - - def address_permision_callback(self, dt=0): - """callback for enable or disable addresses""" - addresses = [addr for addr in config.addresses() - if config.getboolean(str(addr), 'enabled')] - self.parent.parent.ids.content_drawer.ids.identity_dropdown.values = addresses - self.parent.parent.ids.id_create.children[1].ids.composer_dropdown.values = addresses - self.kivy_running_app.identity_list = addresses - - def toggleAction(self, instance): - """This method is used for enable or disable address""" - addr = instance.parent.parent.secondary_text - if instance.active: - self.enable_address_ui(addr, instance) - else: - self.disable_address_ui(addr, instance) diff --git a/src/bitmessagekivy/baseclass/myaddress_widgets.py b/src/bitmessagekivy/baseclass/myaddress_widgets.py deleted file mode 100644 index 23e2342f..00000000 --- a/src/bitmessagekivy/baseclass/myaddress_widgets.py +++ /dev/null @@ -1,64 +0,0 @@ -# pylint: disable=too-many-arguments, no-name-in-module, import-error -# pylint: disable=too-few-public-methods, no-member, too-many-ancestors - -""" -MyAddress widgets are here. -""" - -from kivymd.uix.button import MDFlatButton -from kivymd.uix.dialog import MDDialog -from kivymd.uix.label import MDLabel -from kivymd.uix.list import IRightBodyTouch - -from pybitmessage.bitmessagekivy.get_platform import platform -from pybitmessage.bitmessagekivy.baseclass.common import ThemeClsColor - - -class BadgeText(IRightBodyTouch, MDLabel): - """BadgeText class for kivy UI""" - - -class HelperMyAddress(object): - """Widget used in MyAddress are here""" - dialog_height = .25 - - @staticmethod - def is_active_badge(): - """This function show the 'active' label of active Address.""" - active_status = 'Active' - is_android_width = 90 - width = 50 - height = 60 - badge_obj = BadgeText( - size_hint=(None, None), - size=[is_android_width if platform == 'android' else width, height], - text=active_status, halign='center', - font_style='Body1', theme_text_color='Custom', - text_color=ThemeClsColor, font_size='13sp' - ) - return badge_obj - - @staticmethod - def myaddress_detail_popup(obj, width): - """This method show the details of address as popup opens.""" - show_myaddress_dialogue = MDDialog( - type="custom", - size_hint=(width, HelperMyAddress.dialog_height), - content_cls=obj, - ) - return show_myaddress_dialogue - - @staticmethod - def inactive_address_popup(width, callback_for_menu_items): - """This method shows the warning popup if the address is inactive""" - dialog_text = 'Address is not currently active. Please click on Toggle button to active it.' - dialog_box = MDDialog( - text=dialog_text, - size_hint=(width, HelperMyAddress.dialog_height), - buttons=[ - MDFlatButton( - text="Ok", on_release=lambda x: callback_for_menu_items("Ok") - ), - ], - ) - return dialog_box diff --git a/src/bitmessagekivy/baseclass/network.py b/src/bitmessagekivy/baseclass/network.py deleted file mode 100644 index dcb3f082..00000000 --- a/src/bitmessagekivy/baseclass/network.py +++ /dev/null @@ -1,54 +0,0 @@ -# pylint: disable=unused-argument, consider-using-f-string -# pylint: disable=no-name-in-module, too-few-public-methods - -""" - Network status -""" - -import os - -from kivy.clock import Clock -from kivy.properties import StringProperty -from kivy.uix.screenmanager import Screen - -from pybitmessage import state - -if os.environ.get('INSTALL_TESTS', False) and not state.backend_py3_compatible: - from pybitmessage.mockbm import kivy_main - stats = kivy_main.network.stats - objectracker = kivy_main.network.objectracker -else: - from pybitmessage.network import stats, objectracker - - -class NetworkStat(Screen): - """NetworkStat class for kivy Ui""" - - text_variable_1 = StringProperty( - '{0}::{1}'.format('Total Connections', '0')) - text_variable_2 = StringProperty( - 'Processed {0} per-to-per messages'.format('0')) - text_variable_3 = StringProperty( - 'Processed {0} brodcast messages'.format('0')) - text_variable_4 = StringProperty( - 'Processed {0} public keys'.format('0')) - text_variable_5 = StringProperty( - 'Processed {0} object to be synced'.format('0')) - - def __init__(self, *args, **kwargs): - """Init method for network stat""" - super(NetworkStat, self).__init__(*args, **kwargs) - Clock.schedule_interval(self.init_ui, 1) - - def init_ui(self, dt=0): - """Clock Schdule for method networkstat screen""" - self.text_variable_1 = '{0} :: {1}'.format( - 'Total Connections', str(len(stats.connectedHostsList()))) - self.text_variable_2 = 'Processed {0} per-to-per messages'.format( - str(state.numberOfMessagesProcessed)) - self.text_variable_3 = 'Processed {0} brodcast messages'.format( - str(state.numberOfBroadcastsProcessed)) - self.text_variable_4 = 'Processed {0} public keys'.format( - str(state.numberOfPubkeysProcessed)) - self.text_variable_5 = '{0} object to be synced'.format( - len(objectracker.missingObjects)) diff --git a/src/bitmessagekivy/baseclass/payment.py b/src/bitmessagekivy/baseclass/payment.py deleted file mode 100644 index 6749e1bb..00000000 --- a/src/bitmessagekivy/baseclass/payment.py +++ /dev/null @@ -1,66 +0,0 @@ -# pylint: disable=import-error, no-name-in-module, too-few-public-methods, too-many-ancestors - -''' - Payment/subscription frontend -''' - -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.screenmanager import Screen -from kivy.app import App - -from kivymd.uix.behaviors.elevation import RectangularElevationBehavior -from kivymd.uix.label import MDLabel -from kivymd.uix.list import ( - IRightBodyTouch, - OneLineAvatarIconListItem -) - -from pybitmessage.bitmessagekivy.baseclass.common import toast, kivy_state_variables - - -class Payment(Screen): - """Payment Screen class for kivy Ui""" - - def __init__(self, *args, **kwargs): - """Instantiate kivy state variable""" - super(Payment, self).__init__(*args, **kwargs) - self.kivy_state = kivy_state_variables() - - # TODO: get_free_credits() is not used anywhere, will be used later for Payment/subscription. - def get_free_credits(self, instance): # pylint: disable=unused-argument - """Get the available credits""" - # pylint: disable=no-self-use - self.kivy_state.available_credit = 0 - existing_credits = 0 - if existing_credits > 0: - toast( - 'We already have added free credit' - ' for the subscription to your account!') - else: - toast('Credit added to your account!') - # TODO: There is no sc18 screen id is available, - # need to create sc18 for Credits screen inside main.kv - App.get_running_app().root.ids.sc18.ids.cred.text = '{0}'.format( - self.kivy_state.available_credit) - - -class Category(BoxLayout, RectangularElevationBehavior): - """Category class for kivy Ui""" - elevation_normal = .01 - - -class ProductLayout(BoxLayout, RectangularElevationBehavior): - """ProductLayout class for kivy Ui""" - elevation_normal = .01 - - -class PaymentMethodLayout(BoxLayout): - """PaymentMethodLayout class for kivy Ui""" - - -class ListItemWithLabel(OneLineAvatarIconListItem): - """ListItemWithLabel class for kivy Ui""" - - -class RightLabel(IRightBodyTouch, MDLabel): - """RightLabel class for kivy Ui""" diff --git a/src/bitmessagekivy/baseclass/popup.py b/src/bitmessagekivy/baseclass/popup.py deleted file mode 100644 index d2a4c859..00000000 --- a/src/bitmessagekivy/baseclass/popup.py +++ /dev/null @@ -1,231 +0,0 @@ -# pylint: disable=import-error, attribute-defined-outside-init -# pylint: disable=no-member, no-name-in-module, unused-argument, too-few-public-methods - -""" -All the popup are managed here. - -""" -import logging -from datetime import datetime - -from kivy.clock import Clock -from kivy.metrics import dp -from kivy.properties import StringProperty -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.popup import Popup -from kivy.app import App - -from pybitmessage.bitmessagekivy import kivy_helper_search -from pybitmessage.bitmessagekivy.get_platform import platform - -from pybitmessage.bitmessagekivy.baseclass.common import toast - -from pybitmessage.addresses import decodeAddress - -logger = logging.getLogger('default') - - -class AddressChangingLoader(Popup): - """Run a Screen Loader when changing the Identity for kivy UI""" - - def __init__(self, **kwargs): - super(AddressChangingLoader, self).__init__(**kwargs) - Clock.schedule_once(self.dismiss_popup, 0.5) - - def dismiss_popup(self, dt): - """Dismiss popups""" - self.dismiss() - - -class AddAddressPopup(BoxLayout): - """Popup for adding new address to addressbook""" - - validation_dict = { - "missingbm": "The address should start with ''BM-''", - "checksumfailed": "The address is not typed or copied correctly", - "versiontoohigh": "The version number of this address is higher than this" - " software can support. Please upgrade Bitmessage.", - "invalidcharacters": "The address contains invalid characters.", - "ripetooshort": "Some data encoded in the address is too short.", - "ripetoolong": "Some data encoded in the address is too long.", - "varintmalformed": "Some data encoded in the address is malformed." - } - valid = False - - def __init__(self, **kwargs): - super(AddAddressPopup, self).__init__(**kwargs) - - def checkAddress_valid(self, instance): - """Checking address is valid or not""" - my_addresses = ( - App.get_running_app().root.ids.content_drawer.ids.identity_dropdown.values) - add_book = [addr[1] for addr in kivy_helper_search.search_sql( - folder="addressbook")] - entered_text = str(instance.text).strip() - if entered_text in add_book: - text = 'Address is already in the addressbook.' - elif entered_text in my_addresses: - text = 'You can not save your own address.' - elif entered_text: - text = self.addressChanged(entered_text) - - if entered_text in my_addresses or entered_text in add_book: - self.ids.address.error = True - self.ids.address.helper_text = text - elif entered_text and self.valid: - self.ids.address.error = False - elif entered_text: - self.ids.address.error = True - self.ids.address.helper_text = text - else: - self.ids.address.error = True - self.ids.address.helper_text = 'This field is required' - - def checkLabel_valid(self, instance): - """Checking address label is unique or not""" - entered_label = instance.text.strip() - addr_labels = [labels[0] for labels in kivy_helper_search.search_sql( - folder="addressbook")] - if entered_label in addr_labels: - self.ids.label.error = True - self.ids.label.helper_text = 'Label name already exists.' - elif entered_label: - self.ids.label.error = False - else: - self.ids.label.error = True - self.ids.label.helper_text = 'This field is required' - - def _onSuccess(self, addressVersion, streamNumber, ripe): - pass - - def addressChanged(self, addr): - """Address validation callback, performs validation and gives feedback""" - status, addressVersion, streamNumber, ripe = decodeAddress( - str(addr)) - self.valid = status == 'success' - - if self.valid: - text = "Address is valid." - self._onSuccess(addressVersion, streamNumber, ripe) - return text - return self.validation_dict.get(status) - - -class SavedAddressDetailPopup(BoxLayout): - """Pop-up for Saved Address details for kivy UI""" - - address_label = StringProperty() - address = StringProperty() - - def __init__(self, **kwargs): - """Set screen of address detail page""" - super(SavedAddressDetailPopup, self).__init__(**kwargs) - - def checkLabel_valid(self, instance): - """Checking address label is unique of not""" - entered_label = str(instance.text.strip()) - address_list = kivy_helper_search.search_sql(folder="addressbook") - addr_labels = [labels[0] for labels in address_list] - add_dict = dict(address_list) - if self.address and entered_label in addr_labels \ - and self.address != add_dict[entered_label]: - self.ids.add_label.error = True - self.ids.add_label.helper_text = 'label name already exists.' - elif entered_label: - self.ids.add_label.error = False - else: - self.ids.add_label.error = True - self.ids.add_label.helper_text = 'This field is required' - - -class MyaddDetailPopup(BoxLayout): - """MyaddDetailPopup class for kivy Ui""" - - address_label = StringProperty() - address = StringProperty() - - def __init__(self, **kwargs): - """My Address Details screen setting""" - super(MyaddDetailPopup, self).__init__(**kwargs) - - def send_message_from(self): - """Method used to fill from address of composer autofield""" - App.get_running_app().set_navbar_for_composer() - window_obj = App.get_running_app().root.ids - window_obj.id_create.children[1].ids.ti.text = self.address - window_obj.id_create.children[1].ids.composer_dropdown.text = self.address - window_obj.id_create.children[1].ids.txt_input.text = '' - window_obj.id_create.children[1].ids.subject.text = '' - window_obj.id_create.children[1].ids.body.text = '' - window_obj.scr_mngr.current = 'create' - self.parent.parent.parent.dismiss() - - def close_pop(self): - """Pop is Cancelled""" - self.parent.parent.parent.dismiss() - toast('Cancelled') - - -class AppClosingPopup(Popup): - """AppClosingPopup class for kivy Ui""" - - def __init__(self, **kwargs): - super(AppClosingPopup, self).__init__(**kwargs) - - def closingAction(self, text): - """Action on closing window""" - exit_message = "*******************EXITING FROM APPLICATION*******************" - if text == 'Yes': - logger.debug(exit_message) - import shutdown - shutdown.doCleanShutdown() - else: - self.dismiss() - toast(text) - - -class SenderDetailPopup(Popup): - """SenderDetailPopup class for kivy Ui""" - - to_addr = StringProperty() - from_addr = StringProperty() - time_tag = StringProperty() - - def __init__(self, **kwargs): - """this metthod initialized the send message detial popup""" - super(SenderDetailPopup, self).__init__(**kwargs) - - def assignDetail(self, to_addr, from_addr, timeinseconds): - """Detailes assigned""" - self.to_addr = to_addr - self.from_addr = from_addr - time_obj = datetime.fromtimestamp(int(timeinseconds)) - self.time_tag = time_obj.strftime("%d %b %Y, %I:%M %p") - device_type = 2 if platform == 'android' else 1.5 - pop_height = 1.2 * device_type * (self.ids.sd_label.height + self.ids.dismiss_btn.height) - if len(to_addr) > 3: - self.height = pop_height - self.ids.to_addId.size_hint_y = None - self.ids.to_addId.height = 50 - self.ids.to_addtitle.add_widget(ToAddressTitle()) - frmaddbox = ToAddrBoxlayout() - frmaddbox.set_toAddress(to_addr) - self.ids.to_addId.add_widget(frmaddbox) - else: - self.ids.space_1.height = dp(0) - self.ids.space_2.height = dp(0) - self.ids.myadd_popup_box.spacing = dp(8 if platform == 'android' else 3) - self.height = pop_height / 1.2 - - -class ToAddrBoxlayout(BoxLayout): - """ToAddrBoxlayout class for kivy Ui""" - to_addr = StringProperty() - - def set_toAddress(self, to_addr): - """This method is use to set to address""" - self.to_addr = to_addr - - -class ToAddressTitle(BoxLayout): - """ToAddressTitle class for BoxLayout behaviour""" diff --git a/src/bitmessagekivy/baseclass/qrcode.py b/src/bitmessagekivy/baseclass/qrcode.py deleted file mode 100644 index 4c6d99a0..00000000 --- a/src/bitmessagekivy/baseclass/qrcode.py +++ /dev/null @@ -1,34 +0,0 @@ -# pylint: disable=import-error, no-name-in-module, too-few-public-methods - -""" -Generate QRcode of saved addresses in addressbook. -""" - -import logging - -from kivy.app import App -from kivy.uix.screenmanager import Screen -from kivy.properties import StringProperty -from kivy_garden.qrcode import QRCodeWidget - -logger = logging.getLogger('default') - - -class ShowQRCode(Screen): - """ShowQRCode Screen class for kivy Ui""" - address = StringProperty() - - def __init__(self, *args, **kwargs): - """Instantiate kivy state variable""" - super(ShowQRCode, self).__init__(*args, **kwargs) - self.kivy_running_app = App.get_running_app() - - def qrdisplay(self, instance, address): - """Method used for showing QR Code""" - self.ids.qr.clear_widgets() - self.kivy_running_app.set_toolbar_for_QrCode() - self.address = address # used for label - self.ids.qr.add_widget(QRCodeWidget(data=self.address)) - self.ids.qr.children[0].show_border = False - instance.parent.parent.parent.dismiss() - logger.debug('Show QR code') diff --git a/src/bitmessagekivy/baseclass/scan_screen.py b/src/bitmessagekivy/baseclass/scan_screen.py deleted file mode 100644 index 3321a4fa..00000000 --- a/src/bitmessagekivy/baseclass/scan_screen.py +++ /dev/null @@ -1,105 +0,0 @@ -# pylint: disable=no-member, too-many-arguments, too-few-public-methods -# pylint: disable=no-name-in-module, unused-argument, arguments-differ - -""" -QR code Scan Screen used in message composer to get recipient address - -""" - -import os -import logging -import cv2 - -from kivy.clock import Clock -from kivy.lang import Builder -from kivy.properties import ( - BooleanProperty, - ObjectProperty, - StringProperty -) -from kivy.uix.screenmanager import Screen - -from pybitmessage.bitmessagekivy.get_platform import platform - -logger = logging.getLogger('default') - - -class ScanScreen(Screen): - """ScanScreen is for scaning Qr code""" - # pylint: disable=W0212 - camera_available = BooleanProperty(False) - previous_open_screen = StringProperty() - pop_up_instance = ObjectProperty() - - def __init__(self, *args, **kwargs): - """Getting AddressBook Details""" - super(ScanScreen, self).__init__(*args, **kwargs) - self.check_camera() - - def check_camera(self): - """This method is used for checking camera avaibility""" - if platform != "android": - cap = cv2.VideoCapture(0) - is_cam_open = cap.isOpened() - while is_cam_open: - logger.debug('Camera is available!') - self.camera_available = True - break - else: - logger.debug("Camera is not available!") - self.camera_available = False - else: - self.camera_available = True - - def get_screen(self, screen_name, instance=None): - """This method is used for getting previous screen name""" - self.previous_open_screen = screen_name - if screen_name != 'composer': - self.pop_up_instance = instance - - def on_pre_enter(self): - """ - on_pre_enter works little better on android - It affects screen transition on linux - """ - if not self.children: - tmp = Builder.load_file( - os.path.join( - os.path.dirname(os.path.dirname(__file__)), "kv", "{}.kv").format("scanner") - ) - self.add_widget(tmp) - if platform == "android": - Clock.schedule_once(self.start_camera, 0) - - def on_enter(self): - """ - on_enter works better on linux - It creates a black screen on android until camera gets loaded - """ - if platform != "android": - Clock.schedule_once(self.start_camera, 0) - - def on_leave(self): - """This method will call on leave""" - Clock.schedule_once(self.stop_camera, 0) - - def start_camera(self, *args): - """Its used for starting camera for scanning qrcode""" - # pylint: disable=attribute-defined-outside-init - self.xcam = self.children[0].ids.zbarcam.ids.xcamera - if platform == "android": - self.xcam.play = True - else: - Clock.schedule_once(self.open_cam, 0) - - def stop_camera(self, *args): - """Its used for stop the camera""" - self.xcam.play = False - if platform != "android": - self.xcam._camera._device.release() - - def open_cam(self, *args): - """It will open up the camera""" - if not self.xcam._camera._device.isOpened(): - self.xcam._camera._device.open(self.xcam._camera._index) - self.xcam.play = True diff --git a/src/bitmessagekivy/baseclass/sent.py b/src/bitmessagekivy/baseclass/sent.py deleted file mode 100644 index 59db0ab9..00000000 --- a/src/bitmessagekivy/baseclass/sent.py +++ /dev/null @@ -1,47 +0,0 @@ -# pylint: disable=import-error, attribute-defined-outside-init, too-many-arguments -# pylint: disable=no-member, no-name-in-module, unused-argument, too-few-public-methods - -""" - Sent screen; All sent message managed here. -""" - -from kivy.properties import StringProperty, ListProperty -from kivy.uix.screenmanager import Screen -from kivy.app import App - -from pybitmessage.bitmessagekivy.baseclass.common import kivy_state_variables - - -class Sent(Screen): - """Sent Screen class for kivy UI""" - - queryreturn = ListProperty() - account = StringProperty() - has_refreshed = True - no_search_res_found = "No search result found" - label_str = "Yet no message for this account!" - - def __init__(self, *args, **kwargs): - """Association with the screen""" - super(Sent, self).__init__(*args, **kwargs) - self.kivy_state = kivy_state_variables() - - if self.kivy_state.selected_address == '': - if App.get_running_app().identity_list: - self.kivy_state.selected_address = App.get_running_app().identity_list[0] - - def init_ui(self, dt=0): - """Clock Schdule for method sent accounts""" - self.loadSent() - print(dt) - - def set_defaultAddress(self): - """Set default address""" - if self.kivy_state.selected_address == "": - if self.kivy_running_app.identity_list: - self.kivy_state.selected_address = self.kivy_running_app.identity_list[0] - - def loadSent(self, where="", what=""): - """Load Sent list for Sent messages""" - self.set_defaultAddress() - self.account = self.kivy_state.selected_address diff --git a/src/bitmessagekivy/baseclass/settings.py b/src/bitmessagekivy/baseclass/settings.py deleted file mode 100644 index 1ceb35ee..00000000 --- a/src/bitmessagekivy/baseclass/settings.py +++ /dev/null @@ -1,10 +0,0 @@ -# pylint: disable=unused-argument, no-name-in-module, too-few-public-methods -""" -Settings screen UI -""" - -from kivy.uix.screenmanager import Screen - - -class Setting(Screen): - """Setting Screen for kivy Ui""" diff --git a/src/bitmessagekivy/baseclass/trash.py b/src/bitmessagekivy/baseclass/trash.py deleted file mode 100644 index eb62fdaa..00000000 --- a/src/bitmessagekivy/baseclass/trash.py +++ /dev/null @@ -1,33 +0,0 @@ -# pylint: disable=unused-argument, consider-using-f-string, import-error, attribute-defined-outside-init -# pylint: disable=unnecessary-comprehension, no-member, no-name-in-module, too-few-public-methods - -""" - Trash screen -""" - -from kivy.properties import ( - ListProperty, - StringProperty -) -from kivy.uix.screenmanager import Screen -from kivy.app import App - -from pybitmessage.bitmessagekivy.baseclass.common import kivy_state_variables - - -class Trash(Screen): - """Trash Screen class for kivy Ui""" - - trash_messages = ListProperty() - has_refreshed = True - delete_index = None - table_name = StringProperty() - no_msg_found_str = "Yet no trashed message for this account!" - - def __init__(self, *args, **kwargs): - """Trash method, delete sent message and add in Trash""" - super(Trash, self).__init__(*args, **kwargs) - self.kivy_state = kivy_state_variables() - if self.kivy_state.selected_address == '': - if App.get_running_app().identity_list: - self.kivy_state.selected_address = App.get_running_app().identity_list[0] diff --git a/src/bitmessagekivy/get_platform.py b/src/bitmessagekivy/get_platform.py deleted file mode 100644 index 654b31f4..00000000 --- a/src/bitmessagekivy/get_platform.py +++ /dev/null @@ -1,31 +0,0 @@ -# pylint: disable=no-else-return, too-many-return-statements - -"""To check the platform""" - -from sys import platform as _sys_platform -from os import environ - - -def _get_platform(): - kivy_build = environ.get("KIVY_BUILD", "") - if kivy_build in {"android", "ios"}: - return kivy_build - elif "P4A_BOOTSTRAP" in environ: - return "android" - elif "ANDROID_ARGUMENT" in environ: - return "android" - elif _sys_platform in ("win32", "cygwin"): - return "win" - elif _sys_platform == "darwin": - return "macosx" - elif _sys_platform.startswith("linux"): - return "linux" - elif _sys_platform.startswith("freebsd"): - return "linux" - return "unknown" - - -platform = _get_platform() - -if platform not in ("android", "unknown"): - environ["KIVY_CAMERA"] = "opencv" diff --git a/src/bitmessagekivy/identiconGeneration.py b/src/bitmessagekivy/identiconGeneration.py deleted file mode 100644 index 2e2f2e93..00000000 --- a/src/bitmessagekivy/identiconGeneration.py +++ /dev/null @@ -1,80 +0,0 @@ -""" -Core classes for loading images and converting them to a Texture. -The raw image data can be keep in memory for further access -""" -import hashlib -from io import BytesIO - -from PIL import Image -from kivy.core.image import Image as CoreImage -from kivy.uix.image import Image as kiImage - - -# constants -RESOLUTION = 300, 300 -V_RESOLUTION = 7, 7 -BACKGROUND_COLOR = 255, 255, 255, 255 -MODE = "RGB" - - -def generate(Generate_string=None): - """Generating string""" - hash_string = generate_hash(Generate_string) - color = random_color(hash_string) - image = Image.new(MODE, V_RESOLUTION, BACKGROUND_COLOR) - image = generate_image(image, color, hash_string) - image = image.resize(RESOLUTION, 0) - data = BytesIO() - image.save(data, format='png') - data.seek(0) - # yes you actually need this - im = CoreImage(BytesIO(data.read()), ext='png') - beeld = kiImage() - # only use this line in first code instance - beeld.texture = im.texture - return beeld - - -def generate_hash(string): - """Generating hash""" - try: - # make input case insensitive - string = str.lower(string) - hash_object = hashlib.md5( # nosec B324, B303 - str.encode(string)) - print(hash_object.hexdigest()) - # returned object is a hex string - return hash_object.hexdigest() - except IndexError: - print("Error: Please enter a string as an argument.") - - -def random_color(hash_string): - """Getting random color""" - # remove first three digits from hex string - split = 6 - rgb = hash_string[:split] - split = 2 - r = rgb[:split] - g = rgb[split:2 * split] - b = rgb[2 * split:3 * split] - color = (int(r, 16), int(g, 16), int(b, 16), 0xFF) - return color - - -def generate_image(image, color, hash_string): - """Generating images""" - hash_string = hash_string[6:] - lower_x = 1 - lower_y = 1 - upper_x = int(V_RESOLUTION[0] / 2) + 1 - upper_y = V_RESOLUTION[1] - 1 - limit_x = V_RESOLUTION[0] - 1 - index = 0 - for x in range(lower_x, upper_x): - for y in range(lower_y, upper_y): - if int(hash_string[index], 16) % 2 == 0: - image.putpixel((x, y), color) - image.putpixel((limit_x - x, y), color) - index = index + 1 - return image diff --git a/src/bitmessagekivy/kivy_helper_search.py b/src/bitmessagekivy/kivy_helper_search.py deleted file mode 100644 index c48ca3ad..00000000 --- a/src/bitmessagekivy/kivy_helper_search.py +++ /dev/null @@ -1,71 +0,0 @@ -""" -Sql queries for bitmessagekivy -""" -from pybitmessage.helper_sql import sqlQuery - - -def search_sql( - xAddress="toaddress", account=None, folder="inbox", where=None, - what=None, unreadOnly=False, start_indx=0, end_indx=20): - # pylint: disable=too-many-arguments, too-many-branches - """Method helping for searching mails""" - if what is not None and what != "": - what = "%" + what + "%" - else: - what = None - if folder in ("sent", "draft"): - sqlStatementBase = ( - '''SELECT toaddress, fromaddress, subject, message, status,''' - ''' ackdata, senttime FROM sent ''' - ) - elif folder == "addressbook": - sqlStatementBase = '''SELECT label, address From addressbook ''' - else: - sqlStatementBase = ( - '''SELECT folder, msgid, toaddress, message, fromaddress,''' - ''' subject, received, read FROM inbox ''' - ) - sqlStatementParts = [] - sqlArguments = [] - if account is not None: - if xAddress == 'both': - sqlStatementParts.append("(fromaddress = ? OR toaddress = ?)") - sqlArguments.append(account) - sqlArguments.append(account) - else: - sqlStatementParts.append(xAddress + " = ? ") - sqlArguments.append(account) - if folder != "addressbook": - if folder is not None: - if folder == "new": - folder = "inbox" - unreadOnly = True - sqlStatementParts.append("folder = ? ") - sqlArguments.append(folder) - else: - sqlStatementParts.append("folder != ?") - sqlArguments.append("trash") - if what is not None: - for colmns in where: - if len(where) > 1: - if where[0] == colmns: - filter_col = "(%s LIKE ?" % (colmns) - else: - filter_col += " or %s LIKE ? )" % (colmns) - else: - filter_col = "%s LIKE ?" % (colmns) - sqlArguments.append(what) - sqlStatementParts.append(filter_col) - if unreadOnly: - sqlStatementParts.append("read = 0") - if sqlStatementParts: - sqlStatementBase += "WHERE " + " AND ".join(sqlStatementParts) - if folder in ("sent", "draft"): - sqlStatementBase += \ - "ORDER BY senttime DESC limit {0}, {1}".format( - start_indx, end_indx) - elif folder == "inbox": - sqlStatementBase += \ - "ORDER BY received DESC limit {0}, {1}".format( - start_indx, end_indx) - return sqlQuery(sqlStatementBase, sqlArguments) diff --git a/src/bitmessagekivy/kivy_state.py b/src/bitmessagekivy/kivy_state.py deleted file mode 100644 index 42051ff9..00000000 --- a/src/bitmessagekivy/kivy_state.py +++ /dev/null @@ -1,42 +0,0 @@ -# pylint: disable=too-many-instance-attributes, too-few-public-methods - -""" -Kivy State variables are assigned here, they are separated from state.py -================================= -""" - -import os -import threading - - -class KivyStateVariables(object): - """This Class hold all the kivy state variables""" - - def __init__(self): - self.selected_address = '' - self.navinstance = None - self.mail_id = 0 - self.my_address_obj = None - self.detail_page_type = None - self.ackdata = None - self.status = None - self.screen_density = None - self.msg_counter_objs = None - self.check_sent_acc = None - self.sent_count = 0 - self.inbox_count = 0 - self.trash_count = 0 - self.draft_count = 0 - self.all_count = 0 - self.searching_text = '' - self.search_screen = '' - self.send_draft_mail = None - self.is_allmail = False - self.in_composer = False - self.available_credit = 0 - self.in_sent_method = False - self.in_search_mode = False - self.image_dir = os.path.abspath(os.path.join('images', 'kivy')) - self.kivyui_ready = threading.Event() - self.file_manager = None - self.manager_open = False diff --git a/src/bitmessagekivy/kv/addressbook.kv b/src/bitmessagekivy/kv/addressbook.kv deleted file mode 100644 index 73b4c1ef..00000000 --- a/src/bitmessagekivy/kv/addressbook.kv +++ /dev/null @@ -1,26 +0,0 @@ -: - name: 'addressbook' - BoxLayout: - orientation: 'vertical' - spacing: dp(5) - SearchBar: - id: address_search - GridLayout: - id: identi_tag - padding: [20, 0, 0, 5] - cols: 1 - size_hint_y: None - height: self.minimum_height - MDLabel: - id: tag_label - text: '' - font_style: 'Subtitle2' - BoxLayout: - orientation:'vertical' - ScrollView: - id: scroll_y - do_scroll_x: False - MDList: - id: ml - Loader: - ComposerButton: \ No newline at end of file diff --git a/src/bitmessagekivy/kv/allmails.kv b/src/bitmessagekivy/kv/allmails.kv deleted file mode 100644 index f1b9387e..00000000 --- a/src/bitmessagekivy/kv/allmails.kv +++ /dev/null @@ -1,25 +0,0 @@ -: - name: 'allmails' - BoxLayout: - orientation: 'vertical' - spacing: dp(5) - GridLayout: - id: identi_tag - padding: [20, 20, 0, 5] - spacing: dp(5) - cols: 1 - size_hint_y: None - height: self.minimum_height - MDLabel: - id: tag_label - text: '' - font_style: 'Subtitle2' - BoxLayout: - orientation:'vertical' - ScrollView: - id: scroll_y - do_scroll_x: False - MDList: - id: ml - Loader: - ComposerButton: \ No newline at end of file diff --git a/src/bitmessagekivy/kv/chat.kv b/src/bitmessagekivy/kv/chat.kv deleted file mode 100644 index e21ed503..00000000 --- a/src/bitmessagekivy/kv/chat.kv +++ /dev/null @@ -1,82 +0,0 @@ -#:import C kivy.utils.get_color_from_hex -#:import MDTextField kivymd.uix.textfield.MDTextField -: - name: 'chat' - BoxLayout: - orientation: 'vertical' - canvas.before: - Color: - rgba: 1,1,1,1 - Rectangle: - pos: self.pos - size: self.size - ScrollView: - Label: - id: chat_logs - text: '' - color: C('#101010') - text_size: (self.width, None) - halign: 'left' - valign: 'top' - padding: (0, 0) # fixed in Kivy 1.8.1 - size_hint: (1, None) - height: self.texture_size[1] - markup: True - font_size: sp(20) - MDBoxLayout: - size_hint_y: None - spacing:5 - orientation: 'horizontal' - pos_hint: {'center_y': 1, 'center_x': 1} - halign: 'right' - pos_hint: {'left': 0} - pos_hint: {'x':.8} - height: dp(50) + self.minimum_height - MDFillRoundFlatButton: - text: app.tr._("First message") - opposite_colors: True - pos_hint: {'center_x':0.8,'center_y':0.7} - - - - BoxLayout: - height: 50 - orientation: 'horizontal' - padding: 0 - size_hint: (1, None) - - MDTextField: - id:'id_message_body' - hint_text: 'Empty field' - icon_left: "message" - hint_text: "please enter your text" - mode: "fill" - fill_color: 1/255, 144/255, 254/255, 0.1 - multiline: True - font_color_normal: 0, 0, 0, .4 - icon_right: 'grease-pencil' - icon_right_color: app.theme_cls.primary_light - pos_hint: {'center_x':0.2,'center_y':0.7} - - MDIconButton: - id: file_manager - icon: "attachment" - opposite_colors: True - on_release: app.file_manager_open() - theme_text_color: "Custom" - text_color: app.theme_cls.primary_color - - MDIconButton: - icon: 'camera' - opposite_colors: True - theme_text_color: "Custom" - text_color: app.theme_cls.primary_color - MDIconButton: - id: send_message - icon: "send" - # x: root.parent.x + dp(10) - # pos_hint: {"top": 1, 'left': 1} - color: [1,0,0,1] - on_release: app.rest_default_avatar_img() - theme_text_color: "Custom" - text_color: app.theme_cls.primary_color diff --git a/src/bitmessagekivy/kv/chat_list.kv b/src/bitmessagekivy/kv/chat_list.kv deleted file mode 100644 index e59c32d7..00000000 --- a/src/bitmessagekivy/kv/chat_list.kv +++ /dev/null @@ -1,58 +0,0 @@ -: - name: 'chlist' - canvas.before: - Color: - rgba: 1,1,1,1 - Rectangle: - pos: self.pos - size: self.size - MDTabs: - id: chat_panel - tab_display_mode:'text' - - Tab: - text: app.tr._("Chats") - BoxLayout: - id: chat_box - orientation: 'vertical' - ScrollView: - id: scroll_y - do_scroll_x: False - MDList: - id: ml - MDLabel: - font_style: 'Caption' - theme_text_color: 'Primary' - text: app.tr._('No Chat') - halign: 'center' - size_hint_y: None - bold: True - valign: 'top' - # OneLineAvatarListItem: - # text: "Single-line item with avatar" - # divider: None - # _no_ripple_effect: True - # ImageLeftWidget: - # source: './images/text_images/A.png' - # OneLineAvatarListItem: - # text: "Single-line item with avatar" - # divider: None - # _no_ripple_effect: True - # ImageLeftWidget: - # source: './images/text_images/B.png' - # OneLineAvatarListItem: - # text: "Single-line item with avatar" - # divider: None - # _no_ripple_effect: True - # ImageLeftWidget: - # source: './images/text_images/A.png' - Tab: - text: app.tr._("Contacts") - BoxLayout: - id: contact_box - orientation: 'vertical' - ScrollView: - id: scroll_y - do_scroll_x: False - MDList: - id: ml diff --git a/src/bitmessagekivy/kv/chat_room.kv b/src/bitmessagekivy/kv/chat_room.kv deleted file mode 100644 index 40843c47..00000000 --- a/src/bitmessagekivy/kv/chat_room.kv +++ /dev/null @@ -1,45 +0,0 @@ -#:import C kivy.utils.get_color_from_hex - -: - name: 'chroom' - BoxLayout: - orientation: 'vertical' - canvas.before: - Color: - rgba: 1,1,1,1 - Rectangle: - pos: self.pos - size: self.size - ScrollView: - Label: - id: chat_logs - text: '' - color: C('#101010') - text_size: (self.width, None) - halign: 'left' - valign: 'top' - padding: (0, 0) # fixed in Kivy 1.8.1 - size_hint: (1, None) - height: self.texture_size[1] - markup: True - font_size: sp(20) - BoxLayout: - height: 50 - orientation: 'horizontal' - padding: 0 - size_hint: (1, None) - - TextInput: - id: message - size_hint: (1, 1) - multiline: False - font_size: sp(20) - on_text_validate: root.send_msg() - - MDRaisedButton: - text: app.tr._("Send") - elevation_normal: 2 - opposite_colors: True - size_hint: (0.3, 1) - pos_hint: {"center_x": .5} - on_press: root.send_msg() diff --git a/src/bitmessagekivy/kv/common_widgets.kv b/src/bitmessagekivy/kv/common_widgets.kv deleted file mode 100644 index 275bd12c..00000000 --- a/src/bitmessagekivy/kv/common_widgets.kv +++ /dev/null @@ -1,62 +0,0 @@ -: - source: app.image_path +('/down-arrow.png' if self.parent.is_open == True else '/right-arrow.png') - size: 15, 15 - x: self.parent.x + self.parent.width - self.width - 5 - y: self.parent.y + self.parent.height/2 - self.height + 5 - -: - # id: search_bar - size_hint_y: None - height: self.minimum_height - - MDIconButton: - icon: 'magnify' - - MDTextField: - id: search_field - hint_text: 'Search' - on_text: app.searchQuery(self) - canvas.before: - Color: - rgba: (0,0,0,1) - -: - id: spinner - size_hint: None, None - size: dp(46), dp(46) - pos_hint: {'center_x': 0.5, 'center_y': 0.5} - active: False - -: - size_hint_y: None - height: dp(56) - spacing: '10dp' - pos_hint: {'center_x':0.45, 'center_y': .1} - - Widget: - - MDFloatingActionButton: - icon: 'plus' - opposite_colors: True - elevation_normal: 8 - md_bg_color: [0.941, 0, 0,1] - on_press: app.set_screen('create') - on_press: app.clear_composer() - - - -: - size_hint: None, None - size: dp(36), dp(48) - pos_hint: {'center_x': .95, 'center_y': .4} - on_active: app.root.ids.id_myaddress.toggleAction(self) - -: - canvas: - Color: - id: set_clr - # rgba: 0.5, 0.5, 0.5, 0.5 - rgba: 0,0,0,0 - Rectangle: #woohoo!!! - size: self.size - pos: self.pos \ No newline at end of file diff --git a/src/bitmessagekivy/kv/credits.kv b/src/bitmessagekivy/kv/credits.kv deleted file mode 100644 index 1680d6f0..00000000 --- a/src/bitmessagekivy/kv/credits.kv +++ /dev/null @@ -1,28 +0,0 @@ -: - name: 'credits' - ScrollView: - do_scroll_x: False - BoxLayout: - size_hint_y: None - orientation: 'vertical' - OneLineListTitle: - id: cred - text: app.tr._("Available Credits") - divider: None - theme_text_color: 'Primary' - _no_ripple_effect: True - long_press_time: 1 - - OneLineListTitle: - id: cred - text: app.tr._(root.available_credits) - divider: None - font_style: 'H5' - theme_text_color: 'Primary' - _no_ripple_effect: True - long_press_time: 1 - AnchorLayout: - MDRaisedButton: - height: dp(38) - text: app.tr._("+Add more credits") - on_press: app.set_screen('payment') \ No newline at end of file diff --git a/src/bitmessagekivy/kv/draft.kv b/src/bitmessagekivy/kv/draft.kv deleted file mode 100644 index 56682d2b..00000000 --- a/src/bitmessagekivy/kv/draft.kv +++ /dev/null @@ -1,23 +0,0 @@ -: - name: 'draft' - BoxLayout: - orientation: 'vertical' - spacing: dp(5) - GridLayout: - id: identi_tag - padding: [20, 20, 0, 5] - cols: 1 - size_hint_y: None - height: self.minimum_height - MDLabel: - id: tag_label - text: '' - font_style: 'Subtitle2' - BoxLayout: - orientation:'vertical' - ScrollView: - id: scroll_y - do_scroll_x: False - MDList: - id: ml - ComposerButton: \ No newline at end of file diff --git a/src/bitmessagekivy/kv/inbox.kv b/src/bitmessagekivy/kv/inbox.kv deleted file mode 100644 index b9cc8566..00000000 --- a/src/bitmessagekivy/kv/inbox.kv +++ /dev/null @@ -1,39 +0,0 @@ -: - name: 'inbox' - #transition: NoTransition() - BoxLayout: - orientation: 'vertical' - spacing: dp(5) - SearchBar: - id:inbox_search - GridLayout: - id: identi_tag - padding: [20, 0, 0, 5] - cols: 1 - size_hint_y: None - height: self.minimum_height - MDLabel: - id: tag_label - text: '' - font_style: 'Subtitle2' - #FloatLayout: - # MDScrollViewRefreshLayout: - # id: refresh_layout - # refresh_callback: root.refresh_callback - # root_layout: root.set_root_layout() - # MDList: - # id: ml - BoxLayout: - orientation:'vertical' - ScrollView: - id: scroll_y - do_scroll_x: False - MDList: - id: ml - Loader: - ComposerButton: - -: - size_hint:(None, None) - font_style: 'Caption' - halign: 'center' diff --git a/src/bitmessagekivy/kv/login.kv b/src/bitmessagekivy/kv/login.kv deleted file mode 100644 index d3e2f7f9..00000000 --- a/src/bitmessagekivy/kv/login.kv +++ /dev/null @@ -1,264 +0,0 @@ -#:import SlideTransition kivy.uix.screenmanager.SlideTransition -: - name:"login" - BoxLayout: - orientation: "vertical" - - #buttons-area-outer - BoxLayout: - size_hint_y: .53 - canvas: - Color: - rgba: 1,1,1,1 - Rectangle: - pos: self.pos - size: self.size - - ScreenManager: - id: check_screenmgr - Screen: - name: "check_screen" - BoxLayout: - orientation: "vertical" - padding: 0, dp(5), 0, dp(5) - spacing: dp(5) - - #label area - AnchorLayout: - size_hint_y: None - height: dp(50) - MDLabel: - text: app.tr._("Select method to make an address:") - bold: True - halign: "center" - theme_text_color: "Custom" - text_color: .4,.4,.4,1 - - #upper-checkbor-area - AnchorLayout: - size_hint_y: None - height: dp(40) - BoxLayout: - size_hint_x: None - width: self.minimum_width - - #check-container - AnchorLayout: - size_hint_x: None - width: dp(40) - Check: - active: True - - #text-container - AnchorLayout: - size_hint_x: None - width: dp(200) - MDLabel: - text: app.tr._("Pseudorandom Generator") - - AnchorLayout: - size_hint_y: None - height: dp(40) - BoxLayout: - size_hint_x: None - width: self.minimum_width - - #check-container - AnchorLayout: - size_hint_x: None - width: dp(40) - Check: - - #text-container - AnchorLayout: - size_hint_x: None - width: dp(200) - MDLabel: - text: app.tr._("Passphrase (deterministic) Generator") - AnchorLayout: - MDFillRoundFlatIconButton: - icon: "chevron-double-right" - text: app.tr._("Proceed Next") - on_release: - app.set_screen('random') - on_press: - app.root.ids.id_newidentity.reset_address_label() - - #info-area-outer - BoxLayout: - size_hint_y: .47 - padding: dp(7) - InfoLayout: - orientation:"vertical" - padding: 0, dp(5), 0, dp(5) - canvas: - Color: - rgba:1,1,1,1 - Rectangle: - pos: self.pos - size: self.size - Color: - rgba: app.theme_cls.primary_color - Line: - rounded_rectangle: (self.pos[0]+4, self.pos[1]+4, self.width-8,self.height-8, 10, 10, 10, 10, 50) - width: dp(1) - ScreenManager: - id: info_screenmgr - - Screen: - name: "info1" - ScrollView: - bar_width:0 - do_scroll_x: False - - BoxLayout: - orientation: "vertical" - size_hint_y: None - height: self.minimum_height - - #note area - ContentHead: - section_name: "NOTE:" - ContentBody: - section_text: ("You may generate addresses by using either random numbers or by using a pass-phrase.If you use a pass-phrase, the address is called a deterministic address. The Random Number option is selected by default but deterministic addresses may have several pros and cons.") - - - #pros area - ContentHead: - section_name: "PROS:" - ContentBody: - section_text: ("You can re-create your addresses on any computer from memory you need-not-to worry about backing up your keys.dat file as long as you can remember your pass-phrase.") - - #cons area - ContentHead: - section_name: "CONS:" - ContentBody: - section_text: ("You must remember (or write down) your address version number and the stream number along with your pass-phrase.If you choose a weak pass-phrase and someone on the internet can brute-force it, they can read your messages and send messages as you.") - -: - name:"random" - ScrollView: - id:add_random_bx - -: - orientation: "vertical" - #buttons-area-outer - BoxLayout: - orientation: "vertical" - # padding: 0, dp(5), 0, dp(5) - # spacing: dp(5) - size_hint_y: .53 - canvas: - Color: - rgba: 1,1,1,1 - Rectangle: - pos: self.pos - size: self.size - - #label area - AnchorLayout: - size_hint_y: None - height: dp(50) - MDLabel: - text: app.tr._("Enter a label to generate address for:") - bold: True - halign: "center" - theme_text_color: "Custom" - text_color: .4,.4,.4,1 - - AnchorLayout: - size_hint_y: None - height: dp(40) - MDTextField: - id:lab - hint_text: "Label" - required: True - size_hint_x: None - width: dp(190) - helper_text_mode: "on_error" - # helper_text: "Please enter your label name" - on_text: app.root.ids.id_newidentity.add_validation(self) - canvas.before: - Color: - rgba: (0,0,0,1) - - AnchorLayout: - MDFillRoundFlatIconButton: - icon: "chevron-double-right" - text: app.tr._("Proceed Next") - on_release: app.root.ids.id_newidentity.generateaddress() - - Widget: - - #info-area-outer - BoxLayout: - size_hint_y: .47 - padding: dp(7) - InfoLayout: - orientation:"vertical" - padding: 0, dp(5), 0, dp(5) - canvas: - Color: - rgba:1,1,1,1 - Rectangle: - pos: self.pos - size: self.size - Color: - rgba: app.theme_cls.primary_color - Line: - rounded_rectangle: (self.pos[0]+4, self.pos[1]+4, self.width-8,self.height-8, 10, 10, 10, 10, 50) - width: dp(1) - ScreenManager: - id: info_screenmgr - - Screen: - name: "info2" - ScrollView: - bar_width:0 - do_scroll_x: False - - BoxLayout: - orientation: "vertical" - size_hint_y: None - height: self.minimum_height - - #note area - ContentHead: - section_name: "NOTE:" - ContentBody: - section_text: ("Here you may generate as many addresses as you like..Indeed creating and abandoning addresses is not encouraged.") - -: - group: 'group' - size_hint: None, None - size: dp(48), dp(48) - -: - section_name: "" - orientation: "vertical" - size_hint_y: None - height: dp(50) - padding: dp(20), 0, 0, 0 - Widget: - size_hint_y: None - height: dp(25) - MDLabel: - theme_text_color: "Custom" - text_color: .1,.1,.1,.9 - text: app.tr._(root.section_name) - bold: True - font_style: "Button" - -: - section_text: "" - size_hint_y: None - height: self.minimum_height - padding: dp(50), 0, dp(10), 0 - - MDLabel: - size_hint_y: None - height: self.texture_size[1]+dp(10) - theme_text_color: "Custom" - text_color: 0.3,0.3,0.3,1 - font_style: "Body1" - text: app.tr._(root.section_text) diff --git a/src/bitmessagekivy/kv/maildetail.kv b/src/bitmessagekivy/kv/maildetail.kv deleted file mode 100644 index e98b8661..00000000 --- a/src/bitmessagekivy/kv/maildetail.kv +++ /dev/null @@ -1,87 +0,0 @@ -: - name: 'mailDetail' - ScrollView: - do_scroll_x: False - BoxLayout: - size_hint_y: None - orientation: 'vertical' - # height: dp(bod.height) + self.minimum_height - height: self.minimum_height - padding: dp(10) - # MDLabel: - # size_hint_y: None - # id: subj - # text: root.subject - # theme_text_color: 'Primary' - # halign: 'left' - # font_style: 'H5' - # height: dp(40) - # on_touch_down: root.allclick(self) - OneLineListTitle: - id: subj - text: app.tr._(root.subject) - divider: None - font_style: 'H5' - theme_text_color: 'Primary' - _no_ripple_effect: True - long_press_time: 1 - TwoLineAvatarIconListItem: - id: subaft - text: app.tr._(root.from_addr) - secondary_text: app.tr._('to ' + root.to_addr) - divider: None - on_press: root.detailedPopup() - BadgeText: - size_hint:(None, None) - size:[120, 140] if app.app_platform == 'android' else [64, 80] - text: app.tr._(root.time_tag) - halign:'center' - font_style:'Caption' - pos_hint: {'center_y': .8} - _txt_right_pad: dp(70) - font_size: '11sp' - MDChip: - size_hint: (.16 if app.app_platform == 'android' else .08 , None) - text: app.tr._(root.page_type) - icon: '' - text_color: (1,1,1,1) - pos_hint: {'center_x': .91 if app.app_platform == 'android' else .95, 'center_y': .3} - radius: [8] - height: self.parent.height/4 - AvatarSampleWidget: - source: root.avatarImg - MDLabel: - text: root.status - disabled: True - font_style: 'Body2' - halign:'left' - padding_x: 20 - # MDLabel: - # id: bod - # font_style: 'Subtitle2' - # theme_text_color: 'Primary' - # text: root.message - # halign: 'left' - # height: self.texture_size[1] - MyMDTextField: - id: bod - size_hint_y: None - font_style: 'Subtitle2' - text: root.message - multiline: True - readonly: True - line_color_normal: [0,0,0,0] - _current_line_color: [0,0,0,0] - line_color_focus: [0,0,0,0] - markup: True - font_size: '15sp' - canvas.before: - Color: - rgba: (0,0,0,1) - Loader: - - -: - canvas.before: - Color: - rgba: (0,0,0,1) diff --git a/src/bitmessagekivy/kv/msg_composer.kv b/src/bitmessagekivy/kv/msg_composer.kv deleted file mode 100644 index 13db4f4e..00000000 --- a/src/bitmessagekivy/kv/msg_composer.kv +++ /dev/null @@ -1,161 +0,0 @@ -: - name: 'create' - Loader: - - -: - ScrollView: - id: id_scroll - BoxLayout: - orientation: 'vertical' - size_hint_y: None - height: self.minimum_height + 3 * self.parent.height/5 - padding: dp(20) - spacing: 15 - BoxLayout: - orientation: 'vertical' - MDTextField: - id: ti - size_hint_y: None - hint_text: 'Type or Select sender address' - icon_right: 'account' - icon_right_color: app.theme_cls.primary_light - font_size: '15sp' - multiline: False - required: True - height: 100 - current_hint_text_color: 0,0,0,0.5 - helper_text_mode: "on_error" - canvas.before: - Color: - rgba: (0,0,0,1) - - - BoxLayout: - size_hint_y: None - height: dp(40) - IdentitySpinner: - id: composer_dropdown - background_color: app.theme_cls.primary_dark - values: app.identity_list - on_text: root.auto_fill_fromaddr() if self.text != 'Select' else '' - option_cls: Factory.get("ComposerSpinnerOption") - background_normal: '' - background_color: app.theme_cls.primary_color - color: color_font - font_size: '13.5sp' - ArrowImg: - - - RelativeLayout: - orientation: 'horizontal' - BoxLayout: - orientation: 'vertical' - txt_input: txt_input - rv: rv - size : (890, 60) - MyTextInput: - id: txt_input - size_hint_y: None - font_size: '15sp' - color: color_font - current_hint_text_color: 0,0,0,0.5 - height: 100 - hint_text: app.tr._('Type or Scan QR code for recipients address') - canvas.before: - Color: - rgba: (0,0,0,1) - - RV: - id: rv - MDIconButton: - icon: 'qrcode-scan' - pos_hint: {'center_x': 0.95, 'y': 0.6} - on_release: - if root.is_camara_attached(): app.set_screen('scanscreen') - else: root.camera_alert() - on_press: - app.root.ids.id_scanscreen.get_screen('composer') - - MyMDTextField: - id: subject - hint_text: 'Subject' - height: 100 - font_size: '15sp' - icon_right: 'notebook-outline' - icon_right_color: app.theme_cls.primary_light - current_hint_text_color: 0,0,0,0.5 - font_color_normal: 0, 0, 0, 1 - size_hint_y: None - required: True - multiline: False - helper_text_mode: "on_focus" - canvas.before: - Color: - rgba: (0,0,0,1) - - ScrollView: - id: scrlv - MDTextField: - id: body - hint_text: 'Body' - mode: "fill" - fill_color: 1/255, 144/255, 254/255, 0.1 - multiline: True - font_color_normal: 0, 0, 0, .4 - icon_right: 'grease-pencil' - icon_right_color: app.theme_cls.primary_light - size_hint: 1, 1 - height: app.window_size[1]/4 - canvas.before: - Color: - rgba: 125/255, 125/255, 125/255, 1 - BoxLayout: - spacing:50 - -: - readonly: False - multiline: False - - -: - # Draw a background to indicate selection - color: 0,0,0,1 - canvas.before: - Color: - rgba: app.theme_cls.primary_dark if self.selected else (1, 1, 1, 0) - Rectangle: - pos: self.pos - size: self.size - -: - canvas: - Color: - rgba: 0,0,0,.2 - - Line: - rectangle: self.x +1 , self.y, self.width - 2, self.height -2 - bar_width: 10 - scroll_type:['bars'] - viewclass: 'SelectableLabel' - SelectableRecycleBoxLayout: - default_size: None, dp(20) - default_size_hint: 1, None - size_hint_y: None - height: self.minimum_height - orientation: 'vertical' - multiselect: False - - -: - canvas.before: - Color: - rgba: (0,0,0,1) - - - -: - font_size: '13.5sp' - background_normal: 'atlas://data/images/defaulttheme/textinput_active' - background_color: app.theme_cls.primary_color - color: color_font diff --git a/src/bitmessagekivy/kv/myaddress.kv b/src/bitmessagekivy/kv/myaddress.kv deleted file mode 100644 index 71dde54a..00000000 --- a/src/bitmessagekivy/kv/myaddress.kv +++ /dev/null @@ -1,33 +0,0 @@ -: - name: 'myaddress' - BoxLayout: - id: main_box - orientation: 'vertical' - spacing: dp(5) - SearchBar: - id: search_bar - GridLayout: - id: identi_tag - padding: [20, 0, 0, 5] - cols: 1 - size_hint_y: None - height: self.minimum_height - MDLabel: - id: tag_label - text: app.tr._('My Addresses') - font_style: 'Subtitle2' - FloatLayout: - MDScrollViewRefreshLayout: - id: refresh_layout - refresh_callback: root.refresh_callback - root_layout: root - MDList: - id: ml - Loader: - - -: - size_hint: None, None - size: dp(36), dp(48) - pos_hint: {'center_x': .95, 'center_y': .4} - on_active: app.root.ids.id_myaddress.toggleAction(self) diff --git a/src/bitmessagekivy/kv/network.kv b/src/bitmessagekivy/kv/network.kv deleted file mode 100644 index 17211e98..00000000 --- a/src/bitmessagekivy/kv/network.kv +++ /dev/null @@ -1,131 +0,0 @@ -: - name: 'networkstat' - MDTabs: - id: tab_panel - tab_display_mode:'text' - - Tab: - title: app.tr._("Total connections") - ScrollView: - do_scroll_x: False - MDList: - id: ml - size_hint_y: None - height: dp(200) - OneLineListItem: - text: app.tr._("Total Connections") - _no_ripple_effect: True - BoxLayout: - orientation: 'vertical' - size_hint_y: None - height: dp(58) - MDRaisedButton: - _no_ripple_effect: True - # size_hint: .6, 0 - # height: dp(40) - text: app.tr._(root.text_variable_1) - elevation_normal: 2 - opposite_colors: True - pos_hint: {'center_x': .5} - # MDLabel: - # font_style: 'H6' - # text: app.tr._(root.text_variable_1) - # font_size: '13sp' - # color: (1,1,1,1) - # halign: 'center' - Tab: - title: app.tr._('Processes') - ScrollView: - do_scroll_x: False - MDList: - id: ml - size_hint_y: None - height: dp(500) - OneLineListItem: - text: app.tr._("person-to-person") - _no_ripple_effect: True - - BoxLayout: - orientation: 'vertical' - size_hint_y: None - height: dp(58) - MDRaisedButton: - _no_ripple_effect: True - # size_hint: .6, 0 - # height: dp(40) - text: app.tr._(root.text_variable_2) - elevation_normal: 2 - opposite_colors: True - pos_hint: {'center_x': .5} - # MDLabel: - # font_style: 'H6' - # text: app.tr._(root.text_variable_2) - # font_size: '13sp' - # color: (1,1,1,1) - # halign: 'center' - OneLineListItem: - text: app.tr._("Brodcast") - _no_ripple_effect: True - - BoxLayout: - orientation: 'vertical' - size_hint_y: None - height: dp(58) - MDRaisedButton: - _no_ripple_effect: True - # size_hint: .6, 0 - # height: dp(40) - text: app.tr._(root.text_variable_3) - elevation_normal: 2 - opposite_colors: True - pos_hint: {'center_x': .5} - # MDLabel: - # font_style: 'H6' - # text: app.tr._(root.text_variable_3) - # font_size: '13sp' - # color: (1,1,1,1) - # halign: 'center' - OneLineListItem: - text: app.tr._("publickeys") - _no_ripple_effect: True - - BoxLayout: - orientation: 'vertical' - size_hint_y: None - height: dp(58) - MDRaisedButton: - _no_ripple_effect: True - # size_hint: .6, 0 - # height: dp(40) - text: app.tr._(root.text_variable_4) - elevation_normal: 2 - opposite_colors: True - pos_hint: {'center_x': .5} - # MDLabel: - # font_style: 'H6' - # text: app.tr._(root.text_variable_4) - # font_size: '13sp' - # color: (1,1,1,1) - # halign: 'center' - OneLineListItem: - text: app.tr._("objects") - _no_ripple_effect: True - - BoxLayout: - orientation: 'vertical' - size_hint_y: None - height: dp(58) - MDRaisedButton: - _no_ripple_effect: True - # size_hint: .6, 0 - #height: dp(40) - text: app.tr._(root.text_variable_5) - elevation_normal: 2 - opposite_colors: True - pos_hint: {'center_x': .5} - # MDLabel: - # font_style: 'H6' - # text: app.tr._(root.text_variable_5) - # font_size: '13sp' - # color: (1,1,1,1) - # halign: 'center' diff --git a/src/bitmessagekivy/kv/payment.kv b/src/bitmessagekivy/kv/payment.kv deleted file mode 100644 index 6d475f56..00000000 --- a/src/bitmessagekivy/kv/payment.kv +++ /dev/null @@ -1,325 +0,0 @@ -: - name: "payment" - id: id_payment_screen - payment_plan_id: "" - MDTabs: - id: tab_panel - tab_display_mode:'text' - Tab: - title: app.tr._("Payment") - id: id_payment plan - padding: "12dp" - spacing: "12dp" - BoxLayout: - ScrollView: - bar_width:0 - do_scroll_x: False - BoxLayout: - spacing: dp(5) - padding: dp(5) - size_hint_y: None - height: self.minimum_height - orientation: "vertical" - ProductCategoryLayout: - MDCard: - orientation: "vertical" - padding: "8dp" - spacing: "12dp" - size_hint: None, None - size: "560dp", "40dp" - pos_hint: {"center_x": .5, "center_y": .5} - MDLabel: - text: f"You have {app.encrypted_messages_per_month} messages left" - bold: True - halign:'center' - size_hint_y: None - pos_hint: {"center_x": .5, "center_y": .5} - - MDCard: - orientation: "vertical" - padding: "8dp" - spacing: "12dp" - size_hint: None, None - size: "560dp", "300dp" - md_bg_color: [1, 0.6, 0,0.5] - pos_hint: {"center_x": .5, "center_y": .5} - MDLabel: - text: "Free" - bold: True - halign:'center' - size_hint_y: None - pos_hint: {"center_x": .5, "center_y": .5} - MDRectangleFlatIconButton: - text: "[Currently this plan is active.]" - icon: 'shield-check' - line_color: 0, 0, 0, 0 - text_color: 'ffffff' - pos_hint: {"center_x": .5, "center_y": .5} - font_size: '18sp' - MDSeparator: - height: "1dp" - MDLabel: - text: "You can get zero encrypted message per month" - halign:'center' - bold: True - MDCard: - orientation: "vertical" - padding: "8dp" - spacing: "12dp" - size_hint: None, None - size: "560dp", "300dp" - md_bg_color: [0, 0.6, 0.8,0.8] - pos_hint: {"center_x": .5, "center_y": .5} - payment_plan_id: "sub_standard" - MDLabel: - text: "Standard" - bold: True - halign:'center' - size_hint_y: None - MDSeparator: - height: "1dp" - MDLabel: - text: "You can get 100 encrypted message per month" - halign:'center' - MDRaisedButton: - text: "Get it now" - theme_text_color: 'Primary' - md_bg_color: [1, 1, 1,1] - pos_hint: {'center_x': .5} - on_release:app.open_payment_layout(root.payment_plan_id) - MDCard: - orientation: "vertical" - padding: "8dp" - spacing: "12dp" - size_hint: None, None - size: "560dp", "300dp" - md_bg_color: [1, 0.6, 0.8,0.5] - pos_hint: {"center_x": .5, "center_y": .5} - payment_plan_id: "sub_premium" - MDLabel: - text: "Premium" - bold: True - halign:'center' - size_hint_y: None - MDSeparator: - height: "1dp" - MDLabel: - text: "You can get 1000 encrypted message per month" - halign:'center' - MDRaisedButton: - text: "Get it now" - theme_text_color: 'Primary' - md_bg_color: [1, 1, 1,1] - pos_hint: {'center_x': .5} - on_release:app.open_payment_layout(root.payment_plan_id) - Tab: - title: app.tr._("Extra-Messages") - id: id_payment_tab - BoxLayout: - ScrollView: - bar_width:0 - do_scroll_x: False - BoxLayout: - spacing: dp(8) - padding: dp(5) - size_hint_y: None - height: self.minimum_height - orientation: "vertical" - ProductCategoryLayout: - category_text: "Extra-Messages" - ProductLayout: - heading_text: "100 Encrypted messages " - price_text: "$0.99" - source: app.image_path + "/payment/buynew1.png" - description_text: "Buy extra one hundred encrypted messages!" - product_id: "SKUGASBILLING" - ProductLayout: - heading_text: "1000 Encrypted messages " - price_text: "$1.49" - source: app.image_path + "/payment/buynew1.png" - description_text: "Buy extra one thousand encrypted messages!" - product_id: "SKUUPGRADECAR" -: - size_hint_y: None - height: self.minimum_height - category_text:"" - - orientation: "vertical" - spacing: 2 - - #category area - Category: - text_: root.category_text - -: - canvas: - Color: - rgba: 1,1,1,1 - Rectangle: - pos: self.pos - size: self.size - text_: "" - size_hint_y: None - height: dp(30) - Widget: - size_hint_x: None - width: dp(20) - MDLabel: - text: root.text_ - font_size: sp(15) -: - heading_text: "" - price_text: "" - source: "" - description_text: "" - - product_id: "" - - canvas: - Color: - rgba: 1,1,1,1 - Rectangle: - pos: self.pos - size: self.size - - size_hint_y: None - height: dp(200) - orientation: "vertical" - - #heading area - BoxLayout: - size_hint_y: 0.3 - #text heading - BoxLayout: - Widget: - size_hint_x: None - width: dp(20) - MDLabel: - text: root.heading_text - bold: True - - #price text - BoxLayout: - size_hint_x:.3 - MDLabel: - text: root.price_text - bold: True - halign: "right" - theme_text_color: "Custom" - text_color: 0,0,1,1 - Widget: - size_hint_x: None - width: dp(20) - - #details area - BoxLayout: - size_hint_y: 0.3 - Widget: - size_hint_x: None - width: dp(20) - - #image area - AnchorLayout: - size_hint_x: None - width: self.height - BoxLayout: - canvas: - Color: - rgba: 1,1,1,1 - Ellipse: - size: self.size - pos: self.pos - source: root.source - Widget: - size_hint_x: None - width: dp(10) - - #description text - BoxLayout: - #size_hint_x: 1 - MDLabel: - text: root.description_text - font_size: sp(15) - #Button Area - BoxLayout: - size_hint_y: 0.4 - Widget: - - AnchorLayout: - anchor_x: "right" - MDRaisedButton: - elevation_normal: 5 - text: "BUY" - on_release: - #print(app) - app.open_payment_layout(root.product_id) - - Widget: - size_hint_x: None - width: dp(20) - -: - on_release: app.initiate_purchase(self.method_name) - recent: False - source: "" - method_name: "" - right_label_text: "Recent" if self.recent else "" - - ImageLeftWidget: - source: root.source - - RightLabel: - text: root.right_label_text - theme_text_color: "Custom" - text_color: 0,0,0,0.5 - font_size: sp(12) - -: - orientation: "vertical" - size_hint_y: None - height: "200dp" - - BoxLayout: - size_hint_y: None - height: dp(40) - - Widget: - size_hint_x: None - width: dp(20) - MDLabel: - text: "Select Payment Method" - font_size: sp(14) - bold: True - theme_text_color: "Custom" - text_color: 0,0,0,.5 - - - ScrollView: - - GridLayout: - cols: 1 - size_hint_y:None - height:self.minimum_height - - ListItemWithLabel: - source: app.image_path + "/payment/gplay.png" - text: "Google Play" - method_name: "gplay" - recent: True - - ListItemWithLabel: - source: app.image_path + "/payment/btc.png" - text: "BTC (Currently this feature is not available)" - method_name: "btc" - theme_text_color: 'Secondary' - md_bg_color: [0, 0, 0,1] - ListItemWithLabel: - source: app.image_path + "/payment/paypal.png" - text: "Paypal (Currently this feature is not available)" - method_name: "som" - theme_text_color: 'Secondary' - md_bg_color: [0, 0, 0,1] - ListItemWithLabel: - source: app.image_path + "/payment/buy.png" - text: "One more method" - method_name: "omm" diff --git a/src/bitmessagekivy/kv/popup.kv b/src/bitmessagekivy/kv/popup.kv deleted file mode 100644 index 2db74525..00000000 --- a/src/bitmessagekivy/kv/popup.kv +++ /dev/null @@ -1,333 +0,0 @@ -: - separator_color: 1, 1, 1, 1 - background: "White.png" - Button: - id: btn - disabled: True - background_disabled_normal: "White.png" - Image: - source: app.image_path + '/loader.gif' - anim_delay: 0 - #mipmap: True - size: root.size - - -: - id: popup_box - orientation: 'vertical' - # spacing:dp(20) - # spacing: "12dp" - size_hint_y: None - # height: "120dp" - height: label.height+address.height - BoxLayout: - orientation: 'vertical' - MDTextField: - id: label - multiline: False - hint_text: app.tr._("Label") - required: True - icon_right: 'label' - helper_text_mode: "on_error" - # TODO: on_text: root.checkLabel_valid(self) is not used now but it will be used later - canvas.before: - Color: - rgba: (0,0,0,1) - MDTextField: - id: address - hint_text: app.tr._("Address") - required: True - icon_right: 'book-plus' - helper_text_mode: "on_error" - multiline: False - # TODO: on_text: root.checkAddress_valid(self) is not used now but it will be used later - canvas.before: - Color: - rgba: (0,0,0,1) - -: - id: addbook_popup_box - size_hint_y: None - height: 2.5*(add_label.height) - orientation: 'vertical' - spacing:dp(5) - MDLabel - font_style: 'Subtitle2' - theme_text_color: 'Primary' - text: app.tr._("Label") - font_size: '17sp' - halign: 'left' - MDTextField: - id: add_label - font_style: 'Body1' - font_size: '15sp' - halign: 'left' - text: app.tr._(root.address_label) - theme_text_color: 'Primary' - required: True - helper_text_mode: "on_error" - on_text: root.checkLabel_valid(self) - canvas.before: - Color: - rgba: (0,0,0,1) - MDLabel: - font_style: 'Subtitle2' - theme_text_color: 'Primary' - text: app.tr._("Address") - font_size: '17sp' - halign: 'left' - Widget: - size_hint_y: None - height: dp(1) - BoxLayout: - orientation: 'horizontal' - MDLabel: - id: address - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._(root.address) - font_size: '15sp' - halign: 'left' - IconRightSampleWidget: - pos_hint: {'center_x': 0, 'center_y': 1} - icon: 'content-copy' - on_press: app.copy_composer_text(root.address) - - -: - id: myadd_popup - size_hint_y: None - height: "130dp" - spacing:dp(25) - - #height: dp(1.5*(myaddr_label.height)) - orientation: 'vertical' - MDLabel: - id: myaddr_label - font_style: 'Subtitle2' - theme_text_color: 'Primary' - text: app.tr._("Label") - font_size: '17sp' - halign: 'left' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: root.address_label - font_size: '15sp' - halign: 'left' - MDLabel: - font_style: 'Subtitle2' - theme_text_color: 'Primary' - text: app.tr._("Address") - font_size: '17sp' - halign: 'left' - BoxLayout: - orientation: 'horizontal' - MDLabel: - id: label_address - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._(root.address) - font_size: '15sp' - halign: 'left' - IconRightSampleWidget: - pos_hint: {'center_x': 0, 'center_y': 1} - icon: 'content-copy' - on_press: app.copy_composer_text(root.address) - BoxLayout: - id: my_add_btn - spacing:5 - orientation: 'horizontal' - size_hint_y: None - height: self.minimum_height - MDRaisedButton: - size_hint: 2, None - height: dp(40) - on_press: root.send_message_from() - MDLabel: - font_style: 'H6' - text: app.tr._('Send message from') - font_size: '13sp' - color: (1,1,1,1) - halign: 'center' - MDRaisedButton: - size_hint: 1.5, None - height: dp(40) - on_press: app.set_screen('showqrcode') - on_press: app.root.ids.id_showqrcode.qrdisplay(root, root.address) - MDLabel: - font_style: 'H6' - text: app.tr._('Show QR code') - font_size: '13sp' - color: (1,1,1,1) - halign: 'center' - MDRaisedButton: - size_hint: 1.5, None - height: dp(40) - on_press: root.close_pop() - MDLabel: - font_style: 'H6' - text: app.tr._('Cancel') - font_size: '13sp' - color: (1,1,1,1) - halign: 'center' - -: - id: closing_popup - size_hint : (None,None) - height: 1.4*(popup_label.height+ my_add_btn.children[0].height) - width :app.window_size[0] - (app.window_size[0]/10 if app.app_platform == 'android' else app.window_size[0]/4) - background: app.image_path + '/popup.jpeg' - auto_dismiss: False - separator_height: 0 - BoxLayout: - id: myadd_popup_box - size_hint_y: None - spacing:dp(70) - orientation: 'vertical' - BoxLayout: - size_hint_y: None - orientation: 'vertical' - spacing:dp(25) - MDLabel: - id: popup_label - font_style: 'Subtitle2' - theme_text_color: 'Primary' - text: app.tr._("Bitmessage isn't connected to the network.\n If you quit now, it may cause delivery delays.\n Wait until connected and the synchronisation finishes?") - font_size: '17sp' - halign: 'center' - BoxLayout: - id: my_add_btn - spacing:5 - orientation: 'horizontal' - MDRaisedButton: - size_hint: 1.5, None - height: dp(40) - on_press: root.closingAction(self.children[0].text) - on_press: app.stop() - MDLabel: - font_style: 'H6' - text: app.tr._('Yes') - font_size: '13sp' - color: (1,1,1,1) - halign: 'center' - MDRaisedButton: - size_hint: 1.5, None - height: dp(40) - on_press: root.closingAction(self.children[0].text) - MDLabel: - font_style: 'H6' - text: app.tr._('No') - font_size: '13sp' - color: (1,1,1,1) - halign: 'center' - MDRaisedButton: - size_hint: 1.5, None - height: dp(40) - #on_press: root.dismiss() - on_press: root.closingAction(self.children[0].text) - MDLabel: - font_style: 'H6' - text: app.tr._('Cancel') - font_size: '13sp' - color: (1,1,1,1) - halign: 'center' - -: - id: myadd_popup - size_hint : (None,None) - # height: 2*(sd_label.height+ sd_btn.children[0].height) - width :app.window_size[0] - (app.window_size[0]/10 if app.app_platform == 'android' else app.window_size[0]/4) - background: app.image_path + '/popup.jpeg' - auto_dismiss: False - separator_height: 0 - BoxLayout: - id: myadd_popup_box - size_hint_y: None - orientation: 'vertical' - spacing:dp(8 if app.app_platform == 'android' else 3) - BoxLayout: - orientation: 'vertical' - MDLabel: - id: from_add_label - font_style: 'Subtitle2' - theme_text_color: 'Primary' - text: app.tr._("From :") - font_size: '15sp' - halign: 'left' - Widget: - size_hint_y: None - height: dp(1 if app.app_platform == 'android' else 0) - BoxLayout: - size_hint_y: None - height: 50 - orientation: 'horizontal' - MDLabel: - id: sd_label - font_style: 'Body2' - theme_text_color: 'Primary' - text: app.tr._("[b]" + root.from_addr + "[/b]") - font_size: '15sp' - halign: 'left' - markup: True - IconRightSampleWidget: - icon: 'content-copy' - on_press: app.copy_composer_text(root.from_addr) - Widget: - id: space_1 - size_hint_y: None - height: dp(2 if app.app_platform == 'android' else 0) - BoxLayout: - id: to_addtitle - Widget: - id:space_2 - size_hint_y: None - height: dp(1 if app.app_platform == 'android' else 0) - BoxLayout: - id: to_addId - BoxLayout: - size_hint_y: None - orientation: 'vertical' - height: 50 - MDLabel: - font_style: 'Body2' - theme_text_color: 'Primary' - text: app.tr._("Date : " + root.time_tag) - font_size: '15sp' - halign: 'left' - BoxLayout: - id: sd_btn - orientation: 'vertical' - MDRaisedButton: - id: dismiss_btn - on_press: root.dismiss() - size_hint: .2, 0 - pos_hint: {'x': 0.8, 'y': 0} - MDLabel: - font_style: 'H6' - text: app.tr._('Cancel') - font_size: '13sp' - color: (1,1,1,1) - halign: 'center' - -: - orientation: 'horizontal' - MDLabel: - font_style: 'Body2' - theme_text_color: 'Primary' - text: app.tr._(root.to_addr) - font_size: '15sp' - halign: 'left' - IconRightSampleWidget: - icon: 'content-copy' - on_press: app.copy_composer_text(root.to_addr) - -: - orientation: 'vertical' - MDLabel: - id: to_add_label - font_style: 'Subtitle2' - theme_text_color: 'Primary' - text: "To :" - font_size: '15sp' - halign: 'left' \ No newline at end of file diff --git a/src/bitmessagekivy/kv/qrcode.kv b/src/bitmessagekivy/kv/qrcode.kv deleted file mode 100644 index cadaa996..00000000 --- a/src/bitmessagekivy/kv/qrcode.kv +++ /dev/null @@ -1,33 +0,0 @@ -: - name: 'showqrcode' - BoxLayout: - orientation: 'vertical' - size_hint: (None, None) - pos_hint:{'center_x': .5, 'top': 0.9} - size: (app.window_size[0]/1.8, app.window_size[0]/1.8) - id: qr - BoxLayout: - orientation: 'vertical' - MyMDTextField: - size_hint_y: None - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._(root.address) - multiline: True - readonly: True - line_color_normal: [0,0,0,0] - _current_line_color: [0,0,0,0] - line_color_focus: [0,0,0,0] - halign: 'center' - font_size: dp(15) - bold: True - canvas.before: - Color: - rgba: (0,0,0,1) - # MDLabel: - # size_hint_y: None - # font_style: 'Body1' - # theme_text_color: 'Primary' - # text: "[b]BM-2cV7Y8imvAevK6z6YmhYRcj2t7rghBtDSZ[/b]" - # markup: True - # pos_hint: {'x': .28, 'y': 0.6} \ No newline at end of file diff --git a/src/bitmessagekivy/kv/scan_screen.kv b/src/bitmessagekivy/kv/scan_screen.kv deleted file mode 100644 index dbcff5a1..00000000 --- a/src/bitmessagekivy/kv/scan_screen.kv +++ /dev/null @@ -1,2 +0,0 @@ -: - name:'scanscreen' \ No newline at end of file diff --git a/src/bitmessagekivy/kv/scanner.kv b/src/bitmessagekivy/kv/scanner.kv deleted file mode 100644 index 1c56f6c2..00000000 --- a/src/bitmessagekivy/kv/scanner.kv +++ /dev/null @@ -1,37 +0,0 @@ -#:import ZBarSymbol pyzbar.pyzbar.ZBarSymbol - -BoxLayout: - orientation: 'vertical' - ZBarCam: - id: zbarcam - # optional, by default checks all types - code_types: ZBarSymbol.QRCODE, ZBarSymbol.EAN13 - scan_callback: app._after_scan - scanner_line_y_initial: self.size[1]/2 +self.qrwidth/2 - scanner_line_y_final: self.size[1]/2-self.qrwidth/2 - - canvas: - Color: - rgba: 0,0,0,.25 - - #left rect - Rectangle: - pos: self.pos[0], self.pos[1] - size: self.size[0]/2-self.qrwidth/2, self.size[1] - - #right rect - Rectangle: - pos: self.size[0]/2+self.qrwidth/2, 0 - size: self.size[0]/2-self.qrwidth/2, self.size[1] - - #top rect - Rectangle: - pos: self.size[0]/2-self.qrwidth/2, self.size[1]/2+self.qrwidth/2 - size: self.qrwidth, self.size[1]/2-self.qrwidth/2 - - #bottom rect - Rectangle: - pos: self.size[0]/2-self.qrwidth/2, 0 - size: self.qrwidth, self.size[1]/2-self.qrwidth/2 - - \ No newline at end of file diff --git a/src/bitmessagekivy/kv/sent.kv b/src/bitmessagekivy/kv/sent.kv deleted file mode 100644 index 11477ed6..00000000 --- a/src/bitmessagekivy/kv/sent.kv +++ /dev/null @@ -1,26 +0,0 @@ -: - name: 'sent' - BoxLayout: - orientation: 'vertical' - spacing: dp(5) - SearchBar: - id: sent_search - GridLayout: - id: identi_tag - padding: [20, 0, 0, 5] - cols: 1 - size_hint_y: None - height: self.minimum_height - MDLabel: - id: tag_label - text: '' - font_style: 'Subtitle2' - BoxLayout: - orientation:'vertical' - ScrollView: - id: scroll_y - do_scroll_x: False - MDList: - id: ml - Loader: - ComposerButton: \ No newline at end of file diff --git a/src/bitmessagekivy/kv/settings.kv b/src/bitmessagekivy/kv/settings.kv deleted file mode 100644 index f5796060..00000000 --- a/src/bitmessagekivy/kv/settings.kv +++ /dev/null @@ -1,948 +0,0 @@ -: - name: 'set' - MDTabs: - id: tab_panel - tab_display_mode:'text' - - Tab: - title: app.tr._("User Interface") - ScrollView: - do_scroll_x: False - BoxLayout: - size_hint_y: None - orientation: 'vertical' - height: dp(250) + self.minimum_height - padding: 10 - BoxLayout: - size_hint_y: None - orientation: 'horizontal' - height: self.minimum_height - MDCheckbox: - id: chkbox - size_hint: None, None - size: dp(48), dp(50) - # active: True - halign: 'center' - disabled: True - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Start-on-login not yet supported on your OS") - halign: 'left' - pos_hint: {'center_x': 0, 'center_y': 0.6} - disabled: True - BoxLayout: - size_hint_y: None - orientation: 'vertical' - padding: [20, 0, 0, 0] - spacing: dp(10) - height: dp(100) + self.minimum_height - # pos_hint: {'center_x': 0, 'center_y': 0.6} - BoxLayout: - id: box_height - orientation: 'vertical' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Tray") - halign: 'left' - bold: True - BoxLayout: - orientation: 'horizontal' - MDCheckbox: - id: chkbox - size_hint: None, None - size: dp(48), dp(50) - # active: True - halign: 'center' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Start Bitmessage in the tray(don't show main window)") - halign: 'left' - pos_hint: {'x': 0, 'y': .5} - BoxLayout: - orientation: 'horizontal' - MDCheckbox: - id: chkbox - size_hint: None, None - size: dp(48), dp(50) - # active: True - halign: 'center' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Minimize to tray") - halign: 'left' - pos_hint: {'x': 0, 'y': .5} - BoxLayout: - orientation: 'horizontal' - MDCheckbox: - id: chkbox - size_hint: None, None - size: dp(48), dp(50) - # active: True - halign: 'center' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Close to tray") - halign: 'left' - pos_hint: {'x': 0, 'y': .5} - BoxLayout: - size_hint_y: None - orientation: 'vertical' - BoxLayout: - orientation: 'horizontal' - MDCheckbox: - id: chkbox - size_hint: None, None - size: dp(48), dp(50) - # active: True - halign: 'center' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Hide connection notifications") - halign: 'left' - BoxLayout: - orientation: 'horizontal' - MDCheckbox: - id: chkbox - size_hint: None, None - size: dp(48), dp(50) - active: True - halign: 'center' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Show notification when message received") - halign: 'left' - - BoxLayout: - orientation: 'vertical' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._('In portable Mode, messages and config files are stored in the same directory as the program rather then the normal application-data folder. This makes it convenient to run Bitmessage from a USB thumb drive.') - # text: 'huiiiii' - halign: 'left' - BoxLayout: - size_hint_y: None - orientation: 'vertical' - height: dp(100) + self.minimum_height - BoxLayout: - orientation: 'horizontal' - MDCheckbox: - id: chkbox - size_hint: None, None - size: dp(48), dp(50) - halign: 'center' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Willingly include unencrypted destination address when sending to a mobile device") - halign: 'left' - pos_hint: {'x': 0, 'y': 0.2} - BoxLayout: - orientation: 'horizontal' - MDCheckbox: - id: chkbox - size_hint: None, None - size: dp(48), dp(50) - active: True - halign: 'center' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Use identicons") - halign: 'left' - pos_hint: {'x': 0, 'y': 0.2} - BoxLayout: - orientation: 'horizontal' - MDCheckbox: - id: chkbox - size_hint: None, None - size: dp(48), dp(50) - halign: 'center' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Reply below Quote") - halign: 'left' - pos_hint: {'x': 0, 'y': 0.2} - Widget: - size_hint_y: None - height: 10 - BoxLayout: - size_hint_y: None - orientation: 'vertical' - # padding: [0, 10, 0, 0] - spacing: 10 - padding: [20, 0, 0, 0] - height: dp(20) + self.minimum_height - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Interface Language") - # halign: 'right' - bold: True - MDDropDownItem: - id: dropdown_item - text: "System Setting" - # pos_hint: {"center_x": .5, "center_y": .6} - # current_item: "Item 0" - # on_release: root.menu.open() - BoxLayout: - spacing:5 - orientation: 'horizontal' - # pos_hint: {'x':.76} - BoxLayout: - orientation: 'horizontal' - spacing: 10 - MDRaisedButton: - text: app.tr._('Apply') - # on_press: root.change_language() - Tab: - title: 'Network Settings' - ScrollView: - do_scroll_x: False - BoxLayout: - size_hint_y: None - orientation: 'vertical' - height: dp(500) + self.minimum_height - padding: 10 - BoxLayout: - id: box_height - orientation: 'vertical' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Listening port") - halign: 'left' - bold: True - BoxLayout: - orientation: 'horizontal' - padding: [10, 0, 0, 0] - BoxLayout: - orientation: 'horizontal' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Listen for connections on port:") - halign: 'left' - BoxLayout: - orientation: 'horizontal' - MDTextFieldRect: - size_hint: None, None - size: dp(100), dp(30) - text: app.tr._('8444') - pos_hint: {'center_y': .5, 'center_x': .5} - input_filter: "int" - BoxLayout: - orientation: 'horizontal' - padding_left: 10 - MDCheckbox: - id: chkbox - size_hint: None, None - size: dp(48), dp(50) - # active: True - halign: 'center' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("UPnP") - halign: 'left' - pos_hint: {'x': 0, 'y': 0} - BoxLayout: - orientation: 'vertical' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Proxy server / Tor") - halign: 'left' - bold: True - - GridLayout: - cols: 2 - padding: [10, 0, 0, 0] - MDLabel: - size_hint_x: None - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Type:") - halign: 'left' - MDDropDownItem: - id: dropdown_item2 - dropdown_bg: [1, 1, 1, 1] - text: 'none' - pos_hint: {'x': 0.9, 'y': 0} - items: [f"{i}" for i in ['System Setting','U.S. English']] - BoxLayout: - size_hint_y: None - orientation: 'vertical' - padding: [30, 0, 0, 0] - spacing: 10 - height: dp(100) + self.minimum_height - BoxLayout: - orientation: 'horizontal' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Server hostname:") - halign: 'left' - MDTextFieldRect: - size_hint: None, None - size: dp(app.window_size[0]/4), dp(30) - hint_text: app.tr._('localhost') - pos_hint: {'center_y': .5, 'center_x': .5} - BoxLayout: - orientation: 'horizontal' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Port:") - halign: 'left' - # TextInput: - # size_hint: None, None - # hint_text: '9050' - # size: dp(app.window_size[0]/4), dp(30) - # input_filter: "int" - # readonly: False - # multiline: False - # font_size: '15sp' - MDTextFieldRect: - size_hint: None, None - size: dp(app.window_size[0]/4), dp(30) - hint_text: app.tr._('9050') - pos_hint: {'center_y': .5, 'center_x': .5} - input_filter: "int" - BoxLayout: - orientation: 'horizontal' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Username:") - halign: 'left' - MDTextFieldRect: - size_hint: None, None - size: dp(app.window_size[0]/4), dp(30) - pos_hint: {'center_y': .5, 'center_x': .5} - BoxLayout: - orientation: 'horizontal' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Pass:") - halign: 'left' - MDTextFieldRect: - size_hint: None, None - size: dp(app.window_size[0]/4), dp(30) - pos_hint: {'center_y': .5, 'center_x': .5} - BoxLayout: - orientation: 'horizontal' - padding: [30, 0, 0, 0] - MDCheckbox: - id: chkbox - size_hint: None, None - size: dp(48), dp(50) - # active: True - halign: 'center' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Authentication") - halign: 'left' - pos_hint: {'x': 0, 'y': 0} - BoxLayout: - orientation: 'horizontal' - padding: [30, 0, 0, 0] - MDCheckbox: - id: chkbox - size_hint: None, None - size: dp(48), dp(50) - # active: True - halign: 'center' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Listen for incoming connections when using proxy") - halign: 'left' - pos_hint: {'x': 0, 'y': 0} - BoxLayout: - orientation: 'horizontal' - padding: [30, 0, 0, 0] - MDCheckbox: - id: chkbox - size_hint: None, None - size: dp(48), dp(50) - # active: True - halign: 'center' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Only connect to onion services(*.onion)") - halign: 'left' - pos_hint: {'x': 0, 'y': 0} - BoxLayout: - orientation: 'vertical' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Bandwidth limit") - halign: 'left' - bold: True - BoxLayout: - size_hint_y: None - orientation: 'horizontal' - padding: [30, 0, 0, 0] - height: dp(30) + self.minimum_height - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Maximum download rate (kB/s):[0:unlimited]") - halign: 'left' - MDTextFieldRect: - size_hint: None, None - size: app.window_size[0]/2, dp(30) - hint_text: app.tr._('0') - pos_hint: {'center_y': .5, 'center_x': .5} - input_filter: "int" - BoxLayout: - size_hint_y: None - orientation: 'horizontal' - padding: [30, 0, 0, 0] - height: dp(30) + self.minimum_height - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Maximum upload rate (kB/s):[0:unlimited]") - halign: 'left' - MDTextFieldRect: - size_hint: None, None - size: app.window_size[0]/2, dp(30) - hint_text: '0' - pos_hint: {'center_y': .5, 'center_x': .5} - input_filter: "int" - BoxLayout: - size_hint_y: None - orientation: 'horizontal' - padding: [30, 0, 0, 0] - height: dp(30) + self.minimum_height - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Maximum outbound connections:[0:none]") - halign: 'left' - MDTextFieldRect: - size_hint: None, None - size: app.window_size[0]/2, dp(30) - hint_text: '8' - pos_hint: {'center_y': .5, 'center_x': .5} - input_filter: "int" - BoxLayout: - spacing:5 - orientation: 'horizontal' - # pos_hint: {'x':.76} - - MDRaisedButton: - text: app.tr._('Apply') - Tab: - title: 'Demanded Difficulty' - ScrollView: - do_scroll_x: False - - BoxLayout: - size_hint_y: None - orientation: 'vertical' - height: dp(300) + self.minimum_height - padding: 10 - BoxLayout: - id: box_height - orientation: 'vertical' - # MDLabel: - # font_style: 'Body1' - # theme_text_color: 'Primary' - # text: app.tr._("Listening port") - # halign: 'left' - # bold: True - - # BoxLayout: - # size_hint_y: None - # orientation: 'vertical' - # height: dp(210 if app.app_platform == 'android' else 100)+ self.minimum_height - # padding: 20 - # # spacing: 10 - # BoxLayout: - # # size_hint_y: None - # id: box1_height - # # orientation: 'vertical' - # # height: dp(100) + self.minimum_height - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - # text: app.tr._(root.exp_text) - text: "\n\n\nWhen someone sends you a message, their computer must first complete some work. The difficulty of this work, by default, is 1. You may raise this default for new addresses you create by changing the values here. Any new addresses you create will require senders to meet the higher difficulty. There is one exception: if you add a friend or acquaintance to your address book, Bitmessage will automatically notify them when you next send a message that they need only complete the minimum amount of work: difficulty 1.\n\n" - halign: 'left' - - BoxLayout: - orientation: 'horizontal' - padding: 5 - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Total difficulty:") - halign: 'left' - MDTextFieldRect: - size_hint: None, None - size: dp(app.window_size[0]/4), dp(30) - hint_text: app.tr._('00000.0') - pos_hint: {'center_y': .5, 'center_x': .5} - input_filter: "int" - - BoxLayout: - # size_hint_y: None - id: box1_height - orientation: 'vertical' - padding: 5 - # height: dp(100) + self.minimum_height - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - # text: app.tr._(root.exp_text) - text: "The 'Total difficulty' affects the absolute amount of work the sender must complete. Doubling this value doubles the amount of work." - halign: 'left' - - BoxLayout: - orientation: 'horizontal' - spacing: 0 - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Small message difficulty:") - halign: 'left' - MDTextFieldRect: - size_hint: None, None - size: dp(app.window_size[0]/4), dp(30) - hint_text: app.tr._('00000.0') - pos_hint: {'center_y': .5, 'center_x': .5} - input_filter: "int" - - - BoxLayout: - size_hint_y: None - padding: 0 - id: box1_height - orientation: 'vertical' - # height: dp(100) + self.minimum_height - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - # text: app.tr._(root.exp_text) - text: "The 'Small message difficulty' mostly only affects the difficulty of sending small messages. Doubling this value makes it almost twice as difficult to send a small message but doesn't really affect large messages." - halign: 'left' - - - # BoxLayout: - # id: box2_height - # size_hint_y: None - # orientation: 'vertical' - # height: dp(30) + self.minimum_height - # MDLabel: - # font_style: 'Body1' - # theme_text_color: 'Primary' - # text: app.tr._("Leave these input fields blank for the default behavior.") - # halign: 'left' - # BoxLayout: - # size_hint_y: None - # orientation: 'vertical' - # padding: [10, 0, 0, 0] - # height: dp(50) + self.minimum_height - # BoxLayout: - # orientation: 'horizontal' - # MDLabel: - # font_style: 'Body1' - # theme_text_color: 'Primary' - # text: app.tr._("Give up after") - # halign: 'left' - # MDTextFieldRect: - # size_hint: None, None - # size: dp(70), dp(30) - # text: app.tr._('0') - # # pos_hint: {'center_y': .5, 'center_x': .5} - # input_filter: "int" - # MDLabel: - # font_style: 'Body1' - # theme_text_color: 'Primary' - # text: app.tr._("days and") - # halign: 'left' - # MDTextFieldRect: - # size_hint: None, None - # size: dp(70), dp(30) - # text: '0' - # # pos_hint: {'center_y': .5, 'center_x': .5} - # input_filter: "int" - # MDLabel: - # font_style: 'Body1' - # theme_text_color: 'Primary' - # text: "months" - # halign: 'left' - BoxLayout: - size_hint_y: None - spacing:10 - orientation: 'horizontal' - # pos_hint: {'left': 0} - # pos_hint: {'x':.75} - height: dp(10) + self.minimum_height - MDRaisedButton: - text: app.tr._('Cancel') - MDRaisedButton: - text: app.tr._('Apply') - - Tab: - title: 'Max acceptable Difficulty' - ScrollView: - do_scroll_x: False - BoxLayout: - size_hint_y: None - orientation: 'vertical' - height: dp(210 if app.app_platform == 'android' else 100)+ self.minimum_height - padding: 20 - - # spacing: 10 - BoxLayout: - # size_hint_y: None - id: box1_height - orientation: 'vertical' - spacing: 10 - - # pos_hint: {'x': 0, 'y': 0.2} - # height: dp(100) + self.minimum_height - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - # text: app.tr._(root.exp_text) - text: "\n\n\nHere you may set the maximum amount of work you are willing to do to send a message to another person. Setting these values to 0 means that any value is acceptable." - halign: 'left' - # BoxLayout: - # id: box2_height - # size_hint_y: None - # orientation: 'vertical' - # height: dp(40) + self.minimum_height - # BoxLayout: - # size_hint_y: None - # orientation: 'vertical' - # padding: [10, 0, 0, 0] - # height: dp(50) + self.minimum_height - - GridLayout: - cols: 2 - padding: [10, 0, 0, 0] - - BoxLayout: - size_hint_y: None - orientation: 'vertical' - padding: [10, 0, 0, 0] - spacing: 10 - height: dp(50) + self.minimum_height - BoxLayout: - orientation: 'horizontal' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Maximum acceptable total difficulty:") - halign: 'left' - MDTextFieldRect: - size_hint: None, None - size: dp(app.window_size[0]/4), dp(30) - hint_text: app.tr._('00000.0') - pos_hint: {'center_y': .5, 'center_x': .5} - input_filter: "int" - - BoxLayout: - orientation: 'horizontal' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Hardware GPU acceleration (OpenCL):") - halign: 'left' - MDDropDownItem: - id: dropdown_item - text: "None" - pos_hint: {"center_x": 0, "center_y": 0} - # current_item: "Item 0" - # on_release: root.menu.open() - - # BoxLayout: - # size_hint_y: None - # spacing:5 - # orientation: 'horizontal' - # pos_hint: {'center_y': .4, 'center_x': 1.15} - # halign: 'right' - - BoxLayout: - size_hint_y: None - spacing:5 - orientation: 'horizontal' - pos_hint: {'center_y': 1, 'center_x': 1.15} - halign: 'right' - # pos_hint: {'left': 0} - # pos_hint: {'x':.75} - height: dp(50) + self.minimum_height - MDRaisedButton: - text: app.tr._('Cancel') - MDRaisedButton: - text: app.tr._('OK') - Tab: - title: 'Resends Expire' - ScrollView: - do_scroll_x: False - BoxLayout: - size_hint_y: None - orientation: 'vertical' - height: dp(210 if app.app_platform == 'android' else 100)+ self.minimum_height - padding: 20 - # spacing: 10 - BoxLayout: - # size_hint_y: None - id: box1_height - orientation: 'vertical' - # height: dp(100) + self.minimum_height - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - # text: app.tr._(root.exp_text) - text: "By default, if you send a message to someone and he is offline for more than two days, Bitmessage will send the message again after an additional two days. This will be continued with exponential backoff forever; messages will be resent after 5, 10, 20 days ect. until the receiver acknowledges them. Here you may change that behavior by having Bitmessage give up after a certain number of days or months." - halign: 'left' - BoxLayout: - id: box2_height - size_hint_y: None - orientation: 'vertical' - height: dp(30) + self.minimum_height - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Leave these input fields blank for the default behavior.") - halign: 'left' - BoxLayout: - size_hint_y: None - orientation: 'vertical' - padding: [10, 0, 0, 0] - height: dp(50) + self.minimum_height - BoxLayout: - orientation: 'horizontal' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Give up after") - halign: 'left' - MDTextFieldRect: - size_hint: None, None - size: dp(70), dp(30) - text: app.tr._('0') - pos_hint: {'center_y': .5, 'center_x': .5} - input_filter: "int" - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("days and") - halign: 'left' - MDTextFieldRect: - size_hint: None, None - size: dp(70), dp(30) - text: '0' - pos_hint: {'center_y': .5, 'center_x': .5} - input_filter: "int" - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: "months" - halign: 'left' - BoxLayout: - size_hint_y: None - spacing:5 - orientation: 'horizontal' - # pos_hint: {'left': 0} - # pos_hint: {'x':.75} - height: dp(50) + self.minimum_height - # MDRaisedButton: - # text: app.tr._('Cancel') - MDRaisedButton: - text: app.tr._('Apply') - - Tab: - title: 'Namecoin Integration' - ScrollView: - do_scroll_x: False - BoxLayout: - size_hint_y: None - orientation: 'vertical' - height: dp(210 if app.app_platform == 'android' else 100)+ self.minimum_height - padding: 20 - - # spacing: 10 - BoxLayout: - # size_hint_y: None - id: box1_height - orientation: 'vertical' - spacing: 10 - - # pos_hint: {'x': 0, 'y': 0.2} - # height: dp(100) + self.minimum_height - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - # text: app.tr._(root.exp_text) - text: "\n\n\n\n\n\nBitmessage can utilize a different Bitcoin-based program called Namecoin to make addresses human-friendly. For example, instead of having to tell your friend your long Bitmessage address, you can simply tell him to send a message to test.\n\n(Getting your own Bitmessage address into Namecoin is still rather difficult).\n\nBitmessage can use either namecoind directly or a running nmcontrol instance\n\n" - halign: 'left' - - BoxLayout: - id: box2_height - size_hint_y: None - orientation: 'vertical' - height: dp(40) + self.minimum_height - BoxLayout: - size_hint_y: None - orientation: 'vertical' - padding: [10, 0, 0, 0] - height: dp(50) + self.minimum_height - - BoxLayout: - orientation: 'horizontal' - padding: [10, 0, 0, 0] - - BoxLayout: - orientation: 'horizontal' - - # padding_left: 10 - # MDCheckbox: - # id: chkbox - # size_hint: None, None - # size: dp(48), dp(50) - # # active: True - # halign: 'center' - # MDLabel: - # font_style: 'Body1' - # theme_text_color: 'Primary' - # text: app.tr._("UPnP") - # halign: 'left' - # pos_hint: {'x': 0, 'y': 0} - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Connect to:") - halign: 'left' - - # MDCheckbox: - # id: chkbox - # size_hint: None, None - # size: dp(48), dp(50) - # # active: True - # halign: 'center' - Check: - active: True - pos_hint: {'x': 0, 'y': -0.2} - - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Namecoind") - halign: 'left' - pos_hint: {'x': 0, 'y': 0} - - Check: - active: False - pos_hint: {'x': 0, 'y': -0.2} - - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("NMControl") - halign: 'left' - pos_hint: {'x': 0, 'y': 0} - - GridLayout: - cols: 2 - padding: [10, 0, 0, 0] - - BoxLayout: - size_hint_y: None - orientation: 'vertical' - padding: [30, 0, 0, 0] - spacing: 10 - height: dp(100) + self.minimum_height - BoxLayout: - orientation: 'horizontal' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("hostname:") - halign: 'left' - MDTextFieldRect: - size_hint: None, None - size: dp(app.window_size[0]/4), dp(30) - hint_text: app.tr._('localhost') - pos_hint: {'center_y': .5, 'center_x': .5} - BoxLayout: - orientation: 'horizontal' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Port:") - halign: 'left' - # TextInput: - # size_hint: None, None - # hint_text: '9050' - # size: dp(app.window_size[0]/4), dp(30) - # input_filter: "int" - # readonly: False - # multiline: False - # font_size: '15sp' - MDTextFieldRect: - size_hint: None, None - size: dp(app.window_size[0]/4), dp(30) - hint_text: app.tr._('9050') - pos_hint: {'center_y': .5, 'center_x': .5} - input_filter: "int" - BoxLayout: - orientation: 'horizontal' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Username:") - halign: 'left' - MDTextFieldRect: - size_hint: None, None - size: dp(app.window_size[0]/4), dp(30) - pos_hint: {'center_y': .5, 'center_x': .5} - BoxLayout: - orientation: 'horizontal' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Password:") - halign: 'left' - - MDTextFieldRect: - size_hint: None, None - size: dp(app.window_size[0]/4), dp(30) - pos_hint: {'center_y': .5, 'center_x': .5} - password: True - - - BoxLayout: - size_hint_y: None - spacing:5 - orientation: 'horizontal' - pos_hint: {'center_y': .4, 'center_x': 1.15} - halign: 'right' - # pos_hint: {'left': 0} - # pos_hint: {'x':.75} - height: dp(50) + self.minimum_height - MDRaisedButton: - text: app.tr._('Cancel') - MDRaisedButton: - text: app.tr._('Apply') - MDRaisedButton: - text: app.tr._('OK') - Loader: \ No newline at end of file diff --git a/src/bitmessagekivy/kv/trash.kv b/src/bitmessagekivy/kv/trash.kv deleted file mode 100644 index 97bcf7d7..00000000 --- a/src/bitmessagekivy/kv/trash.kv +++ /dev/null @@ -1,25 +0,0 @@ -: - name: 'trash' - BoxLayout: - orientation: 'vertical' - spacing: dp(5) - GridLayout: - id: identi_tag - padding: [20, 20, 0, 5] - spacing: dp(5) - cols: 1 - size_hint_y: None - height: self.minimum_height - MDLabel: - id: tag_label - text: '' - font_style: 'Subtitle2' - BoxLayout: - orientation:'vertical' - ScrollView: - id: scroll_y - do_scroll_x: False - MDList: - id: ml - Loader: - ComposerButton: diff --git a/src/bitmessagekivy/load_kivy_screens_data.py b/src/bitmessagekivy/load_kivy_screens_data.py deleted file mode 100644 index b4132927..00000000 --- a/src/bitmessagekivy/load_kivy_screens_data.py +++ /dev/null @@ -1,25 +0,0 @@ -""" - Load kivy screens data from json -""" -import os -import json -import importlib - - -data_screen_dict = {} - - -def load_screen_json(data_file="screens_data.json"): - """Load screens data from json""" - - with open(os.path.join(os.path.dirname(__file__), data_file)) as read_file: - all_data = json.load(read_file) - data_screens = list(all_data.keys()) - - for key in all_data: - if all_data[key]['Import']: - import_data = all_data.get(key)['Import'] - import_to = import_data.split("import")[1].strip() - import_from = import_data.split("import")[0].split('from')[1].strip() - data_screen_dict[import_to] = importlib.import_module(import_from, import_to) - return data_screens, all_data, data_screen_dict, 'success' diff --git a/src/bitmessagekivy/main.kv b/src/bitmessagekivy/main.kv deleted file mode 100644 index 42e66762..00000000 --- a/src/bitmessagekivy/main.kv +++ /dev/null @@ -1,388 +0,0 @@ -#:import get_color_from_hex kivy.utils.get_color_from_hex -#:import Factory kivy.factory.Factory -#:import Spinner kivy.uix.spinner.Spinner - -#:import colors kivymd.color_definitions.colors -#:import images_path kivymd.images_path - -#:import IconLeftWidget kivymd.uix.list.IconLeftWidget -#:import MDCard kivymd.uix.card.MDCard -#:import MDCheckbox kivymd.uix.selectioncontrol.MDCheckbox -#:import MDFloatingActionButton kivymd.uix.button.MDFloatingActionButton -#:import MDList kivymd.uix.list.MDList -#:import MDScrollViewRefreshLayout kivymd.uix.refreshlayout.MDScrollViewRefreshLayout -#:import MDSpinner kivymd.uix.spinner.MDSpinner -#:import MDTextField kivymd.uix.textfield.MDTextField -#:import MDTabs kivymd.uix.tab.MDTabs -#:import MDTabsBase kivymd.uix.tab.MDTabsBase -#:import OneLineListItem kivymd.uix.list.OneLineListItem - - - -#:set color_button (0.784, 0.443, 0.216, 1) # brown -#:set color_button_pressed (0.659, 0.522, 0.431, 1) # darker brown -#:set color_font (0.957, 0.890, 0.843, 1) # off white - -: - font_size: '12.5sp' - background_normal: 'atlas://data/images/defaulttheme/textinput_active' - background_color: app.theme_cls.primary_color - color: color_font - - - on_press: root.currentlyActive() - active_color: root.theme_cls.primary_color if root.active else root.theme_cls.text_color - - IconLeftWidget: - icon: root.icon - theme_text_color: "Custom" - text_color: root.active_color - - BadgeText: - id: badge_txt - text: f"{root.badge_text}" - theme_text_color: "Custom" - halign: 'right' - -: - canvas: - Color: - rgba: self.theme_cls.divider_color - Line: - points: root.x, root.y + dp(8), root.x + self.width, root.y + dp(8) - - - - BoxLayout: - orientation: 'vertical' - - FloatLayout: - size_hint_y: None - height: "200dp" - - MDIconButton: - id: reset_image - icon: "refresh" - x: root.parent.x + dp(10) - pos_hint: {"top": 1, 'left': 1} - color: [1,0,0,1] - on_release: app.rest_default_avatar_img() - theme_text_color: "Custom" - text_color: app.theme_cls.primary_color - opacity: 1 if app.have_any_address() else 0 - disabled: False if app.have_any_address() else True - - MDIconButton: - id: file_manager - icon: "file-image" - x: root.parent.x + dp(10) - pos_hint: {"top": 1, 'right': 1} - color: [1,0,0,1] - on_release: app.file_manager_open() - theme_text_color: "Custom" - text_color: app.theme_cls.primary_color - opacity: 1 if app.have_any_address() else 0 - disabled: False if app.have_any_address() else True - - BoxLayout: - id: top_box - size_hint_y: None - height: "200dp" - x: root.parent.x - pos_hint: {"top": 1} - Image: - source: app.get_default_logo(self) - - ScrollView: - id: scroll_y - pos_hint: {"top": 1} - - GridLayout: - id: box_item - cols: 1 - size_hint_y: None - height: self.minimum_height - NavigationDrawerDivider: - NavigationDrawerSubheader: - text: app.tr._('Accounts') - height:"35dp" - NavigationItem: - text: 'dropdown_nav_item' - height: dp(48) - IdentitySpinner: - id: identity_dropdown - pos_hint:{"x":0,"y":0} - name: "identity_dropdown" - option_cls: Factory.get("MySpinnerOption") - font_size: '12.5sp' - text: app.getDefaultAccData(self) - color: color_font - background_normal: '' - background_color: app.theme_cls.primary_color - on_text: app.getCurrentAccountData(self.text) - ArrowImg: - NavigationItem: - id: inbox_cnt - text: app.tr._('Inbox') - icon: 'email-open' - divider: None - on_release: app.set_screen('inbox') - on_release: root.parent.set_state() - on_press: app.load_screen(self) - NavigationItem: - id: send_cnt - text: app.tr._('Sent') - icon: 'send' - divider: None - on_release: app.set_screen('sent') - on_release: root.parent.set_state() - NavigationItem: - id: draft_cnt - text: app.tr._('Draft') - icon: 'message-draw' - divider: None - on_release: app.root.ids.scr_mngr.current = 'draft' - on_release: root.parent.set_state() - NavigationItem: - id: trash_cnt - text: app.tr._('Trash') - icon: 'delete' - divider: None - on_release: app.root.ids.scr_mngr.current = 'trash' - on_press: root.parent.set_state() - on_press: app.load_screen(self) - NavigationItem: - id: allmail_cnt - text: app.tr._('All Mails') - icon: 'mailbox' - divider: None - on_release: app.root.ids.scr_mngr.current = 'allmails' - on_release: root.parent.set_state() - on_press: app.load_screen(self) - NavigationDrawerDivider: - NavigationDrawerSubheader: - text: app.tr._('Chat') - NavigationItem: - id: draft_cnt - text: app.tr._('Chat') - icon: 'chat' - divider: None - on_release: app.root.ids.scr_mngr.current = 'chat' - on_release: root.parent.set_state() - NavigationDrawerDivider: - NavigationDrawerSubheader: - text: app.tr._("All labels") - NavigationItem: - text: app.tr._('Address Book') - icon: 'book-multiple' - divider: None - on_release: app.root.ids.scr_mngr.current = 'addressbook' - on_release: root.parent.set_state() - NavigationItem: - text: app.tr._('Settings') - icon: 'application-settings' - divider: None - on_release: app.root.ids.scr_mngr.current = 'set' - on_release: root.parent.set_state() - NavigationItem: - text: app.tr._('Payment plan') - icon: 'shopping' - divider: None - on_release: app.root.ids.scr_mngr.current = 'payment' - on_release: root.parent.set_state() - NavigationItem: - text: app.tr._('New address') - icon: 'account-plus' - divider: None - on_release: app.root.ids.scr_mngr.current = 'login' - on_release: root.parent.set_state() - on_press: app.reset_login_screen() - NavigationItem: - text: app.tr._('Network status') - icon: 'server-network' - divider: None - on_release: app.root.ids.scr_mngr.current = 'networkstat' - on_release: root.parent.set_state() - NavigationItem: - text: app.tr._('My addresses') - icon: 'account-multiple' - divider: None - on_release: app.root.ids.scr_mngr.current = 'myaddress' - on_release: root.parent.set_state() - -MDNavigationLayout: - id: nav_layout - - MDTopAppBar: - id: toolbar - title: app.format_address_and_label() - opacity: 1 if app.have_any_address() else 0 - disabled: False if app.have_any_address() else True - pos_hint: {"top": 1} - md_bg_color: app.theme_cls.primary_color - elevation: 10 - left_action_items: [['menu', lambda x: nav_drawer.set_state("toggle")]] - right_action_items: [['account-plus', lambda x: app.addingtoaddressbook()]] - - ScreenManager: - id: scr_mngr - size_hint_y: None - height: root.height - toolbar.height - Inbox: - id:id_inbox - Login: - id:sc6 - Random: - id:id_newidentity - MyAddress: - id:id_myaddress - ScanScreen: - id:id_scanscreen - Payment: - id:id_payment - Create: - id:id_create - NetworkStat: - id:id_networkstat - Setting: - id:id_settings - Sent: - id:id_sent - Trash: - id:id_trash - Allmails: - id:id_allmail - Draft: - id:id_draft - AddressBook: - id:id_addressbook - ShowQRCode: - id:id_showqrcode - Chat: - id: id_chat - - MDNavigationDrawer: - id: nav_drawer - - ContentNavigationDrawer: - id: content_drawer - -: - source: app.image_path +('/down-arrow.png' if self.parent.is_open == True else '/right-arrow.png') - size: 15, 15 - x: self.parent.x + self.parent.width - self.width - 5 - y: self.parent.y + self.parent.height/2 - self.height + 5 - - -: - size_hint_y: None - height: self.minimum_height - - MDIconButton: - icon: 'magnify' - - MDTextField: - id: search_field - hint_text: 'Search' - canvas.before: - Color: - rgba: (0,0,0,1) - - -: - id: spinner - size_hint: None, None - size: dp(46), dp(46) - pos_hint: {'center_x': 0.5, 'center_y': 0.5} - active: False - -: - size_hint_y: None - height: dp(56) - spacing: '10dp' - pos_hint: {'center_x':0.45, 'center_y': .1} - - Widget: - - MDFloatingActionButton: - icon: 'plus' - opposite_colors: True - elevation_normal: 8 - md_bg_color: [0.941, 0, 0,1] - on_press: app.root.ids.scr_mngr.current = 'create' - on_press: app.clear_composer() - - -: - size_hint_y: None - height: content.height - - MDCardSwipeLayerBox: - padding: "8dp" - - MDIconButton: - id: delete_msg - icon: "trash-can" - pos_hint: {"center_y": .5} - md_bg_color: (1, 0, 0, 1) - disabled: True - - MDCardSwipeFrontBox: - - TwoLineAvatarIconListItem: - id: content - text: root.text - _no_ripple_effect: True - - AvatarSampleWidget: - id: avater_img - source: None - - TimeTagRightSampleWidget: - id: time_tag - text: '' - font_size: "11sp" - font_style: "Caption" - size: [120, 140] if app.app_platform == "android" else [64, 80] - - -: - size_hint_y: None - height: content.height - - MDCardSwipeLayerBox: - padding: "8dp" - - MDIconButton: - id: delete_msg - icon: "trash-can" - pos_hint: {"center_y": .5} - md_bg_color: (1, 0, 0, 1) - disabled: True - - MDCardSwipeFrontBox: - - TwoLineAvatarIconListItem: - id: content - text: root.text - _no_ripple_effect: True - - AvatarSampleWidget: - id: avater_img - source: None - - TimeTagRightSampleWidget: - id: time_tag - text: 'time' - font_size: "11sp" - font_style: "Caption" - size: [120, 140] if app.app_platform == "android" else [64, 80] - MDChip: - id: chip_tag - size_hint: (0.16 if app.app_platform == "android" else 0.08, None) - text: 'test' - icon: "" - pos_hint: {"center_x": 0.91 if app.app_platform == "android" else 0.94, "center_y": 0.3} - height: '18dp' - text_color: (1,1,1,1) - radius: [8] diff --git a/src/bitmessagekivy/mpybit.py b/src/bitmessagekivy/mpybit.py deleted file mode 100644 index 8c3ab4ab..00000000 --- a/src/bitmessagekivy/mpybit.py +++ /dev/null @@ -1,489 +0,0 @@ -# pylint: disable=too-many-public-methods, unused-variable, too-many-ancestors -# pylint: disable=no-name-in-module, too-few-public-methods, unused-argument -# pylint: disable=attribute-defined-outside-init, too-many-instance-attributes - -""" -Bitmessage android(mobile) interface -""" - -import os -import logging -import sys -from functools import partial -from PIL import Image as PilImage - -from kivy.clock import Clock -from kivy.lang import Builder -from kivy.core.window import Window -from kivy.uix.boxlayout import BoxLayout - -from kivymd.app import MDApp -from kivymd.uix.label import MDLabel -from kivymd.uix.dialog import MDDialog -from kivymd.uix.list import ( - IRightBodyTouch -) -from kivymd.uix.button import MDRaisedButton -from kivymd.uix.bottomsheet import MDCustomBottomSheet -from kivymd.uix.filemanager import MDFileManager - -from pybitmessage.bitmessagekivy.kivy_state import KivyStateVariables -from pybitmessage.bitmessagekivy.base_navigation import ( - BaseLanguage, BaseNavigationItem, BaseNavigationDrawerDivider, - BaseNavigationDrawerSubheader, BaseContentNavigationDrawer, - BaseIdentitySpinner -) -from pybitmessage.bmconfigparser import config # noqa: F401 -from pybitmessage.bitmessagekivy import identiconGeneration -from pybitmessage.bitmessagekivy.get_platform import platform -from pybitmessage.bitmessagekivy.baseclass.common import toast, load_image_path, get_identity_list -from pybitmessage.bitmessagekivy.load_kivy_screens_data import load_screen_json - -from pybitmessage.bitmessagekivy.baseclass.popup import ( - AddAddressPopup, AppClosingPopup, AddressChangingLoader -) -from pybitmessage.bitmessagekivy.baseclass.login import * # noqa: F401, F403 -from pybitmessage.bitmessagekivy.uikivysignaler import UIkivySignaler - -from pybitmessage.mockbm.helper_startup import loadConfig, total_encrypted_messages_per_month - -logger = logging.getLogger('default') - - -class Lang(BaseLanguage): - """UI Language""" - - -class NavigationItem(BaseNavigationItem): - """NavigationItem class for kivy Ui""" - - -class NavigationDrawerDivider(BaseNavigationDrawerDivider): - """ - A small full-width divider that can be placed - in the :class:`MDNavigationDrawer` - """ - - -class NavigationDrawerSubheader(BaseNavigationDrawerSubheader): - """ - A subheader for separating content in :class:`MDNavigationDrawer` - - Works well alongside :class:`NavigationDrawerDivider` - """ - - -class ContentNavigationDrawer(BaseContentNavigationDrawer): - """ContentNavigationDrawer class for kivy Uir""" - - -class BadgeText(IRightBodyTouch, MDLabel): - """BadgeText class for kivy Ui""" - - -class IdentitySpinner(BaseIdentitySpinner): - """Identity Dropdown in Side Navigation bar""" - - -class NavigateApp(MDApp): - """Navigation Layout of class""" - - kivy_state = KivyStateVariables() - title = "PyBitmessage" - identity_list = get_identity_list() - image_path = load_image_path() - app_platform = platform - encrypted_messages_per_month = total_encrypted_messages_per_month() - tr = Lang("en") # for changing in franch replace en with fr - - def __init__(self): - super(NavigateApp, self).__init__() - # workaround for relative imports - sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')) - self.data_screens, self.all_data, self.data_screen_dict, response = load_screen_json() - self.kivy_state_obj = KivyStateVariables() - self.image_dir = load_image_path() - self.kivy_state_obj.screen_density = Window.size - self.window_size = self.kivy_state_obj.screen_density - - def build(self): - """Method builds the widget""" - for kv in self.data_screens: - Builder.load_file( - os.path.join( - os.path.dirname(__file__), - 'kv', - '{0}.kv'.format(self.all_data[kv]["kv_string"]), - ) - ) - Window.bind(on_request_close=self.on_request_close) - return Builder.load_file(os.path.join(os.path.dirname(__file__), 'main.kv')) - - def set_screen(self, screen_name): - """Set the screen name when navigate to other screens""" - self.root.ids.scr_mngr.current = screen_name - - def run(self): - """Running the widgets""" - loadConfig() - kivysignalthread = UIkivySignaler() - kivysignalthread.daemon = True - kivysignalthread.start() - self.kivy_state_obj.kivyui_ready.set() - super(NavigateApp, self).run() - - def addingtoaddressbook(self): - """Dialog for saving address""" - width = .85 if platform == 'android' else .8 - self.add_popup = MDDialog( - title='Add contact', - type="custom", - size_hint=(width, .23), - content_cls=AddAddressPopup(), - buttons=[ - MDRaisedButton( - text="Save", - on_release=self.savecontact, - ), - MDRaisedButton( - text="Cancel", - on_release=self.close_pop, - ), - MDRaisedButton( - text="Scan QR code", - on_release=self.scan_qr_code, - ), - ], - ) - self.add_popup.auto_dismiss = False - self.add_popup.open() - - def scan_qr_code(self, instance): - """this method is used for showing QR code scanner""" - if self.is_camara_attached(): - self.add_popup.dismiss() - self.root.ids.id_scanscreen.get_screen(self.root.ids.scr_mngr.current, self.add_popup) - self.root.ids.scr_mngr.current = 'scanscreen' - else: - alert_text = ( - 'Currently this feature is not avaialbe!' if platform == 'android' else 'Camera is not available!') - self.add_popup.dismiss() - toast(alert_text) - - def is_camara_attached(self): - """This method is for checking the camera is available or not""" - self.root.ids.id_scanscreen.check_camera() - is_available = self.root.ids.id_scanscreen.camera_available - return is_available - - def savecontact(self, instance): - """Method is used for saving contacts""" - popup_obj = self.add_popup.content_cls - label = popup_obj.ids.label.text.strip() - address = popup_obj.ids.address.text.strip() - popup_obj.ids.label.focus = not label - # default focus on address field - popup_obj.ids.address.focus = label or not address - - def close_pop(self, instance): - """Close the popup""" - self.add_popup.dismiss() - toast('Canceled') - - def loadMyAddressScreen(self, action): - """loadMyAddressScreen method spin the loader""" - if len(self.root.ids.id_myaddress.children) <= 2: - self.root.ids.id_myaddress.children[0].active = action - else: - self.root.ids.id_myaddress.children[1].active = action - - def load_screen(self, instance): - """This method is used for loading screen on every click""" - if instance.text == 'Inbox': - self.root.ids.scr_mngr.current = 'inbox' - self.root.ids.id_inbox.children[1].active = True - elif instance.text == 'Trash': - self.root.ids.scr_mngr.current = 'trash' - try: - self.root.ids.id_trash.children[1].active = True - except Exception as e: - self.root.ids.id_trash.children[0].children[1].active = True - Clock.schedule_once(partial(self.load_screen_callback, instance), 1) - - def load_screen_callback(self, instance, dt=0): - """This method is rotating loader for few seconds""" - if instance.text == 'Inbox': - self.root.ids.id_inbox.ids.ml.clear_widgets() - self.root.ids.id_inbox.loadMessagelist(self.kivy_state_obj.selected_address) - self.root.ids.id_inbox.children[1].active = False - elif instance.text == 'Trash': - self.root.ids.id_trash.clear_widgets() - self.root.ids.id_trash.add_widget(self.data_screen_dict['Trash'].Trash()) - try: - self.root.ids.id_trash.children[1].active = False - except Exception as e: - self.root.ids.id_trash.children[0].children[1].active = False - - @staticmethod - def get_enabled_addresses(): - """Getting list of all the enabled addresses""" - addresses = [addr for addr in config.addresses() - if config.getboolean(str(addr), 'enabled')] - return addresses - - @staticmethod - def format_address(address): - """Formatting address""" - return " ({0})".format(address) - - @staticmethod - def format_label(label): - """Formatting label""" - if label: - f_name = label.split() - truncate_string = '...' - f_name_max_length = 15 - formatted_label = f_name[0][:14].capitalize() + truncate_string if len( - f_name[0]) > f_name_max_length else f_name[0].capitalize() - return formatted_label - return '' - - @staticmethod - def format_address_and_label(address=None): - """Getting formatted address information""" - if not address: - try: - address = NavigateApp.get_enabled_addresses()[0] - except IndexError: - return '' - return "{0}{1}".format( - NavigateApp.format_label(config.get(address, "label")), - NavigateApp.format_address(address) - ) - - def getDefaultAccData(self, instance): - """Getting Default Account Data""" - if self.identity_list: - self.kivy_state_obj.selected_address = first_addr = self.identity_list[0] - return first_addr - return 'Select Address' - - def getCurrentAccountData(self, text): - """Get Current Address Account Data""" - if text != '': - if os.path.exists(os.path.join( - self.image_dir, 'default_identicon', '{}.png'.format(text)) - ): - self.load_selected_Image(text) - else: - self.set_identicon(text) - self.root.ids.content_drawer.ids.reset_image.opacity = 0 - self.root.ids.content_drawer.ids.reset_image.disabled = True - address_label = self.format_address_and_label(text) - self.root_window.children[1].ids.toolbar.title = address_label - self.kivy_state_obj.selected_address = text - AddressChangingLoader().open() - for nav_obj in self.root.ids.content_drawer.children[ - 0].children[0].children[0].children: - nav_obj.active = True if nav_obj.text == 'Inbox' else False - self.fileManagerSetting() - Clock.schedule_once(self.setCurrentAccountData, 0.5) - - def setCurrentAccountData(self, dt=0): - """This method set the current accout data on all the screens""" - self.root.ids.id_inbox.ids.ml.clear_widgets() - self.root.ids.id_inbox.loadMessagelist(self.kivy_state_obj.selected_address) - - self.root.ids.id_sent.ids.ml.clear_widgets() - self.root.ids.id_sent.children[2].children[2].ids.search_field.text = '' - self.root.ids.id_sent.loadSent(self.kivy_state_obj.selected_address) - - def fileManagerSetting(self): - """This method is for file manager setting""" - if not self.root.ids.content_drawer.ids.file_manager.opacity and \ - self.root.ids.content_drawer.ids.file_manager.disabled: - self.root.ids.content_drawer.ids.file_manager.opacity = 1 - self.root.ids.content_drawer.ids.file_manager.disabled = False - - def on_request_close(self, *args): # pylint: disable=no-self-use - """This method is for app closing request""" - AppClosingPopup().open() - return True - - def clear_composer(self): - """If slow down, the new composer edit screen""" - self.set_navbar_for_composer() - composer_obj = self.root.ids.id_create.children[1].ids - composer_obj.ti.text = '' - composer_obj.composer_dropdown.text = 'Select' - composer_obj.txt_input.text = '' - composer_obj.subject.text = '' - composer_obj.body.text = '' - self.kivy_state_obj.in_composer = True - self.kivy_state_obj = False - - def set_navbar_for_composer(self): - """Clearing toolbar data when composer open""" - self.root.ids.toolbar.left_action_items = [ - ['arrow-left', lambda x: self.back_press()]] - self.root.ids.toolbar.right_action_items = [ - ['refresh', - lambda x: self.root.ids.id_create.children[1].reset_composer()], - ['send', - lambda x: self.root.ids.id_create.children[1].send(self)]] - - def set_identicon(self, text): - """Show identicon in address spinner""" - img = identiconGeneration.generate(text) - self.root.ids.content_drawer.ids.top_box.children[0].texture = (img.texture) - - # pylint: disable=import-outside-toplevel - def file_manager_open(self): - """This method open the file manager of local system""" - if not self.kivy_state_obj.file_manager: - self.file_manager = MDFileManager( - exit_manager=self.exit_manager, - select_path=self.select_path, - ext=['.png', '.jpg'] - ) - self.file_manager.previous = False - self.file_manager.current_path = '/' - if platform == 'android': - # pylint: disable=import-error - from android.permissions import request_permissions, Permission, check_permission - if check_permission(Permission.WRITE_EXTERNAL_STORAGE) and \ - check_permission(Permission.READ_EXTERNAL_STORAGE): - self.file_manager.show(os.getenv('EXTERNAL_STORAGE')) - self.kivy_state_obj.manager_open = True - else: - request_permissions([Permission.WRITE_EXTERNAL_STORAGE, Permission.READ_EXTERNAL_STORAGE]) - else: - self.file_manager.show(os.environ["HOME"]) - self.kivy_state_obj.manager_open = True - - def select_path(self, path): - """This method is used to set the select image""" - try: - newImg = PilImage.open(path).resize((300, 300)) - if platform == 'android': - android_path = os.path.join( - os.path.join(os.environ['ANDROID_PRIVATE'], 'app', 'images', 'kivy') - ) - if not os.path.exists(os.path.join(android_path, 'default_identicon')): - os.makedirs(os.path.join(android_path, 'default_identicon')) - newImg.save(os.path.join(android_path, 'default_identicon', '{}.png'.format( - self.kivy_state_obj.selected_address)) - ) - else: - if not os.path.exists(os.path.join(self.image_dir, 'default_identicon')): - os.makedirs(os.path.join(self.image_dir, 'default_identicon')) - newImg.save(os.path.join(self.image_dir, 'default_identicon', '{0}.png'.format( - self.kivy_state_obj.selected_address)) - ) - self.load_selected_Image(self.kivy_state_obj.selected_address) - toast('Image changed') - except Exception: - toast('Exit') - self.exit_manager() - - def exit_manager(self, *args): - """Called when the user reaches the root of the directory tree.""" - self.kivy_state_obj.manager_open = False - self.file_manager.close() - - def load_selected_Image(self, curerentAddr): - """This method load the selected image on screen""" - top_box_obj = self.root.ids.content_drawer.ids.top_box.children[0] - top_box_obj.source = os.path.join(self.image_dir, 'default_identicon', '{0}.png'.format(curerentAddr)) - self.root.ids.content_drawer.ids.reset_image.opacity = 1 - self.root.ids.content_drawer.ids.reset_image.disabled = False - top_box_obj.reload() - - def rest_default_avatar_img(self): - """set default avatar generated image""" - self.set_identicon(self.kivy_state_obj.selected_address) - img_path = os.path.join( - self.image_dir, 'default_identicon', '{}.png'.format(self.kivy_state_obj.selected_address) - ) - if os.path.exists(img_path): - os.remove(img_path) - self.root.ids.content_drawer.ids.reset_image.opacity = 0 - self.root.ids.content_drawer.ids.reset_image.disabled = True - toast('Avatar reset') - - def get_default_logo(self, instance): - """Getting default logo image""" - if self.identity_list: - first_addr = self.identity_list[0] - if config.getboolean(str(first_addr), 'enabled'): - if os.path.exists( - os.path.join( - self.image_dir, 'default_identicon', '{}.png'.format(first_addr) - ) - ): - return os.path.join( - self.image_dir, 'default_identicon', '{}.png'.format(first_addr) - ) - else: - img = identiconGeneration.generate(first_addr) - instance.texture = img.texture - return - return os.path.join(self.image_dir, 'drawer_logo1.png') - - @staticmethod - def have_any_address(): - """Checking existance of any address""" - if config.addresses(): - return True - return False - - def reset_login_screen(self): - """This method is used for clearing the widgets of random screen""" - if self.root.ids.id_newidentity.ids.add_random_bx.children: - self.root.ids.id_newidentity.ids.add_random_bx.clear_widgets() - - def reset(self, *args): - """Set transition direction""" - self.root.ids.scr_mngr.transition.direction = 'left' - self.root.ids.scr_mngr.transition.unbind(on_complete=self.reset) - - def back_press(self): - """Method for, reverting composer to previous page""" - if self.root.ids.scr_mngr.current == 'showqrcode': - self.set_common_header() - self.root.ids.scr_mngr.current = 'myaddress' - self.root.ids.scr_mngr.transition.bind(on_complete=self.reset) - self.kivy_state.in_composer = False - - def set_toolbar_for_QrCode(self): - """This method is use for setting Qr code toolbar.""" - self.root.ids.toolbar.left_action_items = [ - ['arrow-left', lambda x: self.back_press()]] - self.root.ids.toolbar.right_action_items = [] - - def set_common_header(self): - """Common header for all the Screens""" - self.root.ids.toolbar.right_action_items = [ - ['account-plus', lambda x: self.addingtoaddressbook()]] - self.root.ids.toolbar.left_action_items = [ - ['menu', lambda x: self.root.ids.nav_drawer.set_state("toggle")]] - return - - def open_payment_layout(self, sku): - """It basically open up a payment layout for kivy UI""" - pml = PaymentMethodLayout() - self.product_id = sku - self.custom_sheet = MDCustomBottomSheet(screen=pml) - self.custom_sheet.open() - - def initiate_purchase(self, method_name): - """initiate_purchase module""" - logger.debug("Purchasing %s through %s", self.product_id, method_name) - - -class PaymentMethodLayout(BoxLayout): - """PaymentMethodLayout class for kivy Ui""" - - -if __name__ == '__main__': - NavigateApp().run() diff --git a/src/bitmessagekivy/screens_data.json b/src/bitmessagekivy/screens_data.json deleted file mode 100644 index 86ddea19..00000000 --- a/src/bitmessagekivy/screens_data.json +++ /dev/null @@ -1,83 +0,0 @@ -{ - "Inbox": { - "kv_string": "inbox", - "name_screen": "inbox", - "Import": "from pybitmessage.bitmessagekivy.baseclass.inbox import Inbox" - }, - "Login": { - "kv_string": "login", - "Import": "from pybitmessage.bitmessagekivy.baseclass.login import *" - }, - "My addresses": { - "kv_string": "myaddress", - "name_screen": "myaddress", - "Import": "from pybitmessage.bitmessagekivy.baseclass.myaddress import MyAddress" - }, - "Scanner": { - "kv_string": "scan_screen", - "Import": "from pybitmessage.bitmessagekivy.baseclass.scan_screen import ScanScreen" - }, - "Payment": { - "kv_string": "payment", - "name_screen": "payment", - "Import": "from pybitmessage.bitmessagekivy.baseclass.payment import Payment" - }, - "Create": { - "kv_string": "msg_composer", - "name_screen": "create", - "Import": "from pybitmessage.bitmessagekivy.baseclass.msg_composer import Create" - }, - "Network status": { - "kv_string": "network", - "name_screen": "networkstat", - "Import": "from pybitmessage.bitmessagekivy.baseclass.network import NetworkStat" - }, - "Settings": { - "kv_string": "settings", - "name_screen": "set", - "Import": "from pybitmessage.bitmessagekivy.baseclass.settings import Setting" - }, - "Sent": { - "kv_string": "sent", - "name_screen": "sent", - "Import": "from pybitmessage.bitmessagekivy.baseclass.sent import Sent" - }, - "Trash": { - "kv_string": "trash", - "name_screen": "trash", - "Import": "from pybitmessage.bitmessagekivy.baseclass.trash import Trash" - }, - "Address Book": { - "kv_string": "addressbook", - "name_screen": "addressbook", - "Import": "from pybitmessage.bitmessagekivy.baseclass.addressbook import AddressBook" - }, - "Popups": { - "kv_string": "popup", - "Import": "from pybitmessage.bitmessagekivy.baseclass.popup import *" - }, - "All Mails": { - "kv_string": "allmails", - "name_screen": "allmails", - "Import": "from pybitmessage.bitmessagekivy.baseclass.allmail import Allmails" - }, - "MailDetail": { - "kv_string": "maildetail", - "name_screen": "mailDetail", - "Import": "from pybitmessage.bitmessagekivy.baseclass.maildetail import MailDetail" - }, - "Draft": { - "kv_string": "draft", - "name_screen": "draft", - "Import": "from pybitmessage.bitmessagekivy.baseclass.draft import Draft" - }, - "Qrcode": { - "kv_string": "qrcode", - "Import": "from pybitmessage.bitmessagekivy.baseclass.qrcode import ShowQRCode" - }, - "Chat": { - "kv_string": "chat", - "Import": "from pybitmessage.bitmessagekivy.baseclass.chat import Chat" - } - -} diff --git a/src/bitmessagekivy/tests/__init__.py b/src/bitmessagekivy/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/bitmessagekivy/tests/common.py b/src/bitmessagekivy/tests/common.py deleted file mode 100644 index 553fa020..00000000 --- a/src/bitmessagekivy/tests/common.py +++ /dev/null @@ -1,37 +0,0 @@ -""" -This module is used for running test cases ui order. -""" -import unittest - -from pybitmessage import state - - -def make_ordered_test(): - """this method is for comparing and arranging in order""" - order = {} - - def ordered_method(f): - """method for ordering""" - order[f.__name__] = len(order) - return f - - def compare_method(a, b): - """method for comparing order of methods""" - return [1, -1][order[a] < order[b]] - - return ordered_method, compare_method - - -ordered, compare = make_ordered_test() -unittest.defaultTestLoader.sortTestMethodsUsing = compare - - -def skip_screen_checks(x): - """This methos is skipping current screen checks""" - def inner(y): - """Inner function""" - if not state.enableKivy: - return unittest.skip('Kivy not enabled') - else: - x(y) - return inner diff --git a/src/bitmessagekivy/tests/sampleData/keys.dat b/src/bitmessagekivy/tests/sampleData/keys.dat deleted file mode 100644 index 940b3e14..00000000 --- a/src/bitmessagekivy/tests/sampleData/keys.dat +++ /dev/null @@ -1,59 +0,0 @@ -[bitmessagesettings] -settingsversion = 0 -port = 8444 -timeformat = %%c -blackwhitelist = black -startonlogon = false -minimizetotray = false -showtraynotifications = true -startintray = false -socksproxytype = none -sockshostname = localhost -socksport = 9050 -socksauthentication = false -socksusername = -sockspassword = -keysencrypted = false -messagesencrypted = false -defaultnoncetrialsperbyte = 1000 -defaultpayloadlengthextrabytes = 1000 -minimizeonclose = false -replybelow = False -maxdownloadrate = 0 -maxuploadrate = 0 -stopresendingafterxdays = -stopresendingafterxmonths = -sockslisten = false -userlocale = system -sendoutgoingconnections = True -useidenticons = True -identiconsuffix = qcqQGW6sQtZK -maxacceptablenoncetrialsperbyte = 20000000000 -maxacceptablepayloadlengthextrabytes = 20000000000 -onionhostname = -onionport = 8444 -onionbindip = 127.0.0.1 -smtpdeliver = -hidetrayconnectionnotifications = false -ttl = 367200 - -[BM-2cTpgCn57rYUgqm5BrgmykuV9gK1Ak1THF] -label = test1 -enabled = true -decoy = false -noncetrialsperbyte = 1000 -payloadlengthextrabytes = 1000 -privsigningkey = 5KYCPJ4Vp31UD6k5NWmDKtHhfapW25UJ7V2MjctYxcgL3BpWGA3 -privencryptionkey = 5JLER8q2zyj3KDEgGMv682en2SRUkkWWhUrNuqVYfGNNhHJmdkJ -lastpubkeysendtime = 1623160189 - -[BM-2cVrdzQjCQRqUuET6dc3byVyRTjZcgcJXj] -label = test2 -enabled = true -decoy = false -noncetrialsperbyte = 1000 -payloadlengthextrabytes = 1000 -privsigningkey = 5KhryWvNowFWWA9JRjQnLVStYKwhpKpAG4RtWwzyaQqmK2fTMue -privencryptionkey = 5JKQ9NqX2LRzHBCgyxc1GAL3rDvyDTHPifpL22a6UNN7K6y9BmL -lastpubkeysendtime = 1623160221 - diff --git a/src/bitmessagekivy/tests/sampleData/knownnodes.dat b/src/bitmessagekivy/tests/sampleData/knownnodes.dat deleted file mode 100644 index 41ba8a28..00000000 --- a/src/bitmessagekivy/tests/sampleData/knownnodes.dat +++ /dev/null @@ -1,110 +0,0 @@ -[ - { - "stream": 1, - "peer": { - "host": "5.45.99.75", - "port": 8444 - }, - "info": { - "lastseen": 1620741290.255359, - "rating": 0, - "self": false - } - }, - { - "stream": 1, - "peer": { - "host": "75.167.159.54", - "port": 8444 - }, - "info": { - "lastseen": 1620741290.255359, - "rating": 0, - "self": false - } - }, - { - "stream": 1, - "peer": { - "host": "95.165.168.168", - "port": 8444 - }, - "info": { - "lastseen": 1620741290.255359, - "rating": 0, - "self": false - } - }, - { - "stream": 1, - "peer": { - "host": "85.180.139.241", - "port": 8444 - }, - "info": { - "lastseen": 1620741290.255359, - "rating": 0, - "self": false - } - }, - { - "stream": 1, - "peer": { - "host": "158.222.217.190", - "port": 8080 - }, - "info": { - "lastseen": 1620741290.255359, - "rating": 0, - "self": false - } - }, - { - "stream": 1, - "peer": { - "host": "178.62.12.187", - "port": 8448 - }, - "info": { - "lastseen": 1620741290.255359, - "rating": 0, - "self": false - } - }, - { - "stream": 1, - "peer": { - "host": "24.188.198.204", - "port": 8111 - }, - "info": { - "lastseen": 1620741290.255359, - "rating": 0, - "self": false - } - }, - { - "stream": 1, - "peer": { - "host": "109.147.204.113", - "port": 1195 - }, - "info": { - "lastseen": 1620741290.255359, - "rating": 0, - "self": false - } - }, - { - "stream": 1, - "peer": { - "host": "178.11.46.221", - "port": 8444 - }, - "info": { - "lastseen": 1620741290.255359, - "rating": 0, - "self": false - } - } -] \ No newline at end of file diff --git a/src/bitmessagekivy/tests/sampleData/messages.dat b/src/bitmessagekivy/tests/sampleData/messages.dat deleted file mode 100644 index 7150b42b..00000000 Binary files a/src/bitmessagekivy/tests/sampleData/messages.dat and /dev/null differ diff --git a/src/bitmessagekivy/tests/telenium_process.py b/src/bitmessagekivy/tests/telenium_process.py deleted file mode 100644 index 0a81044d..00000000 --- a/src/bitmessagekivy/tests/telenium_process.py +++ /dev/null @@ -1,121 +0,0 @@ -""" - Base class for telenium test cases which run kivy app as background process -""" - -import os -import shutil -import tempfile -from time import time, sleep - -from telenium.tests import TeleniumTestCase -from telenium.client import TeleniumHttpException - - -_files = ( - 'keys.dat', 'debug.log', 'messages.dat', 'knownnodes.dat', - '.api_started', 'unittest.lock' -) - -tmp_db_file = ( - 'keys.dat', 'messages.dat' -) - - -def cleanup(files=_files): - """Cleanup application files""" - for pfile in files: - try: - os.remove(os.path.join(tempfile.gettempdir(), pfile)) - except OSError: - pass - - -class TeleniumTestProcess(TeleniumTestCase): - """Setting Screen Functionality Testing""" - cmd_entrypoint = [os.path.join(os.path.abspath(os.getcwd()), 'src', 'mockbm', 'kivy_main.py')] - - @classmethod - def setUpClass(cls): - """Setupclass is for setting temp environment""" - os.environ["BITMESSAGE_HOME"] = tempfile.gettempdir() - cls.populate_test_data() - super(TeleniumTestProcess, cls).setUpClass() - - @staticmethod - def populate_test_data(): - """Set temp data in tmp directory""" - for file_name in tmp_db_file: - old_source_file = os.path.join( - os.path.abspath(os.path.dirname(__file__)), 'sampleData', file_name) - new_destination_file = os.path.join(os.environ['BITMESSAGE_HOME'], file_name) - shutil.copyfile(old_source_file, new_destination_file) - - @classmethod - def tearDownClass(cls): - """Ensures that pybitmessage stopped and removes files""" - # pylint: disable=no-member - super(TeleniumTestProcess, cls).tearDownClass() - cleanup() - - def assert_wait_no_except(self, selector, timeout=-1, value='inbox'): - """This method is to check the application is launched.""" - start = time() - deadline = start + timeout - while time() < deadline: - try: - if self.cli.getattr(selector, 'current') == value: - self.assertTrue(selector, value) - return - except TeleniumHttpException: - sleep(0.1) - continue - finally: - # Finally Sleep is used to make the menu button functionally available for the click process. - # (because screen transition is little bit slow) - sleep(0.2) - raise AssertionError("Timeout") - - def drag(self, xpath1, xpath2): - """this method is for dragging""" - self.cli.drag(xpath1, xpath2, 1) - self.cli.sleep(1) - - def assertCheckScrollDown(self, selector, timeout=-1): - """this method is for checking scroll""" - start = time() - while True: - scroll_distance = self.cli.getattr(selector, 'scroll_y') - if scroll_distance > 0.0: - self.assertGreaterEqual(scroll_distance, 0.0) - return True - if timeout == -1: - return False - if timeout > 0 and time() - start > timeout: - raise Exception("Timeout") - sleep(0.5) - - def assertCheckScrollUp(self, selector, timeout=-1): - """this method is for checking scroll UP""" - start = time() - while True: - scroll_distance = self.cli.getattr(selector, 'scroll_y') - if scroll_distance < 1.0: - self.assertGreaterEqual(scroll_distance, 0.0) - return True - if timeout == -1: - return False - if timeout > 0 and time() - start > timeout: - raise Exception("Timeout") - sleep(0.5) - - def open_side_navbar(self): - """Common method for opening Side navbar (Side Drawer)""" - # Checking the drawer is in 'closed' state - self.cli.execute('app.ContentNavigationDrawer.MDNavigationDrawer.opening_time=0') - self.assertExists('//MDNavigationDrawer[@status~=\"closed\"]', timeout=5) - # This is for checking the menu button is appeared - self.assertExists('//ActionTopAppBarButton[@icon~=\"menu\"]', timeout=5) - # this is for opening Nav drawer - self.cli.wait_click('//ActionTopAppBarButton[@icon=\"menu\"]', timeout=5) - # checking state of Nav drawer - self.assertExists("//MDNavigationDrawer[@state~=\"open\"]", timeout=5) diff --git a/src/bitmessagekivy/tests/test_addressbook.py b/src/bitmessagekivy/tests/test_addressbook.py deleted file mode 100644 index e61fef2a..00000000 --- a/src/bitmessagekivy/tests/test_addressbook.py +++ /dev/null @@ -1,155 +0,0 @@ -from .telenium_process import TeleniumTestProcess -from .common import skip_screen_checks -from .common import ordered - -test_address = { - 'invalid_address': 'BM-2cWmjntZ47WKEUtocrdvs19y5CivpKoi1', - 'autoresponder_address': 'BM-2cVWtdUzPwF7UNGDrZftWuHWiJ6xxBpiSP', - 'recipient': 'BM-2cVpswZo8rWLXDVtZEUNcDQvnvHJ6TLRYr', - 'sender': 'BM-2cVpswZo8rWLXDVtZEUNcDQvnvHJ6TLRYr' -} - - -class AddressBook(TeleniumTestProcess): - """AddressBook Screen Functionality Testing""" - test_label = 'Auto Responder' - test_subject = 'Test Subject' - test_body = 'Hey,This is draft Message Body from Address Book' - - # @skip_screen_checks - @ordered - def test_save_address(self): - """Saving a new Address On Address Book Screen/Window""" - # Checking current Screen(Inbox screen) - self.assert_wait_no_except('//ScreenManager[@current]', timeout=15, value='inbox') - # Method to open side navbar (written in telenium_process.py) - self.open_side_navbar() - # this is for scrolling Nav drawer - self.drag("//NavigationItem[@text=\"Sent\"]", "//NavigationItem[@text=\"Inbox\"]") - # assert for checking scroll function - self.assertCheckScrollDown('//ContentNavigationDrawer//ScrollView[0]', timeout=5) - # this is for opening addressbook screen - self.cli.wait_click('//NavigationItem[@text=\"Address Book\"]', timeout=5) - # This is for checking the Side nav Bar is closed - self.assertExists('//MDNavigationDrawer[@status~=\"closed\"]', timeout=5) - # Checking current screen - self.assertExists("//ScreenManager[@current=\"addressbook\"]", timeout=5) - # Check for rendered button - self.assertExists('//ActionTopAppBarButton[@icon=\"account-plus\"]', timeout=5) - # Click on "Account-Plus' Icon to open popup to save a new Address - self.cli.wait_click('//ActionTopAppBarButton[@icon=\"account-plus\"]', timeout=5) - # Checking the Label Field shows Validation for empty string - self.assertExists('//AddAddressPopup/BoxLayout[0]/MDTextField[@hint_text=\"Label\"][@text=\"\"]', timeout=5) - # Checking the Address Field shows Validation for empty string - self.assertExists('//AddAddressPopup/BoxLayout[0]/MDTextField[@hint_text=\"Address\"][@text=\"\"]', timeout=5) - # Add an address Label to label Field - self.cli.setattr('//AddAddressPopup/BoxLayout[0]/MDTextField[@hint_text=\"Label\"]', 'text', self.test_label) - # Checking the Label Field should not be empty - self.assertExists( - '//AddAddressPopup/BoxLayout[0]/MDTextField[0][@text=\"{}\"]'.format(self.test_label), timeout=5) - # Add Correct Address - self.cli.setattr( - '//AddAddressPopup/BoxLayout[0]/MDTextField[@hint_text=\"Address\"]', 'text', - test_address['autoresponder_address']) - # Checking the Address Field contains correct address - self.assertEqual( - self.cli.getattr('//AddAddressPopup/BoxLayout[0]/MDTextField[1][@text]', 'text'), - test_address['autoresponder_address']) - # Validating the Label field - self.assertExists( - '//AddAddressPopup/BoxLayout[0]/MDTextField[0][@text=\"{}\"]'.format(self.test_label), timeout=5) - # Validating the Valid Address is entered - self.assertExists( - '//AddAddressPopup/BoxLayout[0]/MDTextField[1][@text=\"{}\"]'.format( - test_address['autoresponder_address']), timeout=5) - # Checking cancel button - self.assertExists('//MDRaisedButton[@text=\"Cancel\"]', timeout=5) - # Click on 'Cancel' Button to dismiss the popup - self.cli.wait_click('//MDRaisedButton[@text=\"Cancel\"]', timeout=5) - # Checking current screen - self.assertExists("//ScreenManager[@current=\"addressbook\"]", timeout=5) - - @skip_screen_checks - @ordered - def test_dismiss_addressbook_popup(self): - """This method is to perform Dismiss add Address popup""" - # Checking the "Address saving" Popup is not opened - self.assertNotExists('//AddAddressPopup', timeout=5) - # Checking the "Add account" Button is rendered - self.assertExists('//ActionTopAppBarButton[@icon=\"account-plus\"]', timeout=6) - # Click on Account-Plus Icon to open popup for add Address - self.cli.wait_click('//ActionTopAppBarButton[@icon=\"account-plus\"]', timeout=5) - # Add Label to label Field - self.cli.setattr('//AddAddressPopup/BoxLayout[0]/MDTextField[0]', 'text', 'test_label2') - # Checking the Label Field should not be empty - self.assertExists( - '//AddAddressPopup/BoxLayout[0]/MDTextField[0][@text=\"{}\"]'.format('test_label2'), timeout=5) - # Add Address to Address Field - self.cli.setattr( - '//AddAddressPopup/BoxLayout[0]/MDTextField[1]', 'text', test_address['recipient']) - # Checking the Address Field should not be empty - self.assertExists( - '//AddAddressPopup/BoxLayout[0]/MDTextField[1][@text=\"{}\"]'.format(test_address['recipient']), - timeout=5) - # Checking for "Cancel" button is rendered - self.assertExists('//MDRaisedButton[@text=\"Cancel\"]', timeout=5) - # Click on 'Cancel' Button to dismiss the popup - self.cli.wait_click('//MDRaisedButton[@text=\"Cancel\"]', timeout=5) - # Check Current Screen (Address Book) - self.assertExists("//ScreenManager[@current=\"addressbook\"]", timeout=5) - - @skip_screen_checks - @ordered - def test_send_message_to_saved_address(self): - """This method is to send msg to the saved address from addressbook""" - # Checking the Message detail Dialog box is not opened - self.assertNotExists('//MDDialog', timeout=5) - # Checking the saved address is rendered - self.assertExists('//AddressBook/BoxLayout[0]//SwipeToDeleteItem[0]', timeout=5) - # Click on a Address to open address Details popup - self.cli.wait_click('//AddressBook/BoxLayout[0]//SwipeToDeleteItem[0]', timeout=5) - # Checking the Message detail Dialog is opened - self.assertExists('//MDDialog', timeout=5) - # Checking the buttons are rendered - self.assertExists('//MDRaisedButton', timeout=5) - # Click on the Send to message Button - self.cli.wait_click('//MDRaisedButton[0]', timeout=5) - # Redirected to message composer screen(create) - self.assertExists("//ScreenManager[@current=\"create\"]", timeout=5) - # Checking the Address is populated to recipient field when we try to send message to saved address. - self.assertExists( - '//DropDownWidget/ScrollView[0]//MyTextInput[@text="{}"]'.format( - test_address['autoresponder_address']), timeout=5) - # CLICK BACK-BUTTON - self.cli.wait_click('//MDToolbar/BoxLayout[0]/MDActionTopAppBarButton[@icon=\"arrow-left\"]', timeout=5) - # After Back press, redirected to 'inbox' screen - self.assertExists("//ScreenManager[@current=\"inbox\"]", timeout=8) - - @skip_screen_checks - @ordered - def test_delete_address_from_saved_address(self): - """Delete a saved Address from Address Book""" - # Method to open side navbar (written in telenium_process.py) - self.open_side_navbar() - # this is for opening setting screen - self.cli.wait_click('//NavigationItem[@text=\"Address Book\"]', timeout=5) - # checking state of Nav drawer(closed) - self.assertExists("//MDNavigationDrawer[@state~=\"close\"]", timeout=5) - # Checking current screen - self.assertExists("//ScreenManager[@current=\"addressbook\"]", timeout=8) - # Checking the Address is rendered - self.assertExists('//SwipeToDeleteItem[0]//TwoLineAvatarIconListItem', timeout=5) - # Waiting for the trash icon to be rendered - self.cli.wait('//MDList[0]//MDIconButton[@icon=\"trash-can\"]', timeout=5) - # Enable the trash icon - self.cli.setattr('//MDList[0]//MDIconButton[@disabled]', 'disabled', False) - # Swiping over the Address to delete - self.cli.wait_drag('//MDList[0]//AvatarSampleWidget', '//MDList[0]//TimeTagRightSampleWidget', 2, timeout=5) - # Click on trash icon to delete the Address. - self.cli.wait_click('//MDList[0]//MDIconButton[@icon=\"trash-can\"]', timeout=5) - # Checking the deleted Address is disappeared - self.assertNotExists('//SwipeToDeleteItem[0]//TwoLineAvatarIconListItem', timeout=5) - # Address count should be zero - self.assertEqual(len(self.cli.select('//SwipeToDeleteItem[0]//TwoLineAvatarIconListItem[0]')), 0) - # After Deleting, Screen is redirected to Address Book screen - self.assertExists("//ScreenManager[@current=\"addressbook\"]", timeout=8) diff --git a/src/bitmessagekivy/tests/test_allmail_message.py b/src/bitmessagekivy/tests/test_allmail_message.py deleted file mode 100644 index b08e0f29..00000000 --- a/src/bitmessagekivy/tests/test_allmail_message.py +++ /dev/null @@ -1,35 +0,0 @@ -from .telenium_process import TeleniumTestProcess -from .common import skip_screen_checks -from .common import ordered - - -class AllMailMessage(TeleniumTestProcess): - """AllMail Screen Functionality Testing""" - - # @skip_screen_checks - @ordered - def test_show_allmail_list(self): - """Show All Messages on Mail Screen/Window""" - # This is for checking Current screen - self.assert_wait_no_except('//ScreenManager[@current]', timeout=15, value='inbox') - # Method to open side navbar - self.open_side_navbar() - # this is for opening All Mail screen - self.cli.wait_click('//NavigationItem[@text=\"All Mails\"]', timeout=5) - self.assertExists('//MDNavigationDrawer[@status~=\"closed\"]', timeout=5) - # Assert for checking Current Screen(All mail) - self.assertExists("//ScreenManager[@current=\"allmails\"]", timeout=5) - - @skip_screen_checks - @ordered - def test_delete_message_from_allmail_list(self): - """Delete Message From Message body of Mail Screen/Window""" - # click on a Message to get message details screen - self.cli.wait_click( - '//MDList[0]/CustomSwipeToDeleteItem[0]', timeout=5) - # Assert for checking Current Screen(Mail Detail) - self.assertExists("//ScreenManager[@current=\"mailDetail\"]", timeout=5) - # CLicking on Trash-Can icon to delete Message - self.cli.wait_click('//MDToolbar/BoxLayout[2]/MDActionTopAppBarButton[@icon=\"delete-forever\"]', timeout=5) - # After deleting msg, screen is redirected to All mail screen - self.assertExists("//ScreenManager[@current=\"allmails\"]", timeout=5) diff --git a/src/bitmessagekivy/tests/test_chat_screen.py b/src/bitmessagekivy/tests/test_chat_screen.py deleted file mode 100644 index 98464bf3..00000000 --- a/src/bitmessagekivy/tests/test_chat_screen.py +++ /dev/null @@ -1,26 +0,0 @@ -from .telenium_process import TeleniumTestProcess -from .common import ordered - - -class ChatScreen(TeleniumTestProcess): - """Chat Screen Functionality Testing""" - - @ordered - def test_open_chat_screen(self): - """Opening Chat screen""" - # Checking current Screen(Inbox screen) - self.assert_wait_no_except('//ScreenManager[@current]', timeout=15, value='inbox') - # Method to open side navbar - self.open_side_navbar() - # this is for scrolling Nav drawer - self.drag("//NavigationItem[@text=\"Sent\"]", "//NavigationItem[@text=\"Inbox\"]") - # assert for checking scroll function - self.assertCheckScrollDown('//ContentNavigationDrawer//ScrollView[0]', timeout=10) - # Checking Chat screen label on side nav bar - self.assertExists('//NavigationItem[@text=\"Chat\"]', timeout=5) - # this is for opening Chat screen - self.cli.wait_click('//NavigationItem[@text=\"Chat\"]', timeout=5) - # Checking navigation bar state - self.assertExists('//MDNavigationDrawer[@status~=\"closed\"]', timeout=5) - # Checking current screen - self.assertExists("//Chat[@name~=\"chat\"]", timeout=5) diff --git a/src/bitmessagekivy/tests/test_create_random_address.py b/src/bitmessagekivy/tests/test_create_random_address.py deleted file mode 100644 index 691a9d46..00000000 --- a/src/bitmessagekivy/tests/test_create_random_address.py +++ /dev/null @@ -1,129 +0,0 @@ -""" - Test for creating new identity -""" - -from random import choice -from string import ascii_lowercase -from .telenium_process import TeleniumTestProcess -from .common import ordered - - -class CreateRandomAddress(TeleniumTestProcess): - """This is for testing randrom address creation""" - @staticmethod - def populate_test_data(): - pass - - @ordered - # This method tests the landing screen when the app runs first time and - # the landing screen should be "login" where we can create new address - def test_landing_screen(self): - """Click on Proceed Button to Proceed to Next Screen.""" - # Checking current Screen(Login screen) - self.assert_wait_no_except('//ScreenManager[@current]', timeout=15, value='login') - # Dragging from sent to PROS: to NOTE: - self.drag( - '''//Login//Screen//ContentHead[1][@section_name=\"PROS:\"]''', - '''//Login//Screen//ContentHead[0][@section_name=\"NOTE:\"]''' - ) - # Assert the checkbox is rendered - self.assertExists( - '//Login//Screen[@name=\"check_screen\"]//AnchorLayout[1]/Check[@active=false]', timeout=5 - ) - # Clicking on the checkbox - self.cli.wait_click( - '//Login//Screen[@name=\"check_screen\"]//AnchorLayout[1]/Check', timeout=5 - ) - # Checking Status of checkbox after click - self.assertExists( - '//Login//Screen[@name=\"check_screen\"]//AnchorLayout[1]/Check[@active=true]', timeout=5 - ) - # Checking the Proceed Next button is rendered or not - self.assertExists( - '''//Login//Screen[@name=\"check_screen\"]''' - '''//MDFillRoundFlatIconButton[@text=\"Proceed Next\"]''', timeout=5 - ) - # Clicking on Proceed Next Button to redirect to "random" screen - self.cli.wait_click( - '''//Login//Screen[@name=\"check_screen\"]''' - '''//MDFillRoundFlatIconButton[@text=\"Proceed Next\"]''', timeout=5 - ) - self.assertExists("//ScreenManager[@current=\"random\"]", timeout=5) - - @ordered - def test_generate_random_address_label(self): - """Creating New Adress For New User.""" - # Checking the Button is rendered - self.assertExists( - '//Random//RandomBoxlayout//MDTextField[@hint_text=\"Label\"]', timeout=5) - # Click on Label Text Field to give address Label - self.cli.wait_click( - '//Random//RandomBoxlayout//MDTextField[@hint_text=\"Label\"]', timeout=5) - # Enter a Label Randomly - random_label = "" - for _ in range(10): - random_label += choice(ascii_lowercase) - self.cli.setattr('//Random//MDTextField[0]', "text", random_label) - self.cli.sleep(0.1) - # Checking the Button is rendered - self.assertExists( - '//Random//RandomBoxlayout//MDFillRoundFlatIconButton[@text=\"Proceed Next\"]', timeout=5) - # Click on Proceed Next button to generate random Address - self.cli.wait_click( - '//Random//RandomBoxlayout//MDFillRoundFlatIconButton[@text=\"Proceed Next\"]', timeout=5) - # Checking "My Address" Screen after creating a address - self.assertExists("//ScreenManager[@current=\"myaddress\"]", timeout=5) - # Checking the new address is created - self.assertExists('//MyAddress//MDList[0]/CustomTwoLineAvatarIconListItem', timeout=10) - - @ordered - def test_set_default_address(self): - """Select First Address From Drawer-Box""" - # Checking current screen - self.assertExists("//ScreenManager[@current=\"myaddress\"]", timeout=5) - # This is for opening side navbar - self.open_side_navbar() - # Click to open Address Dropdown - self.assertExists('//NavigationItem[0][@text=\"dropdown_nav_item\"]', timeout=5) - self.assertExists( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]' - '/IdentitySpinner[@name=\"identity_dropdown\"]', timeout=5 - ) - self.assertExists( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]' - '/IdentitySpinner[@name=\"identity_dropdown\"][@is_open=false]', timeout=5 - ) - self.cli.wait( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]' - '/IdentitySpinner[@name=\"identity_dropdown\"][@state=\"normal\"]', timeout=5 - ) - # Click to open Address Dropdown - self.cli.wait_click( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]' - '/IdentitySpinner[@name=\"identity_dropdown\"]', timeout=5 - ) - self.cli.wait( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]' - '/IdentitySpinner[@name=\"identity_dropdown\"][@state=\"normal\"]', timeout=5 - ) - # Check the state of dropdown. - self.assertExists( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]' - '/IdentitySpinner[@name=\"identity_dropdown\"][@is_open=true]', timeout=5 - ) - # List of addresses - addresses_in_dropdown = self.cli.getattr( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]/IdentitySpinner[@values]', 'values' - ) - # Checking the dropdown options are exists - self.assertGreaterEqual(len(self.cli.getattr( - '//MySpinnerOption[@text]', 'text')), len(addresses_in_dropdown) - ) - # Selection of an address to set as a default address. - self.cli.wait_click('//MySpinnerOption[0]', timeout=5) - - self.assertExists( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]' - '/IdentitySpinner[@name=\"identity_dropdown\"][@is_open=false]', timeout=5 - ) - self.cli.sleep(0.5) diff --git a/src/bitmessagekivy/tests/test_draft_message.py b/src/bitmessagekivy/tests/test_draft_message.py deleted file mode 100644 index e876f418..00000000 --- a/src/bitmessagekivy/tests/test_draft_message.py +++ /dev/null @@ -1,158 +0,0 @@ -from .telenium_process import TeleniumTestProcess -from .common import skip_screen_checks -from .common import ordered - -test_address = { - 'receiver': 'BM-2cVWtdUzPwF7UNGDrZftWuHWiJ6xxBpiSP' -} - - -class DraftMessage(TeleniumTestProcess): - """Draft Screen Functionality Testing""" - test_subject = 'Test Subject text' - test_body = 'Hey, This is draft Message Body' - - @ordered - def test_draft_screen(self): - """Test draft screen is open""" - # Checking current Screen(Inbox screen) - self.assert_wait_no_except('//ScreenManager[@current]', timeout=15, value='inbox') - # Method to open side navbar - self.open_side_navbar() - # this is for opening Draft screen - self.cli.wait_click('//NavigationItem[@text=\"Draft\"]', timeout=5) - # Checking the drawer is in 'closed' state - self.assertExists('//MDNavigationDrawer[@status~=\"closed\"]', timeout=5) - # Checking Draft Screen - self.assertExists("//ScreenManager[@current=\"draft\"]", timeout=5) - - @skip_screen_checks - @ordered - def test_save_message_to_draft(self): - """ - Saving a message to draft box when click on back button - """ - # Checking current Screen - self.assert_wait_no_except('//ScreenManager[@current]', timeout=10, value='inbox') - # Click on Composer Icon(Plus icon) - self.cli.wait_click('//ComposerButton[0]/MDFloatingActionButton[@icon=\"plus\"]', timeout=5) - # Checking Message Composer Screen(Create) - self.assertExists("//ScreenManager[@current=\"create\"]", timeout=5) - # ADD SUBJECT - self.cli.setattr('//DropDownWidget/ScrollView[0]//MyMDTextField[0]', 'text', self.test_subject) - # Checking Subject Field is Entered - self.assertExists( - '//DropDownWidget/ScrollView[0]//MyMDTextField[@text=\"{}\"]'.format(self.test_subject), timeout=5) - # ADD MESSAGE BODY - self.cli.setattr( - '//DropDownWidget/ScrollView[0]/BoxLayout[0]/ScrollView[0]/MDTextField[@text]', - 'text', self.test_body) - # Checking Message body is Entered - self.assertExists( - '//DropDownWidget/ScrollView[0]/BoxLayout[0]/ScrollView[0]/MDTextField[@text=\"{}\"]'.format( - self.test_body), timeout=5) - # Click on "Send" Icon - self.cli.wait_click('//MDActionTopAppBarButton[@icon=\"send\"]', timeout=5) - # Checking validation Pop up is Opened - self.assertExists('//MDDialog[@open]', timeout=5) - # checking the button is rendered - self.assertExists('//MDFlatButton[@text=\"Ok\"]', timeout=5) - # Click "OK" button to dismiss the Popup - self.cli.wait_click('//MDFlatButton[@text=\"Ok\"]', timeout=5) - # Checking validation Pop up is Closed - self.assertNotExists('//MDDialog[@open]', timeout=5) - # RECEIVER FIELD - # Checking Receiver Address Field - self.assertExists('//DropDownWidget/ScrollView[0]//MyTextInput[@text=\"\"]', timeout=5) - # Entering Receiver Address - self.cli.setattr( - '//DropDownWidget/ScrollView[0]//MyTextInput[0]', "text", test_address['auto_responder']) - # Checking Receiver Address filled or not - self.assertExists( - '//DropDownWidget/ScrollView[0]//MyTextInput[@text=\"{}\"]'.format(test_address['auto_responder']), - timeout=5) - # Checking the sender's Field is empty - self.assertExists('//DropDownWidget/ScrollView[0]//BoxLayout[1]/MDTextField[@text=\"\"]', timeout=5) - # Assert to check Sender's address dropdown open or not - self.assertEqual(self.cli.getattr('//Create//CustomSpinner[@is_open]', 'is_open'), False) - # Open Sender's Address DropDown - self.cli.wait_click('//Create//CustomSpinner[0]/ArrowImg[0]', timeout=5) - # Checking the status of dropdown - self.assertEqual(self.cli.getattr('//Create//CustomSpinner[@is_open]', 'is_open'), False) - # Checking the dropdown option is rendered - self.assertExists('//ComposerSpinnerOption[0]', timeout=5) - # Select Sender's Address from Dropdown options - self.cli.wait_click('//ComposerSpinnerOption[0]', timeout=5) - # Assert to check Sender address dropdown closed - self.assertEqual(self.cli.getattr('//Create//CustomSpinner[@is_open]', 'is_open'), False) - # Checking sender address is selected - sender_address = self.cli.getattr('//DropDownWidget/ScrollView[0]//BoxLayout[1]/MDTextField[@text]', 'text') - self.assertExists( - '//DropDownWidget/ScrollView[0]//BoxLayout[1]/MDTextField[@text=\"{}\"]'.format(sender_address), timeout=5) - # CLICK BACK-BUTTON - self.cli.wait_click('//MDToolbar/BoxLayout[0]/MDActionTopAppBarButton[@icon=\"arrow-left\"]', timeout=5) - # Checking current screen(Login) after "BACK" Press - self.assertExists("//ScreenManager[@current=\"inbox\"]", timeout=5) - - @skip_screen_checks - @ordered - def test_edit_and_resend_draft_messgae(self): - """Click on a Drafted message to send message""" - # OPEN NAVIGATION-DRAWER - # this is for opening Nav drawer - self.open_side_navbar() - # Checking messages in draft box - self.assertEqual(len(self.cli.select('//SwipeToDeleteItem[0]//TwoLineAvatarIconListItem')), 1) - # Wait to render the widget - self.cli.wait('//SwipeToDeleteItem[0]//TwoLineAvatarIconListItem[0]', timeout=5) - # Click on a Message to view its details (Message Detail screen) - self.cli.wait_click('//SwipeToDeleteItem[0]//TwoLineAvatarIconListItem[0]', timeout=5) - # Checking current screen Mail Detail - self.assertExists("//ScreenManager[@current=\"mailDetail\"]", timeout=5) - - # CLICK on EDIT(Pencil) BUTTON - self.cli.wait_click('//MDToolbar/BoxLayout[2]/MDActionTopAppBarButton[@icon=\"pencil\"]', timeout=5) - # Checking Current Screen 'Create'; composer screen. - self.assertExists("//ScreenManager[@current=\"create\"]", timeout=10) - # Checking the recipient is in the receiver field - self.assertExists( - '//DropDownWidget/ScrollView[0]//MyTextInput[@text=\"{}\"]'.format(test_address['auto_responder']), - timeout=5) - # Checking the sender address is in the sender field - sender_address = self.cli.getattr('//DropDownWidget/ScrollView[0]//BoxLayout[1]/MDTextField[@text]', 'text') - self.assertExists( - '//DropDownWidget/ScrollView[0]//BoxLayout[1]/MDTextField[@text=\"{}\"]'.format(sender_address), timeout=5) - # Checking the subject text is in the subject field - self.assertExists( - '//DropDownWidget/ScrollView[0]//MyMDTextField[@text=\"{}\"]'.format(self.test_subject), timeout=5) - # Checking the Body text is in the Body field - self.assertExists( - '//DropDownWidget/ScrollView[0]//ScrollView[0]/MDTextField[@text=\"{}\"]'.format(self.test_body), - timeout=5) - # CLICK BACK-BUTTON to autosave msg in draft screen - self.cli.wait_click('//MDToolbar/BoxLayout[0]/MDActionTopAppBarButton[@icon=\"arrow-left\"]', timeout=5) - # Checking current screen(Login) after BACK Press - self.assertExists("//ScreenManager[@current=\"draft\"]", timeout=5) - - @skip_screen_checks - @ordered - def test_delete_draft_message(self): - """Deleting a Drafted Message""" - # Checking current screen is Draft Screen - self.assertExists("//ScreenManager[@current=\"draft\"]", timeout=5) - # Cheking the Message is rendered - self.assertExists('//SwipeToDeleteItem[0]//TwoLineAvatarIconListItem[0]', timeout=5) - # Enable the trash icon - self.cli.setattr('//MDList[0]//MDIconButton[@disabled]', 'disabled', False) - # Waiting for the trash icon to be rendered - self.cli.wait('//MDList[0]//MDIconButton[@icon=\"trash-can\"]', timeout=5) - # Swiping over the message to delete - self.cli.wait_drag('//MDList[0]//AvatarSampleWidget', '//MDList[0]//TimeTagRightSampleWidget', 2, timeout=5) - # Click on trash icon to delete the message. - self.cli.wait_click('//MDList[0]//MDIconButton[@icon=\"trash-can\"]', timeout=5) - # Checking the deleted message is disappeared - self.assertNotExists('//SwipeToDeleteItem[0]//TwoLineAvatarIconListItem', timeout=5) - # Message count should be zero - self.assertEqual(len(self.cli.select('//SwipeToDeleteItem[0]//TwoLineAvatarIconListItem[0]')), 0) - # After Deleting, Screen is redirected to Draft screen - self.assertExists("//ScreenManager[@current=\"draft\"]", timeout=8) diff --git a/src/bitmessagekivy/tests/test_filemanager.py b/src/bitmessagekivy/tests/test_filemanager.py deleted file mode 100644 index 6d25553c..00000000 --- a/src/bitmessagekivy/tests/test_filemanager.py +++ /dev/null @@ -1,68 +0,0 @@ -from .telenium_process import TeleniumTestProcess -from .common import ordered - - -class FileManagerOpening(TeleniumTestProcess): - """File-manager Opening Functionality Testing""" - @ordered - def test_open_file_manager(self): - """Opening and Closing File-manager""" - # Checking current Screen(Inbox screen) - self.assert_wait_no_except('//ScreenManager[@current]', timeout=15, value='inbox') - # Method to open side navbar - self.open_side_navbar() - # Click to open Address Dropdown - self.assertExists('//NavigationItem[0][@text=\"dropdown_nav_item\"]', timeout=5) - self.assertExists( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]' - '/IdentitySpinner[@name=\"identity_dropdown\"]', timeout=1 - ) - # Check the state of dropdown - self.assertExists( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]' - '/IdentitySpinner[@name=\"identity_dropdown\"][@is_open=false]', timeout=1 - ) - self.cli.wait( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]' - '/IdentitySpinner[@name=\"identity_dropdown\"][@state=\"normal\"]', timeout=5 - ) - self.cli.wait_click( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]' - '/IdentitySpinner[@name=\"identity_dropdown\"]', timeout=1 - ) - # Check the state of dropdown. - self.assertExists( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]' - '/IdentitySpinner[@name=\"identity_dropdown\"][@is_open=true]', timeout=1 - ) - # List of addresses - addresses_in_dropdown = self.cli.getattr( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]/IdentitySpinner[@values]', 'values' - ) - # Checking the dropdown options are exists - self.assertGreaterEqual(len(self.cli.getattr( - '//MySpinnerOption[@text]', 'text')), len(addresses_in_dropdown) - ) - # Selection of an address to set as a default address. - self.cli.wait_click('//MySpinnerOption[0]', timeout=5) - # this is for scrolling Nav drawer - self.drag("//NavigationItem[@text=\"Sent\"]", "//NavigationItem[@text=\"Inbox\"]") - # assert for checking scroll function - self.assertCheckScrollDown('//ContentNavigationDrawer//ScrollView[0]', timeout=5) - # checking state of Nav drawer - self.assertExists("//MDNavigationDrawer[@state~=\"open\"]", timeout=5) - # Checking File-manager icon - self.assertExists( - '//ContentNavigationDrawer//MDIconButton[1][@icon=\"file-image\"]', - timeout=5 - ) - # Clicking on file manager icon - self.cli.wait_click( - '//ContentNavigationDrawer//MDIconButton[1][@icon=\"file-image\"]', - timeout=5) - # Checking the state of file manager is it open or not - self.assertTrue(self.cli.execute('app.file_manager_open')) - # Closing the filemanager - self.cli.execute('app.exit_manager()') - # Checking the state of file manager is it closed or not - self.assertTrue(self.cli.execute('app.exit_manager()')) diff --git a/src/bitmessagekivy/tests/test_load_screen_data_file.py b/src/bitmessagekivy/tests/test_load_screen_data_file.py deleted file mode 100644 index 619daf25..00000000 --- a/src/bitmessagekivy/tests/test_load_screen_data_file.py +++ /dev/null @@ -1,21 +0,0 @@ - -import unittest -from pybitmessage.bitmessagekivy.load_kivy_screens_data import load_screen_json -from .common import ordered - - -class TestLoadScreenData(unittest.TestCase): - """Screen Data Json test""" - - @ordered - def test_load_json(self): - """Test to load a valid json""" - loaded_screen_names = load_screen_json() - self.assertEqual(loaded_screen_names[3], 'success') - - @ordered - def test_load_invalid_file(self): - """Test to load an invalid json""" - file_name = 'invalid_screens_data.json' - with self.assertRaises(OSError): - load_screen_json(file_name) diff --git a/src/bitmessagekivy/tests/test_myaddress_screen.py b/src/bitmessagekivy/tests/test_myaddress_screen.py deleted file mode 100644 index 78740b79..00000000 --- a/src/bitmessagekivy/tests/test_myaddress_screen.py +++ /dev/null @@ -1,187 +0,0 @@ -from .telenium_process import TeleniumTestProcess -from .common import ordered - - -data = [ - 'BM-2cWmjntZ47WKEUtocrdvs19y5CivpKoi1h', - 'BM-2cVpswZo8rWLXDVtZEUNcDQvnvHJ6TLRYr' -] - - -class MyAddressScreen(TeleniumTestProcess): - """MyAddress Screen Functionality Testing""" - @ordered - def test_myaddress_screen(self): - """Open MyAddress Screen""" - # Checking current Screen(Inbox screen) - self.assert_wait_no_except('//ScreenManager[@current]', timeout=15, value='inbox') - # Method to open side navbar - self.open_side_navbar() - # this is for scrolling Nav drawer - self.drag("//NavigationItem[@text=\"Sent\"]", "//NavigationItem[@text=\"Inbox\"]") - # assert for checking scroll function - self.assertCheckScrollDown('//ContentNavigationDrawer//ScrollView[0]', timeout=10) - # Checking My address label on side nav bar - self.assertExists('//NavigationItem[@text=\"My addresses\"]', timeout=5) - # this is for opening setting screen - self.cli.wait_click('//NavigationItem[@text=\"My addresses\"]', timeout=5) - self.assertExists('//MDNavigationDrawer[@status~=\"closed\"]', timeout=5) - # Checking current screen - self.assertExists("//MyAddress[@name~=\"myaddress\"]", timeout=5) - - @ordered - def test_disable_address(self): - """Disable Addresses""" - # Dragging for loading addreses - self.drag( - '//MyAddress//MDList[0]/CustomTwoLineAvatarIconListItem[@text=\"test2\"]', - '//MyAddress//MDList[0]/CustomTwoLineAvatarIconListItem[@text=\"test1\"]' - ) - self.assertCheckScrollDown('//ContentNavigationDrawer//ScrollView[0]', timeout=10) - # Checking list of Addresses - self.assertExists("//MyAddress//CustomTwoLineAvatarIconListItem", timeout=5) - # Checking the Toggle button is rendered on addresses - self.assertExists("//MyAddress//CustomTwoLineAvatarIconListItem//ToggleBtn", timeout=5) - # Clicking on the Toggle button of first address to make it disable - self.assertExists( - "//MyAddress//CustomTwoLineAvatarIconListItem[@text=\"test2\"]//ToggleBtn[@active=true]", - timeout=5 - ) - # Clicking on Toggle button of first address - self.cli.wait_click( - "//MyAddress//CustomTwoLineAvatarIconListItem[@text=\"test2\"]//ToggleBtn/Thumb", - timeout=5 - ) - # Checking the address is disabled - self.assertExists( - "//MyAddress//CustomTwoLineAvatarIconListItem[@text=\"test2\"]//ToggleBtn[@active=false]", - timeout=5 - ) - # CLICKING ON DISABLE ACCOUNT TO OPEN POPUP - self.cli.wait_click("//MyAddress//CustomTwoLineAvatarIconListItem[@text=\"test2\"]", timeout=5) - # Checking the popup is Opened - self.assertExists( - '//MDDialog[@text=\"Address is not currently active. Please click on Toggle button to active it.\"]', - timeout=5 - ) - # Clicking on 'Ok' Button To Dismiss the popup - self.cli.wait_click('//MDFlatButton[@text=\"Ok\"]', timeout=5) - # Checking the address is disabled - self.assertExists( - "//MyAddress//CustomTwoLineAvatarIconListItem[@text=\"test2\"]//ToggleBtn[@active=false]", - timeout=5 - ) - - @ordered - def test_show_Qrcode(self): - """Show the Qr code of selected address""" - # Checking the current screen is MyAddress - self.assertExists("//MyAddress[@name~=\"myaddress\"]", timeout=5) - # Checking first label - self.assertExists( - '//MyAddress//MDList[0]/CustomTwoLineAvatarIconListItem[1][@text=\"test1\"]', - timeout=5 - ) - # Checking second label - self.assertExists( - '//MyAddress//MDList[0]/CustomTwoLineAvatarIconListItem[0][@text=\"test2\"]', - timeout=5 - ) - self.assertExists( - "//MyAddress//CustomTwoLineAvatarIconListItem[@text=\"test2\"]//ToggleBtn[@active=false]", - timeout=5 - ) - self.assertExists( - "//MyAddress//CustomTwoLineAvatarIconListItem[@text=\"test1\"]//ToggleBtn[@active=true]", - timeout=5 - ) - # Click on Address to open popup - self.cli.wait_click('//MDList[0]/CustomTwoLineAvatarIconListItem[@text=\"test1\"]', timeout=5) - # Check the Popup is opened - self.assertExists('//MyaddDetailPopup//MDLabel[@text=\"Show QR code\"]', timeout=5) - # Cick on 'Show QR code' button to view QR Code - self.cli.wait_click('//MyaddDetailPopup//MDLabel[@text=\"Show QR code\"]', timeout=5) - # Check Current screen is QR Code screen - self.assertExists("//ShowQRCode[@name~=\"showqrcode\"]", timeout=5) - # Check BACK button - self.assertExists('//ActionTopAppBarButton[@icon~=\"arrow-left\"]', timeout=5) - # Click on BACK button - self.cli.wait_click('//ActionTopAppBarButton[@icon~=\"arrow-left\"]', timeout=5) - # Checking current screen(My Address) after BACK press - self.assertExists("//MyAddress[@name~=\"myaddress\"]", timeout=5) - # Checking first label - self.assertExists( - '//MyAddress//MDList[0]/CustomTwoLineAvatarIconListItem[1][@text=\"test1\"]', - timeout=5 - ) - # Checking second label - self.assertExists( - '//MyAddress//MDList[0]/CustomTwoLineAvatarIconListItem[0][@text=\"test2\"]', - timeout=5 - ) - self.cli.sleep(0.3) - - @ordered - def test_enable_address(self): - """Test to enable the disabled address""" - # Checking list of Addresses - self.assertExists("//MyAddress//CustomTwoLineAvatarIconListItem", timeout=5) - # Check the thumb button on address - self.assertExists( - "//MyAddress//CustomTwoLineAvatarIconListItem[@text=\"test2\"]//ToggleBtn/Thumb", - timeout=5 - ) - # Checking the address is disabled - self.assertExists( - "//MyAddress//CustomTwoLineAvatarIconListItem[@text=\"test2\"]//ToggleBtn[@active=false]", - timeout=5 - ) - self.cli.sleep(0.3) - # Clicking on toggle button to enable the address - self.cli.wait_click( - "//MyAddress//CustomTwoLineAvatarIconListItem[@text=\"test2\"]//ToggleBtn/Thumb", - timeout=10 - ) - # Checking the address is enabled - self.assertExists( - "//MyAddress//CustomTwoLineAvatarIconListItem[@text=\"test2\"]//ToggleBtn[@active=true]", - timeout=10 - ) - - @ordered - def test_send_message_from(self): - """Send Message From Send Message From Button""" - # this is for scrolling Myaddress screen - self.drag( - '//MyAddress//MDList[0]/CustomTwoLineAvatarIconListItem[@text=\"test2\"]', - '//MyAddress//MDList[0]/CustomTwoLineAvatarIconListItem[@text=\"test1\"]' - ) - # Checking the addresses - self.assertExists( - '//MyAddress//MDList[0]/CustomTwoLineAvatarIconListItem[@text=\"test1\"]', - timeout=5 - ) - # Click on Address to open popup - self.cli.wait_click('//MDList[0]/CustomTwoLineAvatarIconListItem[@text=\"test1\"]', timeout=5) - # Checking Popup Opened - self.assertExists('//MyaddDetailPopup//MDLabel[@text=\"Send message from\"]', timeout=5) - # Click on Send Message Button to redirect Create Screen - self.cli.wait_click('//MyaddDetailPopup//MDRaisedButton[0]/MDLabel[0]', timeout=5) - # Checking Current screen(Create) - self.assertExists("//Create[@name~=\"create\"]", timeout=5) - # Entering Receiver Address - self.cli.setattr( - '//DropDownWidget/ScrollView[0]//MyTextInput[0]', "text", data[1]) - # Checking Receiver Address filled or not - self.assertNotEqual('//DropDownWidget//MyTextInput[0]', '') - # ADD SUBJECT - self.cli.setattr('//DropDownWidget/ScrollView[0]//MyMDTextField[0]', 'text', 'Hey this is Demo Subject') - # Checking Subject Field is Entered - self.assertNotEqual('//DropDownWidget/ScrollView[0]//MyMDTextField[0]', '') - # ADD MESSAGE BODY - self.cli.setattr( - '//DropDownWidget/ScrollView[0]//ScrollView[0]/MDTextField[0]', - 'text', 'Hey,i am sending message directly from MyAddress book' - ) - # Checking Message body is Entered - self.assertNotEqual('//DropDownWidget/ScrollView[0]//ScrollView[0]/MDTextField[@text]', '') diff --git a/src/bitmessagekivy/tests/test_network_screen.py b/src/bitmessagekivy/tests/test_network_screen.py deleted file mode 100644 index ca398dc1..00000000 --- a/src/bitmessagekivy/tests/test_network_screen.py +++ /dev/null @@ -1,50 +0,0 @@ -# pylint: disable=too-few-public-methods -""" - Kivy Networkstat UI test -""" - -from .telenium_process import TeleniumTestProcess - - -class NetworkStatusScreen(TeleniumTestProcess): - """NetworkStatus Screen Functionality Testing""" - - def test_network_status(self): - """Show NetworkStatus""" - # This is for checking Current screen - self.assert_wait_no_except('//ScreenManager[@current]', timeout=15, value='inbox') - # Method to open side navbar - # due to rapid transition effect, it doesn't click on menu-bar - self.open_side_navbar() - # this is for scrolling Nav drawer - self.drag("//NavigationItem[@text=\"Sent\"]", "//NavigationItem[@text=\"Inbox\"]") - # assert for checking scroll function - self.assertCheckScrollDown('//ContentNavigationDrawer//ScrollView[0]', timeout=5) - # Clicking on Network Status tab - self.cli.wait_click('//NavigationItem[@text=\"Network status\"]', timeout=2) - # Checking the drawer is in 'closed' state - self.assertExists('//MDNavigationDrawer[@status~=\"closed\"]', timeout=5) - # Checking for current screen (Network Status) - self.assertExists("//NetworkStat[@name~=\"networkstat\"]", timeout=2) - # Checking state of Total Connections tab - self.assertExists( - '//NetworkStat/MDTabs[0]//MDTabsLabel[@text=\"Total connections\"][@state=\"down\"]', timeout=5 - ) - # Getting the value of total connections - total_connection_text = self.cli.getattr('//NetworkStat//MDRaisedButton[@text]', 'text') - # Splitting the string from total connection numbers - number_of_connections = int(total_connection_text.split(' ')[-1]) - # Checking Total connections - self.assertGreaterEqual(number_of_connections, 1) - # Checking the state of Process tab - self.assertExists( - '//NetworkStat/MDTabs[0]//MDTabsLabel[@text=\"Processes\"][@state=\"normal\"]', timeout=5 - ) - # Clicking on Processes Tab - self.cli.wait_click( - '//NetworkStat/MDTabs[0]//MDTabsLabel[@text=\"Processes\"]', timeout=1 - ) - # Checking the state of Process tab - self.assertExists( - '//NetworkStat/MDTabs[0]//MDTabsLabel[@text=\"Processes\"][@state=\"down\"]', timeout=5 - ) diff --git a/src/bitmessagekivy/tests/test_payment_subscription.py b/src/bitmessagekivy/tests/test_payment_subscription.py deleted file mode 100644 index 42309001..00000000 --- a/src/bitmessagekivy/tests/test_payment_subscription.py +++ /dev/null @@ -1,70 +0,0 @@ -from .telenium_process import TeleniumTestProcess -from .common import ordered - - -class PaymentScreen(TeleniumTestProcess): - """Payment Plan Screen Functionality Testing""" - - @ordered - def test_select_payment_plan(self): - """Select Payment plan From List of payments""" - # This is for checking Current screen - self.assert_wait_no_except('//ScreenManager[@current]', timeout=15, value='inbox') - # Method to open the side navbar - self.open_side_navbar() - # Dragging from sent to inbox to get Payment tab - self.drag("//NavigationItem[@text=\"Sent\"]", "//NavigationItem[@text=\"Inbox\"]") - # assert for checking scroll function - self.assertCheckScrollDown('//ContentNavigationDrawer//ScrollView[0]', timeout=10) - # this is for opening Payment screen - self.cli.wait_click('//NavigationItem[@text=\"Payment plan\"]', timeout=5) - # Checking the navbar is in closed state - self.assertExists('//MDNavigationDrawer[@status~=\"closed\"]', timeout=5) - # Assert for checking Current Screen - self.assertExists("//ScreenManager[@current=\"payment\"]", timeout=5) - # Checking state of Current tab Payment plan - self.assertExists( - '//Payment/MDTabs[0]//MDTabsLabel[@text=\"Payment\"][@state=\"down\"]', timeout=5 - ) - # Scrolling Down Payment plan Cards - self.drag( - '//Payment//MDTabs[0]//MDCard[2]//MDLabel[@text=\"Standard\"]', - '//Payment//MDTabs[0]//MDCard[1]//MDLabel[@text=\"You can get zero encrypted message per month\"]' - ) - # Checking the subscription offer cards - self.assertExists( - '//Payment/MDTabs[0]//MDCard[3]//MDLabel[@text=\"Premium\"]', - timeout=10 - ) - # Checking the get it now button - self.assertExists( - '//Payment/MDTabs[0]//MDCard[3]//MDRaisedButton[@text=\"Get it now\"]', - timeout=10 - ) - # Clicking on the get it now button - self.cli.wait_click( - '//Payment/MDTabs[0]//MDCard[3]//MDRaisedButton[@text=\"Get it now\"]', - timeout=10 - ) - # Checking the Payment method popup - self.assertExists('//PaymentMethodLayout//ScrollView[0]//ListItemWithLabel[0]', timeout=10) - # CLick on the Payment Method - self.cli.wait_click( - '//PaymentMethodLayout//ScrollView[0]//ListItemWithLabel[0]', - timeout=10 - ) - # Check pop up is opened - self.assertExists('//PaymentMethodLayout[@disabled=false]', timeout=10) - # Click out side to dismiss the popup - self.cli.wait_click('//MDRaisedButton[1]', timeout=10) - # Checking state of next tab Payment - self.assertExists( - '//Payment/MDTabs[0]//MDTabsLabel[@text=\"Extra-Messages\"][@state=\"normal\"]', timeout=5 - ) - # Clicking on Payment tab - self.cli.wait_click('//Payment/MDTabs[0]//MDTabsLabel[@text=\"Extra-Messages\"]', timeout=5) - # Checking state of payment tab after click - self.assertExists( - '//Payment/MDTabs[0]//MDTabsLabel[@text=\"Extra-Messages\"][@state=\"down\"]', timeout=5 - ) - self.cli.sleep(1) diff --git a/src/bitmessagekivy/tests/test_sent_message.py b/src/bitmessagekivy/tests/test_sent_message.py deleted file mode 100644 index a7fd576b..00000000 --- a/src/bitmessagekivy/tests/test_sent_message.py +++ /dev/null @@ -1,133 +0,0 @@ -from .telenium_process import TeleniumTestProcess -from .common import skip_screen_checks -from .common import ordered - -test_address = {'autoresponder_address': 'BM-2cVWtdUzPwF7UNGDrZftWuHWiJ6xxBpiSP'} - - -class SendMessage(TeleniumTestProcess): - """Sent Screen Functionality Testing""" - test_subject = 'Test Subject' - test_body = 'Hello, \n Hope your are doing good.\n\t This is test message body' - - @skip_screen_checks - @ordered - def test_validate_empty_form(self): - """ - Sending Message From Inbox Screen - opens a pop-up(screen) which send message from sender to reciever - """ - # Checking current Screen(Inbox screen) - self.assert_wait_no_except('//ScreenManager[@current]', timeout=10, value='inbox') - # Click on Composer Icon(Plus icon) - self.cli.wait_click('//ComposerButton[0]/MDFloatingActionButton[@icon=\"plus\"]', timeout=5) - # Checking Message Composer Screen(Create) - self.assertExists("//ScreenManager[@current=\"create\"]", timeout=5) - # Checking State of Sender's Address Input Field (should be Empty) - self.assertExists('//DropDownWidget/ScrollView[0]//MDTextField[@text=\"\"]', timeout=5) - # Checking State of Receiver's Address Input Field (should be Empty) - self.assertExists('//DropDownWidget/ScrollView[0]//MyTextInput[@text=\"\"]', timeout=5) - # Checking State of Subject Input Field (shoudl be Empty) - self.assertExists('//DropDownWidget/ScrollView[0]//MyMDTextField[@text=\"\"]', timeout=5) - # Click on Send Icon to check validation working - self.cli.wait_click('//MDActionTopAppBarButton[@icon=\"send\"]', timeout=5) - # Checking validation Pop up is Opened - self.assertExists('//MDDialog[@open]', timeout=5) - # Checking the 'Ok' Button is rendered - self.assertExists('//MDFlatButton[@text=\"Ok\"]', timeout=5) - # Click "OK" button to dismiss the Popup - self.cli.wait_click('//MDFlatButton[@text=\"Ok\"]', timeout=5) - # Checking the pop is closed - self.assertNotExists('//MDDialog[@open]', timeout=5) - # Checking current screen after dialog dismiss - self.assertExists("//ScreenManager[@current=\"create\"]", timeout=10) - - @skip_screen_checks - @ordered - def test_validate_half_filled_form(self): - """ - Validate the half filled form and press back button to save message in draft box. - """ - # Checking current screen (Msg composer screen) - self.assertExists("//ScreenManager[@current=\"create\"]", timeout=5) - # ADD SENDER'S ADDRESS - # Checking State of Sender's Address Input Field (Empty) - self.assertExists('//DropDownWidget/ScrollView[0]//MDTextField[@text=\"\"]', timeout=5) - # Assert to check Sender's address dropdown closed - is_open = self.cli.getattr('//Create//CustomSpinner[@is_open]', 'is_open') - self.assertEqual(is_open, False) - # Open Sender's Address DropDown - self.cli.wait_click('//Create//CustomSpinner[0]/ArrowImg[0]', timeout=5) - # Checking the Address Dropdown is in open State - is_open = self.cli.getattr('//Create//CustomSpinner[@is_open]', 'is_open') - # Select Sender's Address from Dropdown - self.cli.wait_click('//ComposerSpinnerOption[0]', timeout=5) - # Assert to check Sender's address dropdown closed - is_open = self.cli.getattr('//Create//CustomSpinner[@is_open]', 'is_open') - self.assertEqual(is_open, False) - # Checking the sender address is selected - sender_address = self.cli.getattr( - '//DropDownWidget/ScrollView[0]/BoxLayout[0]/ScrollView[0]/MDTextField[@text]', 'text') - self.assertExists( - '//DropDownWidget/ScrollView[0]//MDTextField[@text=\"{}\"]'.format(sender_address), timeout=5) - # Assert check for empty Subject Field - self.assertExists('//DropDownWidget/ScrollView[0]//MyMDTextField[@text=\"\"]', timeout=5) - # ADD SUBJECT - self.cli.setattr('//DropDownWidget/ScrollView[0]//MyMDTextField[0]', 'text', self.test_subject) - # Checking Subject Field is Entered - self.assertExists( - '//DropDownWidget/ScrollView[0]//MyMDTextField[@text=\"{}\"]'.format(self.test_subject), timeout=5) - # Checking BODY Field(EMPTY) - self.assertExists('//DropDownWidget/ScrollView[0]//ScrollView[0]/MDTextField[@text=\"\"]', timeout=5) - # ADD BODY - self.cli.setattr( - '//DropDownWidget/ScrollView[0]/BoxLayout[0]/ScrollView[0]/MDTextField[0]', 'text', self.test_body) - # Checking BODY is Entered - self.assertExists( - '//DropDownWidget/ScrollView[0]//ScrollView[0]/MDTextField[@text=\"{}\"]'.format(self.test_body), - timeout=5) - # click on send icon - self.cli.wait_click('//MDActionTopAppBarButton[@icon=\"send\"]', timeout=5) - # Checking validation Pop up is Opened - self.assertExists('//MDDialog', timeout=5) - # clicked on 'Ok' button to close popup - self.cli.wait_click('//MDFlatButton[@text=\"Ok\"]', timeout=5) - # Checking current screen after dialog dismiss - self.assertExists("//ScreenManager[@current=\"create\"]", timeout=5) - - @skip_screen_checks - @ordered - def test_sending_msg_fully_filled_form(self): - """ - Sending message when all fields are filled - """ - # ADD RECEIVER ADDRESS - # Checking Receiver Address Field - self.assertExists('//DropDownWidget/ScrollView[0]//MyTextInput[@text=\"\"]', timeout=5) - # Entering Receiver Address - self.cli.setattr( - '//DropDownWidget/ScrollView[0]//MyTextInput[0]', "text", test_address['autoresponder_address']) - # Checking Receiver Address filled or not - self.assertExists( - '//DropDownWidget/ScrollView[0]//MyTextInput[@text=\"{}\"]'.format( - test_address['autoresponder_address']), timeout=5) - # Clicking on send icon - self.cli.wait_click('//MDActionTopAppBarButton[@icon=\"send\"]', timeout=5) - # Checking the current screen - self.assertExists("//ScreenManager[@current=\"inbox\"]", timeout=10) - - @skip_screen_checks - @ordered - def test_sent_box(self): - """ - Checking Message in Sent Screen after sending a Message. - """ - # this is for opening Nav drawer - self.open_side_navbar() - # Clicking on Sent Tab - self.cli.wait_click('//NavigationItem[@text=\"Sent\"]', timeout=5) - # Checking current screen; Sent - self.assertExists("//ScreenManager[@current=\"sent\"]", timeout=5) - # Checking number of Sent messages - total_sent_msgs = len(self.cli.select("//SwipeToDeleteItem")) - self.assertEqual(total_sent_msgs, 3) diff --git a/src/bitmessagekivy/tests/test_setting_screen.py b/src/bitmessagekivy/tests/test_setting_screen.py deleted file mode 100644 index 4f3a0a59..00000000 --- a/src/bitmessagekivy/tests/test_setting_screen.py +++ /dev/null @@ -1,38 +0,0 @@ -# pylint: disable=too-few-public-methods - -from .telenium_process import TeleniumTestProcess - - -class SettingScreen(TeleniumTestProcess): - """Setting Screen Functionality Testing""" - - def test_setting_screen(self): - """Show Setting Screen""" - # This is for checking Current screen - self.assert_wait_no_except('//ScreenManager[@current]', timeout=15, value='inbox') - # Method to open side navbar - self.open_side_navbar() - # this is for scrolling Nav drawer - self.drag("//NavigationItem[@text=\"Sent\"]", "//NavigationItem[@text=\"Inbox\"]") - # assert for checking scroll function - self.assertCheckScrollDown('//ContentNavigationDrawer//ScrollView[0]', timeout=10) - # this is for opening setting screen - self.cli.wait_click('//NavigationItem[@text=\"Settings\"]', timeout=5) - self.assertExists('//MDNavigationDrawer[@status~=\"closed\"]', timeout=5) - # Checking current screen - self.assertExists("//ScreenManager[@current=\"set\"]", timeout=5) - # Scrolling down currrent screen - self.cli.wait_drag( - '//MDTabs[0]//MDLabel[@text=\"Close to tray\"]', - '//MDTabs[0]//MDLabel[@text=\"Minimize to tray\"]', 1, timeout=5) - # Checking state of 'Network Settings' sub tab should be 'normal'(inactive) - self.assertExists('//MDTabs[0]//MDTabsLabel[@text=\"Network Settings\"][@state=\"normal\"]', timeout=5) - # Click on "Network Settings" subtab - self.cli.wait_click('//MDTabs[0]//MDTabsLabel[@text=\"Network Settings\"]', timeout=5) - # Checking state of 'Network Settings' sub tab should be 'down'(active) - self.assertExists('//MDTabs[0]//MDTabsLabel[@text=\"Network Settings\"][@state=\"down\"]', timeout=5) - # Scrolling down currrent screen - self.cli.wait_drag( - '//MDTabs[0]//MDLabel[@text=\"Username:\"]', '//MDTabs[0]//MDLabel[@text=\"Port:\"]', 1, timeout=5) - # Checking state of 'Resends Expire' sub tab should be 'normal'(inactive) - self.assertExists('//MDTabs[0]//MDTabsLabel[@text=\"Resends Expire\"][@state=\"normal\"]', timeout=5) diff --git a/src/bitmessagekivy/tests/test_trash_message.py b/src/bitmessagekivy/tests/test_trash_message.py deleted file mode 100644 index d7cdb467..00000000 --- a/src/bitmessagekivy/tests/test_trash_message.py +++ /dev/null @@ -1,21 +0,0 @@ -from .telenium_process import TeleniumTestProcess -from .common import ordered - - -class TrashMessage(TeleniumTestProcess): - """Trash Screen Functionality Testing""" - - @ordered - def test_delete_trash_message(self): - """Delete Trash message permanently from trash message listing""" - # Checking current Screen(Inbox screen) - self.assert_wait_no_except('//ScreenManager[@current]', timeout=15, value='inbox') - # Method to open side navbar - self.open_side_navbar() - # this is for opening Trash screen - self.cli.wait_click('//NavigationItem[@text=\"Trash\"]', timeout=5) - # Checking the drawer is in 'closed' state - self.assertExists('//MDNavigationDrawer[@status~=\"closed\"]', timeout=5) - # Checking Trash Screen - self.assertExists("//ScreenManager[@current=\"trash\"]", timeout=5) - self.cli.sleep(0.5) diff --git a/src/bitmessagekivy/uikivysignaler.py b/src/bitmessagekivy/uikivysignaler.py deleted file mode 100644 index 6f73247e..00000000 --- a/src/bitmessagekivy/uikivysignaler.py +++ /dev/null @@ -1,38 +0,0 @@ -""" - UI Singnaler for kivy interface -""" - -import logging - -from threading import Thread -from kivy.app import App - -from pybitmessage import queues -from pybitmessage import state - -from pybitmessage.bitmessagekivy.baseclass.common import kivy_state_variables - -logger = logging.getLogger('default') - - -class UIkivySignaler(Thread): - """Kivy ui signaler""" - - def __init__(self, *args, **kwargs): - super(UIkivySignaler, self).__init__(*args, **kwargs) - self.kivy_state = kivy_state_variables() - - def run(self): - self.kivy_state.kivyui_ready.wait() - while state.shutdown == 0: - try: - command, data = queues.UISignalQueue.get() - if command == 'writeNewAddressToTable': - address = data[1] - App.get_running_app().identity_list.append(address) - elif command == 'updateSentItemStatusByAckdata': - App.get_running_app().status_dispatching(data) - elif command == 'writeNewpaymentAddressToTable': - pass - except Exception as e: - logger.debug(e) diff --git a/src/bitmessagemain.py b/src/bitmessagemain.py index 8d73786e..f8b85acf 100755 --- a/src/bitmessagemain.py +++ b/src/bitmessagemain.py @@ -1,391 +1,4083 @@ -#!/usr/bin/env python -""" -The PyBitmessage startup script -""" -# Copyright (c) 2012-2016 Jonathan Warren -# Copyright (c) 2012-2022 The Bitmessage developers +#!/usr/bin/env python2.7 +# Copyright (c) 2012 Jonathan Warren +# Copyright (c) 2012 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 +#Right now, PyBitmessage only support connecting to stream 1. It doesn't yet contain logic to expand into further streams. + +#The software version variable is now held in shared.py +verbose = 1 +maximumAgeOfAnObjectThatIAmWillingToAccept = 216000 #Equals two days and 12 hours. +lengthOfTimeToLeaveObjectsInInventory = 237600 #Equals two days and 18 hours. This should be longer than maximumAgeOfAnObjectThatIAmWillingToAccept so that we don't process messages twice. +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. +maximumAgeOfObjectsThatIAdvertiseToOthers = 216000 #Equals two days and 12 hours +maximumAgeOfNodesThatIAdvertiseToOthers = 10800 #Equals three hours +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. +useVeryEasyProofOfWorkForTesting = False #If you set this to True while on the normal network, you won't be able to send or sometimes receive messages. +encryptedBroadcastSwitchoverTime = 1369735200 + import sys - -try: - import pathmagic -except ImportError: - from pybitmessage import pathmagic -app_dir = pathmagic.setup() - -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 ConfigParser +import Queue +from addresses import * +#from shared import * +import shared +from defaultKnownNodes import * import time -import traceback +import socket +import threading +import hashlib +from struct import * +import pickle +import random +import sqlite3 +import threading +from time import strftime, localtime, gmtime +import shutil #used for moving the messages.dat file +import string +import socks +import highlevelcrypto +from pyelliptic.openssl import OpenSSL +import ctypes +from pyelliptic import arithmetic +import signal #Used to capture a Ctrl-C keypress so that Bitmessage can shutdown gracefully. +#The next 3 are used for the API +from SimpleXMLRPCServer import * +import json +from subprocess import call #used when the API must execute an outside program +import singleton +import proofofwork -import defaults -# Network subsystem -import network -import shutdown -import state +#For each stream to which we connect, several outgoingSynSender threads will exist and will collectively create 8 connections with peers. +class outgoingSynSender(threading.Thread): + def __init__(self): + threading.Thread.__init__(self) -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) + def setup(self,streamNumber): + self.streamNumber = streamNumber + def run(self): + time.sleep(1) + global alreadyAttemptedConnectionsListResetTime + while True: + #time.sleep(999999)#I sometimes use this to prevent connections for testing. + if len(selfInitiatedConnections[self.streamNumber]) < 8: #maximum number of outgoing connections = 8 + random.seed() + HOST, = random.sample(shared.knownNodes[self.streamNumber], 1) + alreadyAttemptedConnectionsListLock.acquire() + while HOST in alreadyAttemptedConnectionsList or HOST in shared.connectedHostsList: + alreadyAttemptedConnectionsListLock.release() + #print 'choosing new sample' + random.seed() + HOST, = random.sample(shared.knownNodes[self.streamNumber], 1) + time.sleep(1) + #Clear out the 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() - alreadyAttemptedConnectionsListResetTime) > 1800: + alreadyAttemptedConnectionsList.clear() + alreadyAttemptedConnectionsListResetTime = int(time.time()) + alreadyAttemptedConnectionsListLock.acquire() + alreadyAttemptedConnectionsList[HOST] = 0 + alreadyAttemptedConnectionsListLock.release() + PORT, timeNodeLastSeen = shared.knownNodes[self.streamNumber][HOST] + sock = socks.socksocket(socket.AF_INET, socket.SOCK_STREAM) + #This option apparently avoids the TIME_WAIT state so that we can rebind faster + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.settimeout(20) + if shared.config.get('bitmessagesettings', 'socksproxytype') == 'none' and verbose >= 2: + shared.printLock.acquire() + print 'Trying an outgoing connection to', HOST, ':', PORT + shared.printLock.release() + #sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + elif shared.config.get('bitmessagesettings', 'socksproxytype') == 'SOCKS4a': + if verbose >= 2: + shared.printLock.acquire() + print '(Using SOCKS4a) Trying an outgoing connection to', HOST, ':', PORT + shared.printLock.release() + 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') + sock.setproxy(proxytype, sockshostname, socksport, rdns, socksusername, sockspassword) + else: + sock.setproxy(proxytype, sockshostname, socksport, rdns) + elif shared.config.get('bitmessagesettings', 'socksproxytype') == 'SOCKS5': + if verbose >= 2: + shared.printLock.acquire() + print '(Using SOCKS5) Trying an outgoing connection to', HOST, ':', PORT + shared.printLock.release() + 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) -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() - 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.') - - -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() - - daemon = config.safeGetBoolean('bitmessagesettings', 'daemon') - - try: - opts, _ = getopt.getopt( - sys.argv[1:], "hcdt", - ["help", "curses", "daemon", "test"]) - - except getopt.GetoptError: - self.usage() - sys.exit(2) - - 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') - ) - - if daemon: - state.enableGUI = False # run without a UI - - if state.enableGUI and not state.curses and not depends.check_pyqt(): - sys.exit( - 'PyBitmessage requires PyQt unless you want' - ' to run it as a daemon and interact with it' - ' using the API. You can download PyQt from ' - 'http://www.riverbankcomputing.com/software/pyqt/download' - ' or by searching Google for \'PyQt Download\'.' - ' If you want to run in daemon mode, see ' - 'https://bitmessage.org/wiki/Daemon\n' - 'You can also run PyBitmessage with' - ' the new curses interface by providing' - ' \'-c\' as a commandline argument.' - ) - # is the application already running? If yes then exit. - state.thisapp = singleinstance("", daemon) - - if daemon: - with printLock: - print('Running as a daemon. Send TERM signal to end.') - self.daemonize() - - self.setSignalHandler() - - set_thread_name("PyBitmessage") - - state.dandelion = config.safeGetInt('network', 'dandelion') - # dandelion requires outbound connections, without them, - # stem objects will get stuck forever - if state.dandelion and not config.safeGetBoolean( - 'bitmessagesettings', 'sendoutgoingconnections'): - state.dandelion = 0 - - if state.testmode or config.safeGetBoolean( - 'bitmessagesettings', 'extralowdifficulty'): - defaults.networkDefaultProofOfWorkNonceTrialsPerByte = int( - defaults.networkDefaultProofOfWorkNonceTrialsPerByte / 100) - defaults.networkDefaultPayloadLengthExtraBytes = int( - defaults.networkDefaultPayloadLengthExtraBytes / 100) - - # 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.start() - - 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() - - # 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 cleanerThread - singleCleanerThread = singleCleaner() - # close the main program even if there are threads left - singleCleanerThread.daemon = True - singleCleanerThread.start() - - # start network components if networking is enabled - if state.enableNetwork: - start_proxyconfig() - network.start(config, state) - - if config.safeGetBoolean('bitmessagesettings', 'upnp'): - import upnp - upnpThread = upnp.uPnPThread() - upnpThread.start() - else: - # Populate with hardcoded value (same as connectToStream above) - state.streamsInWhichIAmParticipating.append(1) - - if not daemon and state.enableGUI: - if state.curses: - if not depends.check_curses(): - sys.exit() - print('Running with curses') - import bitmessagecurses - bitmessagecurses.runwrapper() - else: - import bitmessageqt - bitmessageqt.run() - else: - 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() + sock.connect((HOST, PORT)) + rd = receiveDataThread() + rd.daemon = True # close the main program even if there are threads left + #self.emit(SIGNAL("passObjectThrough(PyQt_PyObject)"),rd) + objectsOfWhichThisRemoteNodeIsAlreadyAware = {} + rd.setup(sock,HOST,PORT,self.streamNumber,objectsOfWhichThisRemoteNodeIsAlreadyAware) + rd.start() + shared.printLock.acquire() + print self, 'connected to', HOST, 'during an outgoing attempt.' + shared.printLock.release() + + sd = sendDataThread() + sd.setup(sock,HOST,PORT,self.streamNumber,objectsOfWhichThisRemoteNodeIsAlreadyAware) + sd.start() + sd.sendVersionMessage() + + except socks.GeneralProxyError, err: + if verbose >= 2: + shared.printLock.acquire() + print 'Could NOT connect to', HOST, 'during outgoing attempt.', err + shared.printLock.release() + PORT, timeLastSeen = shared.knownNodes[self.streamNumber][HOST] + 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. + shared.knownNodesLock.acquire() + del shared.knownNodes[self.streamNumber][HOST] + shared.knownNodesLock.release() + shared.printLock.acquire() + print 'deleting ', HOST, 'from shared.knownNodes because it is more than 48 hours old and we could not connect to it.' + shared.printLock.release() + except socks.Socks5AuthError, err: + #self.emit(SIGNAL("updateStatusBar(PyQt_PyObject)"),"SOCKS5 Authentication problem: "+str(err)) + shared.UISignalQueue.put(('updateStatusBar',"SOCKS5 Authentication problem: "+str(err))) + except socks.Socks5Error, err: + pass + print 'SOCKS5 error. (It is possible that the server wants authentication).)' ,str(err) + #self.emit(SIGNAL("updateStatusBar(PyQt_PyObject)"),"SOCKS5 error. Server might require authentication. "+str(err)) + except socks.Socks4Error, err: + print 'Socks4Error:', err + #self.emit(SIGNAL("updateStatusBar(PyQt_PyObject)"),"SOCKS4 error: "+str(err)) + except socket.error, err: + if shared.config.get('bitmessagesettings', 'socksproxytype')[0:5] == 'SOCKS': + print 'Bitmessage MIGHT be having trouble connecting to the SOCKS server. '+str(err) + #self.emit(SIGNAL("updateStatusBar(PyQt_PyObject)"),"Problem: Bitmessage can not connect to the SOCKS server. "+str(err)) + else: + if verbose >= 1: + shared.printLock.acquire() + print 'Could NOT connect to', HOST, 'during outgoing attempt.', err + shared.printLock.release() + PORT, timeLastSeen = shared.knownNodes[self.streamNumber][HOST] + 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 knownNodes data-structure. + shared.knownNodesLock.acquire() + del shared.knownNodes[self.streamNumber][HOST] + shared.knownNodesLock.release() + shared.printLock.acquire() + print 'deleting ', HOST, 'from knownNodes because it is more than 48 hours old and we could not connect to it.' + shared.printLock.release() + except Exception, err: + sys.stderr.write('An exception has occurred in the outgoingSynSender thread that was not caught by other exception types: %s\n' % err) + time.sleep(0.1) + +#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): + def __init__(self): + threading.Thread.__init__(self) + + + def run(self): + #We don't want to accept incoming connections if the user is using a SOCKS proxy. If they eventually select proxy 'none' then this will start listening for connections. + while shared.config.get('bitmessagesettings', 'socksproxytype')[0:5] == 'SOCKS': + time.sleep(300) + + shared.printLock.acquire() + print 'Listening for incoming connections.' + shared.printLock.release() + HOST = '' # Symbolic name meaning all available interfaces + PORT = shared.config.getint('bitmessagesettings', 'port') + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + #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) + + + while True: + #We don't want to accept incoming connections if the user is using a SOCKS proxy. If the user eventually select proxy 'none' then this will start listening for connections. + while shared.config.get('bitmessagesettings', 'socksproxytype')[0:5] == 'SOCKS': + time.sleep(10) + while len(shared.connectedHostsList) > 220: + shared.printLock.acquire() + print 'We are connected to too many people. Not accepting further incoming connections for ten seconds.' + shared.printLock.release() + time.sleep(10) + a,(HOST,PORT) = sock.accept() + + #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. + while HOST in shared.connectedHostsList: + shared.printLock.acquire() + print 'We are already connected to', HOST+'. Ignoring connection.' + shared.printLock.release() + a.close() + a,(HOST,PORT) = sock.accept() + objectsOfWhichThisRemoteNodeIsAlreadyAware = {} + a.settimeout(20) + + sd = sendDataThread() + sd.setup(a,HOST,PORT,-1,objectsOfWhichThisRemoteNodeIsAlreadyAware) + sd.start() + + rd = receiveDataThread() + rd.daemon = True # close the main program even if there are threads left + rd.setup(a,HOST,PORT,-1,objectsOfWhichThisRemoteNodeIsAlreadyAware) + rd.start() + + shared.printLock.acquire() + print self, 'connected to', HOST,'during INCOMING request.' + shared.printLock.release() + +#This thread is created either by the synSenderThread(for outgoing connections) or the singleListenerThread(for incoming connectiosn). +class receiveDataThread(threading.Thread): + def __init__(self): + threading.Thread.__init__(self) + self.data = '' + self.verackSent = False + self.verackReceived = False + + def setup(self,sock,HOST,port,streamNumber,objectsOfWhichThisRemoteNodeIsAlreadyAware): + self.sock = sock + self.HOST = HOST + self.PORT = port + self.streamNumber = streamNumber + self.payloadLength = 0 #This is the protocol payload length thus it doesn't include the 24 byte message header + self.objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave = {} + shared.connectedHostsList[self.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. + 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 + selfInitiatedConnections[streamNumber][self] = 0 + self.ackDataThatWeHaveYetToSend = [] #When we receive a message bound for us, we store the acknowledgement that we need to send (the ackdata) here until we are done processing all other data received from this peer. + self.objectsOfWhichThisRemoteNodeIsAlreadyAware = objectsOfWhichThisRemoteNodeIsAlreadyAware + + def run(self): + shared.printLock.acquire() + print 'ID of the receiveDataThread is', str(id(self))+'. The size of the shared.connectedHostsList is now', len(shared.connectedHostsList) + shared.printLock.release() + while True: + try: + self.data += self.sock.recv(4096) + except socket.timeout: + shared.printLock.acquire() + print 'Timeout occurred waiting for data from', self.HOST + '. Closing receiveData thread. (ID:',str(id(self))+ ')' + shared.printLock.release() + break + except Exception, err: + shared.printLock.acquire() + print 'sock.recv error. Closing receiveData thread (HOST:', self.HOST, 'ID:',str(id(self))+ ').', err + shared.printLock.release() + break + #print 'Received', repr(self.data) + if self.data == "": + shared.printLock.acquire() + print 'Connection to', self.HOST, 'closed. Closing receiveData thread. (ID:',str(id(self))+ ')' + shared.printLock.release() + break + else: + self.processData() + + + + + try: + del selfInitiatedConnections[self.streamNumber][self] + shared.printLock.acquire() + print 'removed self (a receiveDataThread) from selfInitiatedConnections' + shared.printLock.release() + except: + pass + shared.broadcastToSendDataQueues((0, 'shutdown', self.HOST)) + try: + del shared.connectedHostsList[self.HOST] + except Exception, err: + shared.printLock.acquire() + print 'Could not delete', self.HOST, 'from shared.connectedHostsList.', err + shared.printLock.release() + try: + del numberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHavePerPeer[self.HOST] + except: + pass + shared.UISignalQueue.put(('updateNetworkStatusTab','no data')) + shared.printLock.acquire() + print 'The size of the connectedHostsList is now:', len(shared.connectedHostsList) + shared.printLock.release() + + def processData(self): + global verbose + #if verbose >= 3: + #shared.printLock.acquire() + #print 'self.data is currently ', repr(self.data) + #shared.printLock.release() + if len(self.data) < 20: #if so little of the data has arrived that we can't even unpack the payload length + pass + elif self.data[0:4] != '\xe9\xbe\xb4\xd9': + if verbose >= 1: + shared.printLock.acquire() + sys.stderr.write('The magic bytes were not correct. First 40 bytes of data: %s\n' % repr(self.data[0:40])) + print 'self.data:', self.data.encode('hex') + shared.printLock.release() + self.data = "" + else: + self.payloadLength, = unpack('>L',self.data[16:20]) + if len(self.data) >= self.payloadLength+24: #check if the whole message has arrived yet. If it has,... + if self.data[20:24] == hashlib.sha512(self.data[24:self.payloadLength+24]).digest()[0:4]:#test the checksum in the message. If it is correct... + #print 'message checksum is correct' + #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). + shared.knownNodesLock.acquire() + shared.knownNodes[self.streamNumber][self.HOST] = (self.PORT,int(time.time())) + shared.knownNodesLock.release() + if self.payloadLength <= 180000000: #If the size of the message is greater than 180MB, ignore it. (I get memory errors when processing messages much larger than this though it is concievable that this value will have to be lowered if some systems are less tolarant of large messages.) + remoteCommand = self.data[4:16] + shared.printLock.acquire() + print 'remoteCommand', repr(remoteCommand.replace('\x00','')), ' from', self.HOST + shared.printLock.release() + if remoteCommand == 'version\x00\x00\x00\x00\x00': + self.recversion(self.data[24:self.payloadLength+24]) + elif remoteCommand == 'verack\x00\x00\x00\x00\x00\x00': + self.recverack() + elif remoteCommand == 'addr\x00\x00\x00\x00\x00\x00\x00\x00' and self.connectionIsOrWasFullyEstablished: + self.recaddr(self.data[24:self.payloadLength+24]) + elif remoteCommand == 'getpubkey\x00\x00\x00' and self.connectionIsOrWasFullyEstablished: + self.recgetpubkey(self.data[24:self.payloadLength+24]) + elif remoteCommand == 'pubkey\x00\x00\x00\x00\x00\x00' and self.connectionIsOrWasFullyEstablished: + self.recpubkey(self.data[24:self.payloadLength+24]) + elif remoteCommand == 'inv\x00\x00\x00\x00\x00\x00\x00\x00\x00' and self.connectionIsOrWasFullyEstablished: + self.recinv(self.data[24:self.payloadLength+24]) + elif remoteCommand == 'getdata\x00\x00\x00\x00\x00' and self.connectionIsOrWasFullyEstablished: + self.recgetdata(self.data[24:self.payloadLength+24]) + elif remoteCommand == 'msg\x00\x00\x00\x00\x00\x00\x00\x00\x00' and self.connectionIsOrWasFullyEstablished: + self.recmsg(self.data[24:self.payloadLength+24]) + elif remoteCommand == 'broadcast\x00\x00\x00' and self.connectionIsOrWasFullyEstablished: + self.recbroadcast(self.data[24:self.payloadLength+24]) + elif remoteCommand == 'ping\x00\x00\x00\x00\x00\x00\x00\x00' and self.connectionIsOrWasFullyEstablished: + self.sendpong() + elif remoteCommand == 'pong\x00\x00\x00\x00\x00\x00\x00\x00' and self.connectionIsOrWasFullyEstablished: + pass + elif remoteCommand == 'alert\x00\x00\x00\x00\x00\x00\x00' and self.connectionIsOrWasFullyEstablished: + pass + + self.data = self.data[self.payloadLength+24:]#take this message out and then process the next message + if self.data == '': + while len(self.objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave) > 0: + random.seed() + objectHash, = random.sample(self.objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave, 1) + if objectHash in shared.inventory: + shared.printLock.acquire() + print 'Inventory (in memory) already has object listed in inv message.' + shared.printLock.release() + del self.objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave[objectHash] + elif isInSqlInventory(objectHash): + if verbose >= 3: + shared.printLock.acquire() + print 'Inventory (SQL on disk) already has object listed in inv message.' + shared.printLock.release() + del self.objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave[objectHash] + else: + self.sendgetdata(objectHash) + del self.objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave[objectHash] #It is possible that the remote node doesn't respond with the object. In that case, we'll very likely get it from someone else anyway. + if len(self.objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave) == 0: + shared.printLock.acquire() + print '(concerning', self.HOST + ')', 'number of objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave is now', len(self.objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave) + shared.printLock.release() + try: + del numberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHavePerPeer[self.HOST] #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.objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave) == 0: + shared.printLock.acquire() + print '(concerning', self.HOST + ')', 'number of objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave is now', len(self.objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave) + shared.printLock.release() + try: + del numberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHavePerPeer[self.HOST] #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.objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave) > 0: + shared.printLock.acquire() + print '(concerning', self.HOST + ')', 'number of objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave is now', len(self.objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave) + shared.printLock.release() + numberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHavePerPeer[self.HOST] = len(self.objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave) #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. + if len(self.ackDataThatWeHaveYetToSend) > 0: + self.data = self.ackDataThatWeHaveYetToSend.pop() + self.processData() + else: + print 'Checksum incorrect. Clearing this message.' + self.data = self.data[self.payloadLength+24:] + + def isProofOfWorkSufficient(self,data,nonceTrialsPerByte=0,payloadLengthExtraBytes=0): + if nonceTrialsPerByte < shared.networkDefaultProofOfWorkNonceTrialsPerByte: + nonceTrialsPerByte = shared.networkDefaultProofOfWorkNonceTrialsPerByte + if payloadLengthExtraBytes < shared.networkDefaultPayloadLengthExtraBytes: + payloadLengthExtraBytes = shared.networkDefaultPayloadLengthExtraBytes + POW, = unpack('>Q',hashlib.sha512(hashlib.sha512(data[:8]+ hashlib.sha512(data[8:]).digest()).digest()).digest()[0:8]) + #print 'POW:', POW + return POW <= 2**64 / ((len(data)+payloadLengthExtraBytes) * (nonceTrialsPerByte)) + + def sendpong(self): + print 'Sending pong' + try: + self.sock.sendall('\xE9\xBE\xB4\xD9\x70\x6F\x6E\x67\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xcf\x83\xe1\x35') + except Exception, err: + #if not 'Bad file descriptor' in err: + shared.printLock.acquire() + sys.stderr.write('sock.sendall error: %s\n' % err) + shared.printLock.release() + + def recverack(self): + print 'verack received' + self.verackReceived = True + if self.verackSent == True: + #We have thus both sent and received a verack. + self.connectionFullyEstablished() + + def connectionFullyEstablished(self): + self.connectionIsOrWasFullyEstablished = True + if not self.initiatedConnection: + #self.emit(SIGNAL("setStatusIcon(PyQt_PyObject)"),'green') + 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')) + remoteNodeIncomingPort, remoteNodeSeenTime = shared.knownNodes[self.streamNumber][self.HOST] + shared.printLock.acquire() + print 'Connection fully established with', self.HOST, remoteNodeIncomingPort + print 'The size of the connectedHostsList is now', len(shared.connectedHostsList) + print 'The length of sendDataQueues is now:', len(shared.sendDataQueues) + print 'broadcasting addr from within connectionFullyEstablished function.' + shared.printLock.release() + self.broadcastaddr([(int(time.time()), self.streamNumber, 1, self.HOST, remoteNodeIncomingPort)]) #This lets all of our peers know about this new node. + self.sendaddr() #This is one large addr message to this one peer. + if not self.initiatedConnection and len(shared.connectedHostsList) > 200: + shared.printLock.acquire() + print 'We are connected to too many people. Closing connection.' + shared.printLock.release() + shared.broadcastToSendDataQueues((0, 'shutdown', self.HOST)) + return + self.sendBigInv() + + def sendBigInv(self): + shared.sqlLock.acquire() + #Select all hashes which are younger than two days old and in this stream. + t = (int(time.time())-maximumAgeOfObjectsThatIAdvertiseToOthers,int(time.time())-lengthOfTimeToHoldOnToAllPubkeys,self.streamNumber) + shared.sqlSubmitQueue.put('''SELECT hash FROM inventory WHERE ((receivedtime>? and objecttype<>'pubkey') or (receivedtime>? and objecttype='pubkey')) and streamnumber=?''') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + bigInvList = {} + for row in queryreturn: + hash, = row + if hash not in self.objectsOfWhichThisRemoteNodeIsAlreadyAware: + bigInvList[hash] = 0 + else: + shared.printLock.acquire() + print 'Not including an object hash in a big inv message because the remote node is already aware of it.'#This line is here to check that this feature is working. + shared.printLock.release() + #We also have messages in our inventory in memory (which is a python dictionary). Let's fetch those too. + for hash, storedValue in shared.inventory.items(): + if hash not in self.objectsOfWhichThisRemoteNodeIsAlreadyAware: + objectType, streamNumber, payload, receivedTime = storedValue + if streamNumber == self.streamNumber and receivedTime > int(time.time())-maximumAgeOfObjectsThatIAdvertiseToOthers: + bigInvList[hash] = 0 + else: + shared.printLock.acquire() + print 'Not including an object hash in a big inv message because the remote node is already aware of it.'#This line is here to check that this feature is working. + shared.printLock.release() + 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) + + #Self explanatory. 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 + headerData = '\xe9\xbe\xb4\xd9' #magic bits, slighly different from Bitcoin's magic bits. + headerData += 'inv\x00\x00\x00\x00\x00\x00\x00\x00\x00' + headerData += pack('>L',len(payload)) + headerData += hashlib.sha512(payload).digest()[:4] + shared.printLock.acquire() + print 'Sending huge inv message with', numberOfObjects, 'objects to just this one peer' + shared.printLock.release() + try: + self.sock.sendall(headerData + payload) + except Exception, err: + #if not 'Bad file descriptor' in err: + shared.printLock.acquire() + sys.stderr.write('sock.sendall error: %s\n' % err) + shared.printLock.release() + + #We have received a broadcast message + def recbroadcast(self,data): + self.messageProcessingStartTime = time.time() + #First we must check to make sure the proof of work is sufficient. + if not self.isProofOfWorkSufficient(data): + print 'Proof of work in broadcast message insufficient.' + return + readPosition = 8 #bypass the nonce + embeddedTime, = unpack('>I',data[readPosition:readPosition+4]) + + #This section is used for the transition from 32 bit time to 64 bit time in the protocol. + if embeddedTime == 0: + embeddedTime, = unpack('>Q',data[readPosition:readPosition+8]) + readPosition += 8 + else: + readPosition += 4 + + if embeddedTime > (int(time.time())+10800): #prevent funny business + print 'The embedded time in this broadcast message is more than three hours in the future. That doesn\'t make sense. Ignoring message.' + return + if embeddedTime < (int(time.time())-maximumAgeOfAnObjectThatIAmWillingToAccept): + print 'The embedded time in this broadcast message is too old. Ignoring message.' + return + if len(data) < 180: + print 'The payload length of this broadcast packet is unreasonably low. Someone is probably trying funny business. Ignoring message.' + return + #Let us check to make sure the stream number is correct (thus preventing an individual from sending broadcasts out on the wrong streams or all streams). + broadcastVersion, broadcastVersionLength = decodeVarint(data[readPosition:readPosition+10]) + if broadcastVersion >= 2: + streamNumber, streamNumberLength = decodeVarint(data[readPosition+broadcastVersionLength:readPosition+broadcastVersionLength+10]) + if streamNumber != self.streamNumber: + print 'The stream number encoded in this broadcast message (' + str(streamNumber) + ') does not match the stream number on which it was received. Ignoring it.' + return + + shared.inventoryLock.acquire() + self.inventoryHash = calculateInventoryHash(data) + if self.inventoryHash in shared.inventory: + print 'We have already received this broadcast object. Ignoring.' + shared.inventoryLock.release() + return + elif isInSqlInventory(self.inventoryHash): + print 'We have already received this broadcast object (it is stored on disk in the SQL inventory). Ignoring it.' + shared.inventoryLock.release() + return + #It is valid so far. Let's let our peers know about it. + objectType = 'broadcast' + shared.inventory[self.inventoryHash] = (objectType, self.streamNumber, data, embeddedTime) + shared.inventoryLock.release() + self.broadcastinv(self.inventoryHash) + #self.emit(SIGNAL("incrementNumberOfBroadcastsProcessed()")) + shared.UISignalQueue.put(('incrementNumberOfBroadcastsProcessed','no data')) + + + self.processbroadcast(readPosition,data)#When this function returns, we will have either successfully processed this broadcast because we are interested in it, ignored it because we aren't interested in it, or found problem with the broadcast that warranted ignoring it. + + # Let us now set lengthOfTimeWeShouldUseToProcessThisMessage. If we haven't used the specified amount of time, we shall sleep. These values are mostly the same values used for msg messages although broadcast messages are processed faster. + if len(data) > 100000000: #Size is greater than 100 megabytes + lengthOfTimeWeShouldUseToProcessThisMessage = 100 #seconds. + elif len(data) > 10000000: #Between 100 and 10 megabytes + lengthOfTimeWeShouldUseToProcessThisMessage = 20 #seconds. + elif len(data) > 1000000: #Between 10 and 1 megabyte + lengthOfTimeWeShouldUseToProcessThisMessage = 3 #seconds. + else: #Less than 1 megabyte + lengthOfTimeWeShouldUseToProcessThisMessage = .6 #seconds. + + + sleepTime = lengthOfTimeWeShouldUseToProcessThisMessage - (time.time()- self.messageProcessingStartTime) + if sleepTime > 0: + shared.printLock.acquire() + print 'Timing attack mitigation: Sleeping for', sleepTime ,'seconds.' + shared.printLock.release() + time.sleep(sleepTime) + shared.printLock.acquire() + print 'Total message processing time:', time.time()- self.messageProcessingStartTime, 'seconds.' + shared.printLock.release() + + #A broadcast message has a valid time and POW and requires processing. The recbroadcast function calls this one. + def processbroadcast(self,readPosition,data): + broadcastVersion, broadcastVersionLength = decodeVarint(data[readPosition:readPosition+9]) + readPosition += broadcastVersionLength + if broadcastVersion < 1 or broadcastVersion > 2: + print 'Cannot decode incoming broadcast versions higher than 2. Assuming the sender isn\' being silly, you should upgrade Bitmessage because this message shall be ignored.' + return + if broadcastVersion == 1: + beginningOfPubkeyPosition = readPosition #used when we add the pubkey to our pubkey table + sendersAddressVersion, sendersAddressVersionLength = decodeVarint(data[readPosition:readPosition+9]) + if sendersAddressVersion <= 1 or sendersAddressVersion >=3: + #Cannot decode senderAddressVersion higher than 2. Assuming the sender isn\' being silly, you should upgrade Bitmessage because this message shall be ignored. + return + readPosition += sendersAddressVersionLength + if sendersAddressVersion == 2: + sendersStream, sendersStreamLength = decodeVarint(data[readPosition:readPosition+9]) + readPosition += sendersStreamLength + behaviorBitfield = data[readPosition:readPosition+4] + readPosition += 4 + sendersPubSigningKey = '\x04' + data[readPosition:readPosition+64] + readPosition += 64 + sendersPubEncryptionKey = '\x04' + data[readPosition:readPosition+64] + readPosition += 64 + endOfPubkeyPosition = readPosition + sendersHash = data[readPosition:readPosition+20] + if sendersHash not in shared.broadcastSendersForWhichImWatching: + #Display timing data + shared.printLock.acquire() + print 'Time spent deciding that we are not interested in this v1 broadcast:', time.time()- self.messageProcessingStartTime + shared.printLock.release() return + #At this point, this message claims to be from sendersHash and we are interested in it. We still have to hash the public key to make sure it is truly the key that matches the hash, and also check the signiture. + readPosition += 20 - test_core_result = test_core.run() - self.stop() - test_core.cleanup() - sys.exit(not test_core_result.wasSuccessful()) + sha = hashlib.new('sha512') + sha.update(sendersPubSigningKey+sendersPubEncryptionKey) + ripe = hashlib.new('ripemd160') + ripe.update(sha.digest()) + if ripe.digest() != sendersHash: + #The sender of this message lied. + return + messageEncodingType, messageEncodingTypeLength = decodeVarint(data[readPosition:readPosition+9]) + if messageEncodingType == 0: + return + readPosition += messageEncodingTypeLength + messageLength, messageLengthLength = decodeVarint(data[readPosition:readPosition+9]) + readPosition += messageLengthLength + message = data[readPosition:readPosition+messageLength] + readPosition += messageLength + readPositionAtBottomOfMessage = readPosition + signatureLength, signatureLengthLength = decodeVarint(data[readPosition:readPosition+9]) + readPosition += signatureLengthLength + signature = data[readPosition:readPosition+signatureLength] + try: + highlevelcrypto.verify(data[12:readPositionAtBottomOfMessage],signature,sendersPubSigningKey.encode('hex')) + print 'ECDSA verify passed' + except Exception, err: + print 'ECDSA verify failed', err + return + #verify passed - @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 + #Let's store the public key in case we want to reply to this person. + #We don't have the correct nonce or time (which would let us send out a pubkey message) so we'll just fill it with 1's. We won't be able to send this pubkey to others (without doing the proof of work ourselves, which this program is programmed to not do.) + t = (ripe.digest(),'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'+'\xFF\xFF\xFF\xFF'+data[beginningOfPubkeyPosition:endOfPubkeyPosition],int(time.time()),'yes') + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''INSERT INTO pubkeys VALUES (?,?,?,?)''') + shared.sqlSubmitQueue.put(t) + shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() + shared.workerQueue.put(('newpubkey',(sendersAddressVersion,sendersStream,ripe.digest()))) #This will 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. + + fromAddress = encodeAddress(sendersAddressVersion,sendersStream,ripe.digest()) + shared.printLock.acquire() + print 'fromAddress:', fromAddress + shared.printLock.release() + if messageEncodingType == 2: + bodyPositionIndex = string.find(message,'\nBody:') + if bodyPositionIndex > 1: + subject = message[8:bodyPositionIndex] + body = message[bodyPositionIndex+6:] + else: + subject = '' + body = message + elif messageEncodingType == 1: + body = message + subject = '' + elif messageEncodingType == 0: + print 'messageEncodingType == 0. Doing nothing with the message.' + else: + body = 'Unknown encoding type.\n\n' + repr(message) + subject = '' + + toAddress = '[Broadcast subscribers]' + if messageEncodingType <> 0: + shared.sqlLock.acquire() + t = (self.inventoryHash,toAddress,fromAddress,subject,int(time.time()),body,'inbox',messageEncodingType,0) + shared.sqlSubmitQueue.put('''INSERT INTO inbox VALUES (?,?,?,?,?,?,?,?,?)''') + shared.sqlSubmitQueue.put(t) + shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() + #self.emit(SIGNAL("displayNewInboxMessage(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"),self.inventoryHash,toAddress,fromAddress,subject,body) + shared.UISignalQueue.put(('displayNewInboxMessage',(self.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 shared.safeConfigGetBoolean('bitmessagesettings','apienabled'): + try: + apiNotifyPath = shared.config.get('bitmessagesettings','apinotifypath') + except: + apiNotifyPath = '' + if apiNotifyPath != '': + call([apiNotifyPath, "newBroadcast"]) + + #Display timing data + shared.printLock.acquire() + print 'Time spent processing this interesting broadcast:', time.time()- self.messageProcessingStartTime + shared.printLock.release() + if broadcastVersion == 2: + cleartextStreamNumber, cleartextStreamNumberLength = decodeVarint(data[readPosition:readPosition+10]) + readPosition += cleartextStreamNumberLength + initialDecryptionSuccessful = False + for key, cryptorObject in shared.MyECSubscriptionCryptorObjects.items(): + try: + 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 + print 'EC decryption successful using key associated with ripe hash:', key.encode('hex') + break + except Exception, err: + pass + #print 'cryptorObject.decrypt Exception:', err + if not initialDecryptionSuccessful: + #This is not a broadcast I am interested in. + shared.printLock.acquire() + print 'Length of time program spent failing to decrypt this v2 broadcast:', time.time()- self.messageProcessingStartTime, 'seconds.' + shared.printLock.release() + return + #At this point this is a broadcast I have decrypted and thus am interested in. + signedBroadcastVersion, readPosition = decodeVarint(decryptedData[:10]) + beginningOfPubkeyPosition = readPosition #used when we add the pubkey to our pubkey table + sendersAddressVersion, sendersAddressVersionLength = decodeVarint(decryptedData[readPosition:readPosition+9]) + if sendersAddressVersion < 2 or sendersAddressVersion > 3: + print 'Cannot decode senderAddressVersion other than 2 or 3. Assuming the sender isn\' 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: + print '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] + readPosition += 64 + sendersPubEncryptionKey = '\x04' + decryptedData[readPosition:readPosition+64] + readPosition += 64 + if sendersAddressVersion >= 3: + requiredAverageProofOfWorkNonceTrialsPerByte, varintLength = decodeVarint(decryptedData[readPosition:readPosition+10]) + readPosition += varintLength + print 'sender\'s requiredAverageProofOfWorkNonceTrialsPerByte is', requiredAverageProofOfWorkNonceTrialsPerByte + requiredPayloadLengthExtraBytes, varintLength = decodeVarint(decryptedData[readPosition:readPosition+10]) + readPosition += varintLength + print 'sender\'s requiredPayloadLengthExtraBytes is', requiredPayloadLengthExtraBytes + endOfPubkeyPosition = readPosition + + sha = hashlib.new('sha512') + sha.update(sendersPubSigningKey+sendersPubEncryptionKey) + ripe = hashlib.new('ripemd160') + ripe.update(sha.digest()) + + if toRipe != ripe.digest(): + print 'The 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: + return + readPosition += messageEncodingTypeLength + messageLength, messageLengthLength = decodeVarint(decryptedData[readPosition:readPosition+9]) + readPosition += messageLengthLength + message = decryptedData[readPosition:readPosition+messageLength] + readPosition += messageLength + readPositionAtBottomOfMessage = readPosition + signatureLength, signatureLengthLength = decodeVarint(decryptedData[readPosition:readPosition+9]) + readPosition += signatureLengthLength + signature = decryptedData[readPosition:readPosition+signatureLength] + try: + highlevelcrypto.verify(decryptedData[:readPositionAtBottomOfMessage],signature,sendersPubSigningKey.encode('hex')) + print 'ECDSA verify passed' + except Exception, err: + print 'ECDSA verify failed', err + return + #verify passed + + #Let's store the public key in case we want to reply to this person. + t = (ripe.digest(),'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'+'\xFF\xFF\xFF\xFF'+decryptedData[beginningOfPubkeyPosition:endOfPubkeyPosition],int(time.time()),'yes') + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''INSERT INTO pubkeys VALUES (?,?,?,?)''') + shared.sqlSubmitQueue.put(t) + shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() + shared.workerQueue.put(('newpubkey',(sendersAddressVersion,sendersStream,ripe.digest()))) #This will 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. + + fromAddress = encodeAddress(sendersAddressVersion,sendersStream,ripe.digest()) + shared.printLock.acquire() + print 'fromAddress:', fromAddress + shared.printLock.release() + if messageEncodingType == 2: + bodyPositionIndex = string.find(message,'\nBody:') + if bodyPositionIndex > 1: + subject = message[8:bodyPositionIndex] + body = message[bodyPositionIndex+6:] + else: + subject = '' + body = message + elif messageEncodingType == 1: + body = message + subject = '' + elif messageEncodingType == 0: + print 'messageEncodingType == 0. Doing nothing with the message.' + else: + body = 'Unknown encoding type.\n\n' + repr(message) + subject = '' + + toAddress = '[Broadcast subscribers]' + if messageEncodingType <> 0: + shared.sqlLock.acquire() + t = (self.inventoryHash,toAddress,fromAddress,subject,int(time.time()),body,'inbox',messageEncodingType,0) + shared.sqlSubmitQueue.put('''INSERT INTO inbox VALUES (?,?,?,?,?,?,?,?,?)''') + shared.sqlSubmitQueue.put(t) + shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() + #self.emit(SIGNAL("displayNewInboxMessage(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"),self.inventoryHash,toAddress,fromAddress,subject,body) + shared.UISignalQueue.put(('displayNewInboxMessage',(self.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 shared.safeConfigGetBoolean('bitmessagesettings','apienabled'): + try: + apiNotifyPath = shared.config.get('bitmessagesettings','apinotifypath') + except: + apiNotifyPath = '' + if apiNotifyPath != '': + call([apiNotifyPath, "newBroadcast"]) + + #Display timing data + shared.printLock.acquire() + print 'Time spent processing this interesting broadcast:', time.time()- self.messageProcessingStartTime + shared.printLock.release() + + + #We have received a msg message. + def recmsg(self,data): + self.messageProcessingStartTime = time.time() + #First we must check to make sure the proof of work is sufficient. + if not self.isProofOfWorkSufficient(data): + print 'Proof of work in msg message insufficient.' + return + + readPosition = 8 + embeddedTime, = unpack('>I',data[readPosition:readPosition+4]) + + #This section is used for the transition from 32 bit time to 64 bit time in the protocol. + if embeddedTime == 0: + embeddedTime, = unpack('>Q',data[readPosition:readPosition+8]) + readPosition += 8 else: - parentPid = os.getpid() - state.thisapp.lock() # relock + readPosition += 4 - 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 + if embeddedTime > int(time.time())+10800: + print 'The time in the msg message is too new. Ignoring it. Time:', embeddedTime + return + if embeddedTime < int(time.time())-maximumAgeOfAnObjectThatIAmWillingToAccept: + print 'The time in the msg message is too old. Ignoring it. Time:', embeddedTime + return + streamNumberAsClaimedByMsg, streamNumberAsClaimedByMsgLength = decodeVarint(data[readPosition:readPosition+9]) + if streamNumberAsClaimedByMsg != self.streamNumber: + print 'The stream number encoded in this msg (' + str(streamNumberAsClaimedByMsg) + ') message does not match the stream number on which it was received. Ignoring it.' + return + readPosition += streamNumberAsClaimedByMsgLength + self.inventoryHash = calculateInventoryHash(data) + shared.inventoryLock.acquire() + if self.inventoryHash in shared.inventory: + print 'We have already received this msg message. Ignoring.' + shared.inventoryLock.release() + return + elif isInSqlInventory(self.inventoryHash): + print 'We have already received this msg message (it is stored on disk in the SQL inventory). Ignoring it.' + shared.inventoryLock.release() + return + #This msg message is valid. Let's let our peers know about it. + objectType = 'msg' + shared.inventory[self.inventoryHash] = (objectType, self.streamNumber, data, embeddedTime) + shared.inventoryLock.release() + self.broadcastinv(self.inventoryHash) + #self.emit(SIGNAL("incrementNumberOfMessagesProcessed()")) + shared.UISignalQueue.put(('incrementNumberOfMessagesProcessed','no data')) + + + self.processmsg(readPosition,data) #When this function returns, we will have either successfully processed the message bound for us, ignored it because it isn't bound for us, or found problem with the message that warranted ignoring it. + + # Let us now set lengthOfTimeWeShouldUseToProcessThisMessage. If we haven't used the specified amount of time, we shall sleep. These values are based on test timings and you may change them at-will. + if len(data) > 100000000: #Size is greater than 100 megabytes + lengthOfTimeWeShouldUseToProcessThisMessage = 100 #seconds. Actual length of time it took my computer to decrypt and verify the signature of a 100 MB message: 3.7 seconds. + elif len(data) > 10000000: #Between 100 and 10 megabytes + lengthOfTimeWeShouldUseToProcessThisMessage = 20 #seconds. Actual length of time it took my computer to decrypt and verify the signature of a 10 MB message: 0.53 seconds. Actual length of time it takes in practice when processing a real message: 1.44 seconds. + elif len(data) > 1000000: #Between 10 and 1 megabyte + lengthOfTimeWeShouldUseToProcessThisMessage = 3 #seconds. Actual length of time it took my computer to decrypt and verify the signature of a 1 MB message: 0.18 seconds. Actual length of time it takes in practice when processing a real message: 0.30 seconds. + else: #Less than 1 megabyte + lengthOfTimeWeShouldUseToProcessThisMessage = .6 #seconds. Actual length of time it took my computer to decrypt and verify the signature of a 100 KB message: 0.15 seconds. Actual length of time it takes in practice when processing a real message: 0.25 seconds. + + + sleepTime = lengthOfTimeWeShouldUseToProcessThisMessage - (time.time()- self.messageProcessingStartTime) + if sleepTime > 0: + shared.printLock.acquire() + print 'Timing attack mitigation: Sleeping for', sleepTime ,'seconds.' + shared.printLock.release() + time.sleep(sleepTime) + shared.printLock.acquire() + print 'Total message processing time:', time.time()- self.messageProcessingStartTime, 'seconds.' + shared.printLock.release() + + + #A msg message has a valid time and POW and requires processing. The recmsg function calls this one. + def processmsg(self,readPosition, encryptedData): + initialDecryptionSuccessful = False + #Let's check whether this is a message acknowledgement bound for us. + if encryptedData[readPosition:] in ackdataForWhichImWatching: + shared.printLock.acquire() + print 'This msg IS an acknowledgement bound for me.' + shared.printLock.release() + del ackdataForWhichImWatching[encryptedData[readPosition:]] + t = ('ackreceived',encryptedData[readPosition:]) + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('UPDATE sent SET status=? WHERE ackdata=?') + shared.sqlSubmitQueue.put(t) + shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() + #self.emit(SIGNAL("updateSentItemStatusByAckdata(PyQt_PyObject,PyQt_PyObject)"),encryptedData[readPosition:],'Acknowledgement of the message received just now.') + shared.UISignalQueue.put(('updateSentItemStatusByAckdata',(encryptedData[readPosition:],'Acknowledgement of the message received just now.'))) + return else: - state.thisapp.lock() # relock - state.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) + shared.printLock.acquire() + print 'This was NOT an acknowledgement bound for me.' + #print 'ackdataForWhichImWatching', ackdataForWhichImWatching + shared.printLock.release() - @staticmethod - def setSignalHandler(): - """Setting the Signal Handler""" - signal.signal(signal.SIGINT, signal_handler) - signal.signal(signal.SIGTERM, signal_handler) - # signal.signal(signal.SIGINT, signal.SIG_DFL) + #This is not an acknowledgement bound for me. See if it is a message bound for me by trying to decrypt it with my private keys. + for key, cryptorObject in shared.myECCryptorObjects.items(): + try: + decryptedData = cryptorObject.decrypt(encryptedData[readPosition:]) + 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 + print 'EC decryption successful using key associated with ripe hash:', key.encode('hex') + break + except Exception, err: + pass + #print 'cryptorObject.decrypt Exception:', err + if not initialDecryptionSuccessful: + #This is not a message bound for me. + shared.printLock.acquire() + print 'Length of time program spent failing to decrypt this message:', time.time()- self.messageProcessingStartTime, 'seconds.' + shared.printLock.release() + else: + #This is a message bound for me. + toAddress = shared.myAddressesByHash[toRipe] #Look up my address based on the RIPE hash. + readPosition = 0 + messageVersion, messageVersionLength = decodeVarint(decryptedData[readPosition:readPosition+10]) + readPosition += messageVersionLength + if messageVersion != 1: + print 'Cannot understand message versions other than one. Ignoring message.' + return + sendersAddressVersionNumber, sendersAddressVersionNumberLength = decodeVarint(decryptedData[readPosition:readPosition+10]) + readPosition += sendersAddressVersionNumberLength + if sendersAddressVersionNumber == 0: + print 'Cannot understand sendersAddressVersionNumber = 0. Ignoring message.' + return + if sendersAddressVersionNumber >= 4: + print 'Sender\'s address version number', sendersAddressVersionNumber, 'not yet supported. Ignoring message.' + return + if len(decryptedData) < 170: + print 'Length of the unencrypted data is unreasonably short. Sanity check failed. Ignoring message.' + return + sendersStreamNumber, sendersStreamNumberLength = decodeVarint(decryptedData[readPosition:readPosition+10]) + if sendersStreamNumber == 0: + print 'sender\'s stream number is 0. Ignoring message.' + return + readPosition += sendersStreamNumberLength + behaviorBitfield = decryptedData[readPosition:readPosition+4] + readPosition += 4 + pubSigningKey = '\x04' + decryptedData[readPosition:readPosition+64] + readPosition += 64 + pubEncryptionKey = '\x04' + decryptedData[readPosition:readPosition+64] + readPosition += 64 + if sendersAddressVersionNumber >= 3: + requiredAverageProofOfWorkNonceTrialsPerByte, varintLength = decodeVarint(decryptedData[readPosition:readPosition+10]) + readPosition += varintLength + print 'sender\'s requiredAverageProofOfWorkNonceTrialsPerByte is', requiredAverageProofOfWorkNonceTrialsPerByte + requiredPayloadLengthExtraBytes, varintLength = decodeVarint(decryptedData[readPosition:readPosition+10]) + readPosition += varintLength + print 'sender\'s requiredPayloadLengthExtraBytes is', requiredPayloadLengthExtraBytes + endOfThePublicKeyPosition = readPosition #needed for when we store the pubkey in our database of pubkeys for later use. + if toRipe != decryptedData[readPosition:readPosition+20]: + shared.printLock.acquire() + print 'The original sender of this message did not send it to you. Someone is attempting a Surreptitious Forwarding Attack.' + print 'See: http://world.std.com/~dtd/sign_encrypt/sign_encrypt7.html' + print 'your toRipe:', toRipe.encode('hex') + print 'embedded destination toRipe:', decryptedData[readPosition:readPosition+20].encode('hex') + shared.printLock.release() + return + readPosition += 20 + messageEncodingType, messageEncodingTypeLength = decodeVarint(decryptedData[readPosition:readPosition+10]) + readPosition += messageEncodingTypeLength + messageLength, messageLengthLength = decodeVarint(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 + 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] + try: + highlevelcrypto.verify(decryptedData[:positionOfBottomOfAckData],signature,pubSigningKey.encode('hex')) + print 'ECDSA verify passed' + except Exception, err: + print 'ECDSA verify failed', err + return + shared.printLock.acquire() + print 'As a matter of intellectual curiosity, here is the Bitcoin address associated with the keys owned by the other person:', calculateBitcoinAddressFromPubkey(pubSigningKey), ' ..and here is the testnet address:',calculateTestnetAddressFromPubkey(pubSigningKey),'. 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.' + shared.printLock.release() + #calculate the fromRipe. + sha = hashlib.new('sha512') + sha.update(pubSigningKey+pubEncryptionKey) + ripe = hashlib.new('ripemd160') + ripe.update(sha.digest()) + #Let's store the public key in case we want to reply to this person. + t = (ripe.digest(),'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'+'\xFF\xFF\xFF\xFF'+decryptedData[messageVersionLength:endOfThePublicKeyPosition],int(time.time()),'yes') + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''INSERT INTO pubkeys VALUES (?,?,?,?)''') + shared.sqlSubmitQueue.put(t) + shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() + shared.workerQueue.put(('newpubkey',(sendersAddressVersionNumber,sendersStreamNumber,ripe.digest()))) #This will 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. + fromAddress = encodeAddress(sendersAddressVersionNumber,sendersStreamNumber,ripe.digest()) + #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 decodeAddress(toAddress)[1] >= 3:#If the toAddress version number is 3 or higher: + if not shared.isAddressInMyAddressBookSubscriptionsListOrWhitelist(fromAddress): #If I'm not friendly with this person: + requiredNonceTrialsPerByte = shared.config.getint(toAddress,'noncetrialsperbyte') + requiredPayloadLengthExtraBytes = shared.config.getint(toAddress,'payloadlengthextrabytes') + if not self.isProofOfWorkSufficient(encryptedData,requiredNonceTrialsPerByte,requiredPayloadLengthExtraBytes): + print 'Proof of work in msg message 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 + t = (fromAddress,) + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''SELECT label FROM blacklist where address=? and enabled='1' ''') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + if queryreturn != []: + shared.printLock.acquire() + print 'Message ignored because address is in blacklist.' + shared.printLock.release() + blockMessage = True + else: #We're using a whitelist + t = (fromAddress,) + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''SELECT label FROM whitelist where address=? and enabled='1' ''') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + if queryreturn == []: + print 'Message ignored because address not in whitelist.' + blockMessage = True + if not blockMessage: + print 'fromAddress:', fromAddress + print 'First 150 characters of message:', repr(message[:150]) - @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 + toLabel = shared.config.get(toAddress, 'label') + if toLabel == '': + toLabel = toAddress -All parameters are optional. -''') + if messageEncodingType == 2: + bodyPositionIndex = string.find(message,'\nBody:') + if bodyPositionIndex > 1: + subject = message[8:bodyPositionIndex] + body = message[bodyPositionIndex+6:] + else: + subject = '' + body = message + elif messageEncodingType == 1: + body = message + subject = '' + elif messageEncodingType == 0: + print '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.' + else: + body = 'Unknown encoding type.\n\n' + repr(message) + subject = '' + if messageEncodingType <> 0: + shared.sqlLock.acquire() + t = (self.inventoryHash,toAddress,fromAddress,subject,int(time.time()),body,'inbox',messageEncodingType,0) + shared.sqlSubmitQueue.put('''INSERT INTO inbox VALUES (?,?,?,?,?,?,?,?,?)''') + shared.sqlSubmitQueue.put(t) + shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() + #self.emit(SIGNAL("displayNewInboxMessage(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"),self.inventoryHash,toAddress,fromAddress,subject,body) + shared.UISignalQueue.put(('displayNewInboxMessage',(self.inventoryHash,toAddress,fromAddress,subject,body))) - @staticmethod - def stop(): - """Stop main application""" - with printLock: - print('Stopping Bitmessage Deamon.') - shutdown.doCleanShutdown() + #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 shared.safeConfigGetBoolean('bitmessagesettings','apienabled'): + try: + apiNotifyPath = shared.config.get('bitmessagesettings','apinotifypath') + except: + apiNotifyPath = '' + if apiNotifyPath != '': + call([apiNotifyPath, "newMessage"]) - # .. 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'): - return None - address = config.get('bitmessagesettings', 'apiinterface') - port = config.getint('bitmessagesettings', 'apiport') - return {'address': address, 'port': port} + #Let us now check and see whether our receiving address is behaving as a mailing list + if shared.safeConfigGetBoolean(toAddress,'mailinglist'): + 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 = strftime("%a, %Y-%m-%d %H:%M:%S UTC",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. + ackdata = OpenSSL.rand(32) #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. + toAddress = '[Broadcast subscribers]' + ripe = '' + shared.sqlLock.acquire() + t = ('',toAddress,ripe,fromAddress,subject,message,ackdata,int(time.time()),'broadcastpending',1,1,'sent',2) + shared.sqlSubmitQueue.put('''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)''') + shared.sqlSubmitQueue.put(t) + shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() + + #self.emit(SIGNAL("displayNewSentMessage(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"),toAddress,'[Broadcast subscribers]',fromAddress,subject,message,ackdata) + shared.UISignalQueue.put(('displayNewSentMessage',(toAddress,'[Broadcast subscribers]',fromAddress,subject,message,ackdata))) + shared.workerQueue.put(('sendbroadcast',(fromAddress,subject,message))) + + if self.isAckDataValid(ackData): + print 'ackData is valid. Will process it.' + self.ackDataThatWeHaveYetToSend.append(ackData) #When we have processed all data, the processData function will pop the ackData out and process it as if it is a message received from our peer. + #Display timing data + timeRequiredToAttemptToDecryptMessage = time.time()- self.messageProcessingStartTime + successfullyDecryptMessageTimings.append(timeRequiredToAttemptToDecryptMessage) + sum = 0 + for item in successfullyDecryptMessageTimings: + sum += item + shared.printLock.acquire() + print 'Time to decrypt this message successfully:', timeRequiredToAttemptToDecryptMessage + print 'Average time for all message decryption successes since startup:', sum / len(successfullyDecryptMessageTimings) + shared.printLock.release() + + def isAckDataValid(self,ackData): + if len(ackData) < 24: + print 'The length of ackData is unreasonably short. Not sending ackData.' + return False + if ackData[0:4] != '\xe9\xbe\xb4\xd9': + print 'Ackdata magic bytes were wrong. Not sending ackData.' + return False + ackDataPayloadLength, = unpack('>L',ackData[16:20]) + if len(ackData)-24 != ackDataPayloadLength: + print 'ackData payload length doesn\'t match the payload length specified in the header. Not sending ackdata.' + return False + if ackData[4:16] != 'getpubkey\x00\x00\x00' and ackData[4:16] != 'pubkey\x00\x00\x00\x00\x00\x00' and ackData[4:16] != 'msg\x00\x00\x00\x00\x00\x00\x00\x00\x00' and ackData[4:16] != 'broadcast\x00\x00\x00' : + return False + return True + + 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 + else: + return '['+mailingListName+'] ' + subject + + #We have received a pubkey + def recpubkey(self,data): + self.pubkeyProcessingStartTime = time.time() + if len(data) < 146 or len(data) >600: #sanity check + return + #We must check to make sure the proof of work is sufficient. + if not self.isProofOfWorkSufficient(data): + print 'Proof of work in pubkey message insufficient.' + return + + readPosition = 8 #for the nonce + embeddedTime, = unpack('>I',data[readPosition:readPosition+4]) + + #This section is used for the transition from 32 bit time to 64 bit time in the protocol. + if embeddedTime == 0: + embeddedTime, = unpack('>Q',data[readPosition:readPosition+8]) + readPosition += 8 + else: + readPosition += 4 + + if embeddedTime < int(time.time())-lengthOfTimeToHoldOnToAllPubkeys: + shared.printLock.acquire() + print 'The embedded time in this pubkey message is too old. Ignoring. Embedded time is:', embeddedTime + shared.printLock.release() + return + if embeddedTime > int(time.time()) + 10800: + shared.printLock.acquire() + print 'The embedded time in this pubkey message more than several hours in the future. This is irrational. Ignoring message.' + shared.printLock.release() + return + addressVersion, varintLength = decodeVarint(data[readPosition:readPosition+10]) + readPosition += varintLength + streamNumber, varintLength = decodeVarint(data[readPosition:readPosition+10]) + readPosition += varintLength + if self.streamNumber != streamNumber: + print 'stream number embedded in this pubkey doesn\'t match our stream number. Ignoring.' + return + + inventoryHash = calculateInventoryHash(data) + shared.inventoryLock.acquire() + if inventoryHash in shared.inventory: + print 'We have already received this pubkey. Ignoring it.' + shared.inventoryLock.release() + return + elif isInSqlInventory(inventoryHash): + print 'We have already received this pubkey (it is stored on disk in the SQL inventory). Ignoring it.' + shared.inventoryLock.release() + return + objectType = 'pubkey' + shared.inventory[inventoryHash] = (objectType, self.streamNumber, data, embeddedTime) + shared.inventoryLock.release() + self.broadcastinv(inventoryHash) + #self.emit(SIGNAL("incrementNumberOfPubkeysProcessed()")) + shared.UISignalQueue.put(('incrementNumberOfPubkeysProcessed','no data')) + + self.processpubkey(data) + + lengthOfTimeWeShouldUseToProcessThisMessage = .2 + sleepTime = lengthOfTimeWeShouldUseToProcessThisMessage - (time.time()- self.pubkeyProcessingStartTime) + if sleepTime > 0: + shared.printLock.acquire() + print 'Timing attack mitigation: Sleeping for', sleepTime ,'seconds.' + shared.printLock.release() + time.sleep(sleepTime) + shared.printLock.acquire() + print 'Total pubkey processing time:', time.time()- self.pubkeyProcessingStartTime, 'seconds.' + shared.printLock.release() + + def processpubkey(self,data): + readPosition = 8 #for the nonce + embeddedTime, = unpack('>I',data[readPosition:readPosition+4]) + readPosition += 4 #for the time + addressVersion, varintLength = decodeVarint(data[readPosition:readPosition+10]) + readPosition += varintLength + streamNumber, varintLength = decodeVarint(data[readPosition:readPosition+10]) + readPosition += varintLength + if addressVersion == 0: + print '(Within processpubkey) addressVersion of 0 doesn\'t make sense.' + return + if addressVersion >= 4 or addressVersion == 1: + shared.printLock.acquire() + print 'This version of Bitmessage cannot handle version', addressVersion,'addresses.' + shared.printLock.release() + return + if addressVersion == 2: + if len(data) < 146: #sanity check. This is the minimum possible length. + print '(within processpubkey) payloadLength less than 146. Sanity check failed.' + return + bitfieldBehaviors = data[readPosition:readPosition+4] + readPosition += 4 + publicSigningKey = data[readPosition:readPosition+64] + #Is it possible for a public key to be invalid such that trying to encrypt or sign with it will cause an error? If it is, we should probably test these keys here. + readPosition += 64 + publicEncryptionKey = data[readPosition:readPosition+64] + if len(publicEncryptionKey) < 64: + print 'publicEncryptionKey length less than 64. Sanity check failed.' + return + sha = hashlib.new('sha512') + sha.update('\x04'+publicSigningKey+'\x04'+publicEncryptionKey) + ripeHasher = hashlib.new('ripemd160') + ripeHasher.update(sha.digest()) + ripe = ripeHasher.digest() + + shared.printLock.acquire() + print 'within recpubkey, addressVersion:', addressVersion, ', streamNumber:', streamNumber + print 'ripe', ripe.encode('hex') + print 'publicSigningKey in hex:', publicSigningKey.encode('hex') + print 'publicEncryptionKey in hex:', publicEncryptionKey.encode('hex') + shared.printLock.release() + + t = (ripe,) + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''SELECT usedpersonally FROM pubkeys WHERE hash=? AND usedpersonally='yes' ''') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + if queryreturn != []: #if this pubkey is already in our database and if we have used it personally: + print 'We HAVE used this pubkey personally. Updating time.' + t = (ripe,data,embeddedTime,'yes') + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''INSERT INTO pubkeys VALUES (?,?,?,?)''') + shared.sqlSubmitQueue.put(t) + shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() + shared.workerQueue.put(('newpubkey',(addressVersion,streamNumber,ripe))) + else: + print 'We have NOT used this pubkey personally. Inserting in database.' + t = (ripe,data,embeddedTime,'no') #This will also update the embeddedTime. + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''INSERT INTO pubkeys VALUES (?,?,?,?)''') + shared.sqlSubmitQueue.put(t) + shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() + shared.workerQueue.put(('newpubkey',(addressVersion,streamNumber,ripe))) + if addressVersion == 3: + if len(data) < 170: #sanity check. + print '(within processpubkey) payloadLength less than 170. Sanity check failed.' + return + bitfieldBehaviors = data[readPosition:readPosition+4] + readPosition += 4 + publicSigningKey = '\x04'+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, we should probably test these keys here. + readPosition += 64 + publicEncryptionKey = '\x04'+data[readPosition:readPosition+64] + readPosition += 64 + specifiedNonceTrialsPerByte, specifiedNonceTrialsPerByteLength = decodeVarint(data[readPosition:readPosition+10]) + readPosition += specifiedNonceTrialsPerByteLength + specifiedPayloadLengthExtraBytes, specifiedPayloadLengthExtraBytesLength = decodeVarint(data[readPosition:readPosition+10]) + readPosition += specifiedPayloadLengthExtraBytesLength + signatureLength, signatureLengthLength = decodeVarint(data[readPosition:readPosition+10]) + signature = data[readPosition:readPosition+signatureLengthLength] + try: + highlevelcrypto.verify(data[8:readPosition],signature,publicSigningKey.encode('hex')) + print 'ECDSA verify passed (within processpubkey)' + except Exception, err: + print 'ECDSA verify failed (within processpubkey)', err + return + + sha = hashlib.new('sha512') + sha.update(publicSigningKey+publicEncryptionKey) + ripeHasher = hashlib.new('ripemd160') + ripeHasher.update(sha.digest()) + ripe = ripeHasher.digest() + + shared.printLock.acquire() + print 'within recpubkey, addressVersion:', addressVersion, ', streamNumber:', streamNumber + print 'ripe', ripe.encode('hex') + print 'publicSigningKey in hex:', publicSigningKey.encode('hex') + print 'publicEncryptionKey in hex:', publicEncryptionKey.encode('hex') + shared.printLock.release() + + t = (ripe,) + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''SELECT usedpersonally FROM pubkeys WHERE hash=? AND usedpersonally='yes' ''') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + if queryreturn != []: #if this pubkey is already in our database and if we have used it personally: + print 'We HAVE used this pubkey personally. Updating time.' + t = (ripe,data,embeddedTime,'yes') + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''INSERT INTO pubkeys VALUES (?,?,?,?)''') + shared.sqlSubmitQueue.put(t) + shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() + else: + print 'We have NOT used this pubkey personally. Inserting in database.' + t = (ripe,data,embeddedTime,'no') #This will also update the embeddedTime. + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''INSERT INTO pubkeys VALUES (?,?,?,?)''') + shared.sqlSubmitQueue.put(t) + shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() + shared.workerQueue.put(('newpubkey',(addressVersion,streamNumber,ripe))) -def main(): - """Triggers main module""" - mainprogram = Main() - mainprogram.start() + #We have received a getpubkey message + def recgetpubkey(self,data): + if not self.isProofOfWorkSufficient(data): + print 'Proof of work in getpubkey message insufficient.' + return + if len(data) < 34: + print 'getpubkey message doesn\'t contain enough data. Ignoring.' + return + readPosition = 8 #bypass the nonce + embeddedTime, = unpack('>I',data[readPosition:readPosition+4]) + #This section is used for the transition from 32 bit time to 64 bit time in the protocol. + if embeddedTime == 0: + embeddedTime, = unpack('>Q',data[readPosition:readPosition+8]) + readPosition += 8 + else: + readPosition += 4 + + if embeddedTime > int(time.time())+10800: + print 'The time in this getpubkey message is too new. Ignoring it. Time:', embeddedTime + return + if embeddedTime < int(time.time())-maximumAgeOfAnObjectThatIAmWillingToAccept: + print 'The time in this getpubkey message is too old. Ignoring it. Time:', embeddedTime + return + requestedAddressVersionNumber, addressVersionLength = decodeVarint(data[readPosition:readPosition+10]) + readPosition += addressVersionLength + streamNumber, streamNumberLength = decodeVarint(data[readPosition:readPosition+10]) + if streamNumber <> self.streamNumber: + print 'The streamNumber', streamNumber, 'doesn\'t match our stream number:', self.streamNumber + return + readPosition += streamNumberLength + + inventoryHash = calculateInventoryHash(data) + shared.inventoryLock.acquire() + if inventoryHash in shared.inventory: + print 'We have already received this getpubkey request. Ignoring it.' + shared.inventoryLock.release() + return + elif isInSqlInventory(inventoryHash): + print 'We have already received this getpubkey request (it is stored on disk in the SQL inventory). Ignoring it.' + shared.inventoryLock.release() + return + + objectType = 'getpubkey' + shared.inventory[inventoryHash] = (objectType, self.streamNumber, data, embeddedTime) + shared.inventoryLock.release() + #This getpubkey request is valid so far. Forward to peers. + self.broadcastinv(inventoryHash) + + if requestedAddressVersionNumber == 0: + print 'The requestedAddressVersionNumber of the pubkey request is zero. That doesn\'t make any sense. Ignoring it.' + return + elif requestedAddressVersionNumber == 1: + print 'The requestedAddressVersionNumber of the pubkey request is 1 which isn\'t supported anymore. Ignoring it.' + return + elif requestedAddressVersionNumber > 3: + print 'The requestedAddressVersionNumber of the pubkey request is too high. Can\'t understand. Ignoring it.' + return + + requestedHash = data[readPosition:readPosition+20] + if len(requestedHash) != 20: + print 'The length of the requested hash is not 20 bytes. Something is wrong. Ignoring.' + return + print 'the hash requested in this getpubkey request is:', requestedHash.encode('hex') + + """shared.sqlLock.acquire() + t = (requestedHash,int(time.time())-lengthOfTimeToHoldOnToAllPubkeys) #this prevents SQL injection + shared.sqlSubmitQueue.put('''SELECT hash, transmitdata, time FROM pubkeys WHERE hash=? AND havecorrectnonce=1 AND time>?''') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + if queryreturn != []: + for row in queryreturn: + hash, payload, timeEncodedInPubkey = row + shared.printLock.acquire() + print 'We have the requested pubkey stored in our database of pubkeys. Sending it.' + shared.printLock.release() + inventoryHash = calculateInventoryHash(payload) + objectType = 'pubkey' + shared.inventory[inventoryHash] = (objectType, self.streamNumber, payload, timeEncodedInPubkey)#If the time embedded in this pubkey is more than 3 days old then this object isn't going to last very long in the inventory- the cleanerThread is going to come along and move it from the inventory in memory to the SQL inventory and then delete it from the SQL inventory. It should still find its way back to the original requestor if he is online however. + self.broadcastinv(inventoryHash)""" + #else: #the pubkey is not in our database of pubkeys. Let's check if the requested key is ours (which would mean we should do the POW, put it in the pubkey table, and broadcast out the pubkey.) + if requestedHash in shared.myAddressesByHash: #if this address hash is one of mine + if decodeAddress(shared.myAddressesByHash[requestedHash])[1] != requestedAddressVersionNumber: + shared.printLock.acquire() + sys.stderr.write('(Within the recgetpubkey function) Someone requested one of my pubkeys but the requestedAddressVersionNumber doesn\'t match my actual address version number. That shouldn\'t have happened. Ignoring.\n') + shared.printLock.release() + return + try: + lastPubkeySendTime = int(shared.config.get(shared.myAddressesByHash[requestedHash],'lastpubkeysendtime')) + except: + lastPubkeySendTime = 0 + if lastPubkeySendTime < time.time()-lengthOfTimeToHoldOnToAllPubkeys: #If the last time we sent our pubkey was 28 days ago + shared.printLock.acquire() + print '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.' + shared.printLock.release() + if requestedAddressVersionNumber == 2: + shared.workerQueue.put(('doPOWForMyV2Pubkey',requestedHash)) + elif requestedAddressVersionNumber == 3: + shared.workerQueue.put(('doPOWForMyV3Pubkey',requestedHash)) + else: + shared.printLock.acquire() + print 'Found getpubkey-requested-hash in my list of EC hashes BUT we already sent it recently. Ignoring request. The lastPubkeySendTime is:',lastPubkeySendTime + shared.printLock.release() + else: + shared.printLock.acquire() + print 'This getpubkey request is not for any of my keys.' + shared.printLock.release() + + + #We have received an inv message + def recinv(self,data): + totalNumberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave = 0 + print 'number of keys(hosts) in numberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHavePerPeer:', len(numberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHavePerPeer) + for key, value in numberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHavePerPeer.items(): + totalNumberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave += value + print 'totalNumberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave = ', totalNumberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave + numberOfItemsInInv, lengthOfVarint = decodeVarint(data[:10]) + if len(data) < lengthOfVarint + (numberOfItemsInInv * 32): + print '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 totalNumberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave > 200000 and len(self.objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave) > 1000: #inv flooding attack mitigation + shared.printLock.acquire() + print 'We already have', totalNumberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave, 'items yet to retrieve from peers and over 1000 from this node in particular. Ignoring this inv message.' + shared.printLock.release() + return + self.objectsOfWhichThisRemoteNodeIsAlreadyAware[data[lengthOfVarint:32+lengthOfVarint]] = 0 + if data[lengthOfVarint:32+lengthOfVarint] in shared.inventory: + shared.printLock.acquire() + print 'Inventory (in memory) has inventory item already.' + shared.printLock.release() + elif isInSqlInventory(data[lengthOfVarint:32+lengthOfVarint]): + print 'Inventory (SQL on disk) has inventory item already.' + else: + self.sendgetdata(data[lengthOfVarint:32+lengthOfVarint]) + else: + print 'inv message lists', numberOfItemsInInv, 'objects.' + for i in range(numberOfItemsInInv): #upon finishing dealing with an incoming message, the receiveDataThread will request a random object from the peer. 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(data[lengthOfVarint+(32*i):32+lengthOfVarint+(32*i)]) == 32: #The length of an inventory hash should be 32. If it isn't 32 then the remote node is either badly programmed or behaving nefariously. + if totalNumberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave > 200000 and len(self.objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave) > 1000: #inv flooding attack mitigation + shared.printLock.acquire() + print 'We already have', totalNumberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave, 'items yet to retrieve from peers and over',len(self.objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave),'from this node in particular. Ignoring the rest of this inv message.' + shared.printLock.release() + break + self.objectsOfWhichThisRemoteNodeIsAlreadyAware[data[lengthOfVarint+(32*i):32+lengthOfVarint+(32*i)]] = 0 + self.objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave[data[lengthOfVarint+(32*i):32+lengthOfVarint+(32*i)]] = 0 + numberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHavePerPeer[self.HOST] = len(self.objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave) + + + #Send a getdata message to our peer to request the object with the given hash + def sendgetdata(self,hash): + shared.printLock.acquire() + print 'sending getdata to retrieve object with hash:', hash.encode('hex') + shared.printLock.release() + payload = '\x01' + hash + headerData = '\xe9\xbe\xb4\xd9' #magic bits, slighly different from Bitcoin's magic bits. + headerData += 'getdata\x00\x00\x00\x00\x00' + headerData += pack('>L',len(payload)) #payload length. Note that we add an extra 8 for the nonce. + headerData += hashlib.sha512(payload).digest()[:4] + try: + self.sock.sendall(headerData + payload) + except Exception, err: + #if not 'Bad file descriptor' in err: + shared.printLock.acquire() + sys.stderr.write('sock.sendall error: %s\n' % err) + shared.printLock.release() + + #We have received a getdata request from our peer + def recgetdata(self, data): + numberOfRequestedInventoryItems, lengthOfVarint = decodeVarint(data[:10]) + if len(data) < lengthOfVarint + (32 * numberOfRequestedInventoryItems): + print 'getdata message does not contain enough data. Ignoring.' + return + for i in xrange(numberOfRequestedInventoryItems): + hash = data[lengthOfVarint+(i*32):32+lengthOfVarint+(i*32)] + shared.printLock.acquire() + print 'received getdata request for item:', hash.encode('hex') + shared.printLock.release() + #print 'inventory is', shared.inventory + if hash in shared.inventory: + objectType, streamNumber, payload, receivedTime = shared.inventory[hash] + self.sendData(objectType,payload) + else: + t = (hash,) + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''select objecttype, payload from inventory where hash=?''') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + if queryreturn <> []: + for row in queryreturn: + objectType, payload = row + self.sendData(objectType,payload) + else: + print 'Someone asked for an object with a getdata which is not in either our memory inventory or our SQL inventory. That shouldn\'t have happened.' + + #Our peer has requested (in a getdata message) that we send an object. + def sendData(self,objectType,payload): + headerData = '\xe9\xbe\xb4\xd9' #magic bits, slighly different from Bitcoin's magic bits. + if objectType == 'pubkey': + shared.printLock.acquire() + print 'sending pubkey' + shared.printLock.release() + headerData += 'pubkey\x00\x00\x00\x00\x00\x00' + elif objectType == 'getpubkey' or objectType == 'pubkeyrequest': + shared.printLock.acquire() + print 'sending getpubkey' + shared.printLock.release() + headerData += 'getpubkey\x00\x00\x00' + elif objectType == 'msg': + shared.printLock.acquire() + print 'sending msg' + shared.printLock.release() + headerData += 'msg\x00\x00\x00\x00\x00\x00\x00\x00\x00' + elif objectType == 'broadcast': + shared.printLock.acquire() + print 'sending broadcast' + shared.printLock.release() + headerData += 'broadcast\x00\x00\x00' + else: + sys.stderr.write('Error: sendData has been asked to send a strange objectType: %s\n' % str(objectType)) + return + headerData += pack('>L',len(payload)) #payload length. + headerData += hashlib.sha512(payload).digest()[:4] + try: + self.sock.sendall(headerData + payload) + except Exception, err: + #if not 'Bad file descriptor' in err: + shared.printLock.acquire() + sys.stderr.write('sock.sendall error: %s\n' % err) + shared.printLock.release() + + #Send an inv message with just one hash to all of our peers + def broadcastinv(self,hash): + shared.printLock.acquire() + print 'broadcasting inv with hash:', hash.encode('hex') + shared.printLock.release() + shared.broadcastToSendDataQueues((self.streamNumber, 'sendinv', hash)) + + + #We have received an addr message. + def recaddr(self,data): + listOfAddressDetailsToBroadcastToPeers = [] + numberOfAddressesIncluded = 0 + numberOfAddressesIncluded, lengthOfNumberOfAddresses = decodeVarint(data[:10]) + + if verbose >= 1: + shared.printLock.acquire() + print 'addr message contains', numberOfAddressesIncluded, 'IP addresses.' + shared.printLock.release() + + if self.remoteProtocolVersion == 1: + if numberOfAddressesIncluded > 1000 or numberOfAddressesIncluded == 0: + return + if len(data) != lengthOfNumberOfAddresses + (34 * numberOfAddressesIncluded): + print 'addr message does not contain the correct amount of data. Ignoring.' + return + + needToWriteKnownNodesToDisk = False + for i in range(0,numberOfAddressesIncluded): + try: + if data[16+lengthOfNumberOfAddresses+(34*i):28+lengthOfNumberOfAddresses+(34*i)] != '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF': + shared.printLock.acquire() + print 'Skipping IPv6 address.', repr(data[16+lengthOfNumberOfAddresses+(34*i):28+lengthOfNumberOfAddresses+(34*i)]) + shared.printLock.release() + continue + except Exception, err: + shared.printLock.acquire() + sys.stderr.write('ERROR TRYING TO UNPACK recaddr (to test for an IPv6 address). Message: %s\n' % str(err)) + shared.printLock.release() + break #giving up on unpacking any more. We should still be connected however. + + try: + recaddrStream, = unpack('>I',data[4+lengthOfNumberOfAddresses+(34*i):8+lengthOfNumberOfAddresses+(34*i)]) + except Exception, err: + shared.printLock.acquire() + sys.stderr.write('ERROR TRYING TO UNPACK recaddr (recaddrStream). Message: %s\n' % str(err)) + shared.printLock.release() + break #giving up on unpacking any more. We should still be connected however. + 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 + try: + recaddrServices, = unpack('>Q',data[8+lengthOfNumberOfAddresses+(34*i):16+lengthOfNumberOfAddresses+(34*i)]) + except Exception, err: + shared.printLock.acquire() + sys.stderr.write('ERROR TRYING TO UNPACK recaddr (recaddrServices). Message: %s\n' % str(err)) + shared.printLock.release() + break #giving up on unpacking any more. We should still be connected however. + + try: + recaddrPort, = unpack('>H',data[32+lengthOfNumberOfAddresses+(34*i):34+lengthOfNumberOfAddresses+(34*i)]) + except Exception, err: + shared.printLock.acquire() + sys.stderr.write('ERROR TRYING TO UNPACK recaddr (recaddrPort). Message: %s\n' % str(err)) + shared.printLock.release() + break #giving up on unpacking any more. We should still be connected however. + #print 'Within recaddr(): IP', recaddrIP, ', Port', recaddrPort, ', i', i + hostFromAddrMessage = socket.inet_ntoa(data[28+lengthOfNumberOfAddresses+(34*i):32+lengthOfNumberOfAddresses+(34*i)]) + #print 'hostFromAddrMessage', hostFromAddrMessage + if data[28+lengthOfNumberOfAddresses+(34*i)] == '\x7F': + print 'Ignoring IP address in loopback range:', hostFromAddrMessage + continue + if isHostInPrivateIPRange(hostFromAddrMessage): + print 'Ignoring IP address in private range:', hostFromAddrMessage + continue + timeSomeoneElseReceivedMessageFromThisNode, = unpack('>I',data[lengthOfNumberOfAddresses+(34*i):4+lengthOfNumberOfAddresses+(34*i)]) #This is the 'time' value in the received addr message. + 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. + shared.knownNodesLock.acquire() + shared.knownNodes[recaddrStream] = {} + shared.knownNodesLock.release() + if hostFromAddrMessage 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. + shared.knownNodesLock.acquire() + shared.knownNodes[recaddrStream][hostFromAddrMessage] = (recaddrPort, timeSomeoneElseReceivedMessageFromThisNode) + shared.knownNodesLock.release() + needToWriteKnownNodesToDisk = True + hostDetails = (timeSomeoneElseReceivedMessageFromThisNode, recaddrStream, recaddrServices, hostFromAddrMessage, recaddrPort) + listOfAddressDetailsToBroadcastToPeers.append(hostDetails) + else: + PORT, timeLastReceivedMessageFromThisNode = shared.knownNodes[recaddrStream][hostFromAddrMessage]#PORT in this case is either the port we used to connect to the remote node, or the port that was specified by someone else in a past addr message. + if (timeLastReceivedMessageFromThisNode < timeSomeoneElseReceivedMessageFromThisNode) and (timeSomeoneElseReceivedMessageFromThisNode < int(time.time())): + shared.knownNodesLock.acquire() + shared.knownNodes[recaddrStream][hostFromAddrMessage] = (PORT, timeSomeoneElseReceivedMessageFromThisNode) + shared.knownNodesLock.release() + if PORT != recaddrPort: + print 'Strange occurance: The port specified in an addr message', str(recaddrPort),'does not match the port',str(PORT),'that this program (or some other peer) used to connect to it',str(hostFromAddrMessage),'. Perhaps they changed their port or are using a strange NAT configuration.' + if needToWriteKnownNodesToDisk: #Runs if any nodes were new to us. Also, share those nodes with our peers. + shared.knownNodesLock.acquire() + output = open(shared.appdata + 'knownnodes.dat', 'wb') + pickle.dump(shared.knownNodes, output) + output.close() + shared.knownNodesLock.release() + self.broadcastaddr(listOfAddressDetailsToBroadcastToPeers) #no longer broadcast + shared.printLock.acquire() + print 'knownNodes currently has', len(shared.knownNodes[self.streamNumber]), 'nodes for this stream.' + shared.printLock.release() + elif self.remoteProtocolVersion >= 2: #The difference is that in protocol version 2, network addresses use 64 bit times rather than 32 bit times. + if numberOfAddressesIncluded > 1000 or numberOfAddressesIncluded == 0: + return + if len(data) != lengthOfNumberOfAddresses + (38 * numberOfAddressesIncluded): + print 'addr message does not contain the correct amount of data. Ignoring.' + return + + needToWriteKnownNodesToDisk = False + for i in range(0,numberOfAddressesIncluded): + try: + if data[20+lengthOfNumberOfAddresses+(38*i):32+lengthOfNumberOfAddresses+(38*i)] != '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF': + shared.printLock.acquire() + print 'Skipping IPv6 address.', repr(data[20+lengthOfNumberOfAddresses+(38*i):32+lengthOfNumberOfAddresses+(38*i)]) + shared.printLock.release() + continue + except Exception, err: + shared.printLock.acquire() + sys.stderr.write('ERROR TRYING TO UNPACK recaddr (to test for an IPv6 address). Message: %s\n' % str(err)) + shared.printLock.release() + break #giving up on unpacking any more. We should still be connected however. + + try: + recaddrStream, = unpack('>I',data[8+lengthOfNumberOfAddresses+(38*i):12+lengthOfNumberOfAddresses+(38*i)]) + except Exception, err: + shared.printLock.acquire() + sys.stderr.write('ERROR TRYING TO UNPACK recaddr (recaddrStream). Message: %s\n' % str(err)) + shared.printLock.release() + break #giving up on unpacking any more. We should still be connected however. + 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 + try: + recaddrServices, = unpack('>Q',data[12+lengthOfNumberOfAddresses+(38*i):20+lengthOfNumberOfAddresses+(38*i)]) + except Exception, err: + shared.printLock.acquire() + sys.stderr.write('ERROR TRYING TO UNPACK recaddr (recaddrServices). Message: %s\n' % str(err)) + shared.printLock.release() + break #giving up on unpacking any more. We should still be connected however. + + try: + recaddrPort, = unpack('>H',data[36+lengthOfNumberOfAddresses+(38*i):38+lengthOfNumberOfAddresses+(38*i)]) + except Exception, err: + shared.printLock.acquire() + sys.stderr.write('ERROR TRYING TO UNPACK recaddr (recaddrPort). Message: %s\n' % str(err)) + shared.printLock.release() + break #giving up on unpacking any more. We should still be connected however. + #print 'Within recaddr(): IP', recaddrIP, ', Port', recaddrPort, ', i', i + hostFromAddrMessage = socket.inet_ntoa(data[32+lengthOfNumberOfAddresses+(38*i):36+lengthOfNumberOfAddresses+(38*i)]) + #print 'hostFromAddrMessage', hostFromAddrMessage + if data[32+lengthOfNumberOfAddresses+(38*i)] == '\x7F': + print 'Ignoring IP address in loopback range:', hostFromAddrMessage + continue + if data[32+lengthOfNumberOfAddresses+(38*i)] == '\x0A': + print 'Ignoring IP address in private range:', hostFromAddrMessage + continue + if data[32+lengthOfNumberOfAddresses+(38*i):34+lengthOfNumberOfAddresses+(38*i)] == '\xC0A8': + print 'Ignoring IP address in private range:', hostFromAddrMessage + 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. + shared.knownNodesLock.acquire() + shared.knownNodes[recaddrStream] = {} + shared.knownNodesLock.release() + if hostFromAddrMessage 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. + shared.knownNodesLock.acquire() + shared.knownNodes[recaddrStream][hostFromAddrMessage] = (recaddrPort, timeSomeoneElseReceivedMessageFromThisNode) + shared.knownNodesLock.release() + shared.printLock.acquire() + print 'added new node', hostFromAddrMessage, 'to knownNodes in stream', recaddrStream + shared.printLock.release() + needToWriteKnownNodesToDisk = True + hostDetails = (timeSomeoneElseReceivedMessageFromThisNode, recaddrStream, recaddrServices, hostFromAddrMessage, recaddrPort) + listOfAddressDetailsToBroadcastToPeers.append(hostDetails) + else: + PORT, timeLastReceivedMessageFromThisNode = shared.knownNodes[recaddrStream][hostFromAddrMessage]#PORT in this case is either the port we used to connect to the remote node, or the port that was specified by someone else in a past addr message. + if (timeLastReceivedMessageFromThisNode < timeSomeoneElseReceivedMessageFromThisNode) and (timeSomeoneElseReceivedMessageFromThisNode < int(time.time())): + shared.knownNodesLock.acquire() + shared.knownNodes[recaddrStream][hostFromAddrMessage] = (PORT, timeSomeoneElseReceivedMessageFromThisNode) + shared.knownNodesLock.release() + if PORT != recaddrPort: + print 'Strange occurance: The port specified in an addr message', str(recaddrPort),'does not match the port',str(PORT),'that this program (or some other peer) used to connect to it',str(hostFromAddrMessage),'. Perhaps they changed their port or are using a strange NAT configuration.' + if needToWriteKnownNodesToDisk: #Runs if any nodes were new to us. Also, share those nodes with our peers. + shared.knownNodesLock.acquire() + output = open(shared.appdata + 'knownnodes.dat', 'wb') + pickle.dump(shared.knownNodes, output) + output.close() + shared.knownNodesLock.release() + self.broadcastaddr(listOfAddressDetailsToBroadcastToPeers) + shared.printLock.acquire() + print 'knownNodes currently has', len(shared.knownNodes[self.streamNumber]), 'nodes for this stream.' + shared.printLock.release() + + + #Function runs when we want to broadcast an addr message to all of our peers. Runs when we learn of nodes that we didn't previously know about and want to share them with our peers. + def broadcastaddr(self,listOfAddressDetailsToBroadcastToPeers): + numberOfAddressesInAddrMessage = len(listOfAddressDetailsToBroadcastToPeers) + payload = '' + for hostDetails in listOfAddressDetailsToBroadcastToPeers: + 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 += '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF' + socket.inet_aton(host) + payload += pack('>H',port)#remote port + + payload = encodeVarint(numberOfAddressesInAddrMessage) + payload + datatosend = '\xE9\xBE\xB4\xD9addr\x00\x00\x00\x00\x00\x00\x00\x00' + datatosend = datatosend + pack('>L',len(payload)) #payload length + datatosend = datatosend + hashlib.sha512(payload).digest()[0:4] + datatosend = datatosend + payload + + if verbose >= 1: + shared.printLock.acquire() + print 'Broadcasting addr with', numberOfAddressesInAddrMessage, 'entries.' + shared.printLock.release() + shared.broadcastToSendDataQueues((self.streamNumber, 'sendaddr', datatosend)) + + #Send a big addr message to our peer + 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. + shared.knownNodesLock.acquire() + if len(shared.knownNodes[self.streamNumber]) > 0: + for i in range(500): + random.seed() + HOST, = random.sample(shared.knownNodes[self.streamNumber], 1) + if isHostInPrivateIPRange(HOST): + continue + addrsInMyStream[HOST] = shared.knownNodes[self.streamNumber][HOST] + if len(shared.knownNodes[self.streamNumber*2]) > 0: + for i in range(250): + random.seed() + HOST, = random.sample(shared.knownNodes[self.streamNumber*2], 1) + if isHostInPrivateIPRange(HOST): + continue + addrsInChildStreamLeft[HOST] = shared.knownNodes[self.streamNumber*2][HOST] + if len(shared.knownNodes[(self.streamNumber*2)+1]) > 0: + for i in range(250): + random.seed() + HOST, = random.sample(shared.knownNodes[(self.streamNumber*2)+1], 1) + if isHostInPrivateIPRange(HOST): + continue + addrsInChildStreamRight[HOST] = shared.knownNodes[(self.streamNumber*2)+1][HOST] + shared.knownNodesLock.release() + numberOfAddressesInAddrMessage = 0 + payload = '' + #print 'addrsInMyStream.items()', addrsInMyStream.items() + for HOST, value in addrsInMyStream.items(): + PORT, timeLastReceivedMessageFromThisNode = value + if timeLastReceivedMessageFromThisNode > (int(time.time())- maximumAgeOfNodesThatIAdvertiseToOthers): #If it is younger than 3 hours old.. + numberOfAddressesInAddrMessage += 1 + if self.remoteProtocolVersion == 1: + payload += pack('>I',timeLastReceivedMessageFromThisNode) #32-bit time + else: + payload += pack('>Q',timeLastReceivedMessageFromThisNode) #64-bit time + payload += pack('>I',self.streamNumber) + payload += pack('>q',1) #service bit flags offered by this node + payload += '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF' + socket.inet_aton(HOST) + payload += pack('>H',PORT)#remote port + for HOST, value in addrsInChildStreamLeft.items(): + PORT, timeLastReceivedMessageFromThisNode = value + if timeLastReceivedMessageFromThisNode > (int(time.time())- maximumAgeOfNodesThatIAdvertiseToOthers): #If it is younger than 3 hours old.. + numberOfAddressesInAddrMessage += 1 + if self.remoteProtocolVersion == 1: + payload += pack('>I',timeLastReceivedMessageFromThisNode) #32-bit time + else: + 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 += '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF' + socket.inet_aton(HOST) + payload += pack('>H',PORT)#remote port + for HOST, value in addrsInChildStreamRight.items(): + PORT, timeLastReceivedMessageFromThisNode = value + if timeLastReceivedMessageFromThisNode > (int(time.time())- maximumAgeOfNodesThatIAdvertiseToOthers): #If it is younger than 3 hours old.. + numberOfAddressesInAddrMessage += 1 + if self.remoteProtocolVersion == 1: + payload += pack('>I',timeLastReceivedMessageFromThisNode) #32-bit time + else: + 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 += '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF' + socket.inet_aton(HOST) + payload += pack('>H',PORT)#remote port + + payload = encodeVarint(numberOfAddressesInAddrMessage) + payload + datatosend = '\xE9\xBE\xB4\xD9addr\x00\x00\x00\x00\x00\x00\x00\x00' + datatosend = datatosend + pack('>L',len(payload)) #payload length + datatosend = datatosend + hashlib.sha512(payload).digest()[0:4] + datatosend = datatosend + payload + try: + self.sock.sendall(datatosend) + if verbose >= 1: + shared.printLock.acquire() + print 'Sending addr with', numberOfAddressesInAddrMessage, 'entries.' + shared.printLock.release() + except Exception, err: + #if not 'Bad file descriptor' in err: + shared.printLock.acquire() + sys.stderr.write('sock.sendall error: %s\n' % err) + shared.printLock.release() + + #We have received a version message + def recversion(self,data): + if len(data) < 83: + #This version message is unreasonably short. Forget it. + return + elif not self.verackSent: + self.remoteProtocolVersion, = unpack('>L',data[:4]) + #print 'remoteProtocolVersion', self.remoteProtocolVersion + 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] + readPosition += useragentLength + numberOfStreamsInVersionMessage, lengthOfNumberOfStreamsInVersionMessage = decodeVarint(data[readPosition:]) + readPosition += lengthOfNumberOfStreamsInVersionMessage + self.streamNumber, lengthOfRemoteStreamNumber = decodeVarint(data[readPosition:]) + shared.printLock.acquire() + print 'Remote node useragent:', useragent, ' stream number:', self.streamNumber + shared.printLock.release() + if self.streamNumber != 1: + shared.broadcastToSendDataQueues((0, 'shutdown', self.HOST)) + shared.printLock.acquire() + print 'Closed connection to', self.HOST, 'because they are interested in stream', self.streamNumber,'.' + shared.printLock.release() + return + shared.connectedHostsList[self.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 sendData thread doesn't know the stream. We have to set it. + if not self.initiatedConnection: + shared.broadcastToSendDataQueues((0,'setStreamNumber',(self.HOST,self.streamNumber))) + if data[72:80] == eightBytesOfRandomDataUsedToDetectConnectionsToSelf: + shared.broadcastToSendDataQueues((0, 'shutdown', self.HOST)) + shared.printLock.acquire() + print 'Closing connection to myself: ', self.HOST + shared.printLock.release() + return + shared.broadcastToSendDataQueues((0,'setRemoteProtocolVersion',(self.HOST,self.remoteProtocolVersion))) + + shared.knownNodesLock.acquire() + shared.knownNodes[self.streamNumber][self.HOST] = (self.remoteNodeIncomingPort, int(time.time())) + output = open(shared.appdata + 'knownnodes.dat', 'wb') + pickle.dump(shared.knownNodes, output) + output.close() + shared.knownNodesLock.release() + + self.sendverack() + if self.initiatedConnection == False: + self.sendversion() + + #Sends a version message + def sendversion(self): + shared.printLock.acquire() + print 'Sending version message' + shared.printLock.release() + try: + self.sock.sendall(assembleVersionMessage(self.HOST,self.PORT,self.streamNumber)) + except Exception, err: + #if not 'Bad file descriptor' in err: + shared.printLock.acquire() + sys.stderr.write('sock.sendall error: %s\n' % err) + shared.printLock.release() + + #Sends a verack message + def sendverack(self): + shared.printLock.acquire() + print 'Sending verack' + shared.printLock.release() + try: + self.sock.sendall('\xE9\xBE\xB4\xD9\x76\x65\x72\x61\x63\x6B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xcf\x83\xe1\x35') + except Exception, err: + #if not 'Bad file descriptor' in err: + shared.printLock.acquire() + sys.stderr.write('sock.sendall error: %s\n' % err) + shared.printLock.release() + #cf 83 e1 35 + self.verackSent = True + if self.verackReceived == True: + self.connectionFullyEstablished() + + + +#Every connection to a peer has a sendDataThread (and also a receiveDataThread). +class sendDataThread(threading.Thread): + def __init__(self): + threading.Thread.__init__(self) + self.mailbox = Queue.Queue() + shared.sendDataQueues.append(self.mailbox) + shared.printLock.acquire() + print 'The length of sendDataQueues at sendDataThread init is:', len(shared.sendDataQueues) + shared.printLock.release() + self.data = '' + + def setup(self,sock,HOST,PORT,streamNumber,objectsOfWhichThisRemoteNodeIsAlreadyAware): + self.sock = sock + self.HOST = HOST + self.PORT = PORT + self.streamNumber = streamNumber + self.remoteProtocolVersion = -1 #This must be set using setRemoteProtocolVersion command which is sent through the self.mailbox 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.objectsOfWhichThisRemoteNodeIsAlreadyAware = objectsOfWhichThisRemoteNodeIsAlreadyAware + shared.printLock.acquire() + print 'The streamNumber of this sendDataThread (ID:', str(id(self))+') at setup() is', self.streamNumber + shared.printLock.release() + + def sendVersionMessage(self): + datatosend = assembleVersionMessage(self.HOST,self.PORT,self.streamNumber)#the IP and port of the remote host, and my streamNumber. + + shared.printLock.acquire() + print 'Sending version packet: ', repr(datatosend) + shared.printLock.release() + try: + self.sock.sendall(datatosend) + except Exception, err: + #if not 'Bad file descriptor' in err: + shared.printLock.acquire() + sys.stderr.write('sock.sendall error: %s\n' % err) + shared.printLock.release() + self.versionSent = 1 + + def run(self): + while True: + deststream,command,data = self.mailbox.get() + #shared.printLock.acquire() + #print 'sendDataThread, destream:', deststream, ', Command:', command, ', ID:',id(self), ', HOST:', self.HOST + #shared.printLock.release() + + if deststream == self.streamNumber or deststream == 0: + if command == 'shutdown': + if data == self.HOST or data == 'all': + shared.printLock.acquire() + print 'sendDataThread (associated with', self.HOST,') ID:',id(self), 'shutting down now.' + shared.printLock.release() + try: + self.sock.shutdown(socket.SHUT_RDWR) + self.sock.close() + except: + pass + shared.sendDataQueues.remove(self.mailbox) + shared.printLock.acquire() + print 'len of sendDataQueues', len(shared.sendDataQueues) + shared.printLock.release() + 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': + hostInMessage, specifiedStreamNumber = data + if hostInMessage == self.HOST: + shared.printLock.acquire() + print 'setting the stream number in the sendData thread (ID:',id(self), ') to', specifiedStreamNumber + shared.printLock.release() + self.streamNumber = specifiedStreamNumber + elif command == 'setRemoteProtocolVersion': + hostInMessage, specifiedRemoteProtocolVersion = data + if hostInMessage == self.HOST: + shared.printLock.acquire() + print 'setting the remote node\'s protocol version in the sendData thread (ID:',id(self), ') to', specifiedRemoteProtocolVersion + shared.printLock.release() + self.remoteProtocolVersion = specifiedRemoteProtocolVersion + elif command == 'sendaddr': + if self.remoteProtocolVersion == 1: + shared.printLock.acquire() + print 'a sendData thread is not sending an addr message to this particular peer ('+self.HOST+') because their protocol version is 1.' + shared.printLock.release() + else: + try: + #To prevent some network analysis, 'leak' the data out to our peer after waiting a random amount of time unless we have a long list of messages in our queue to send. + random.seed() + time.sleep(random.randrange(0, 10)) + self.sock.sendall(data) + self.lastTimeISentData = int(time.time()) + except: + print 'self.sock.sendall failed' + try: + self.sock.shutdown(socket.SHUT_RDWR) + self.sock.close() + except: + pass + shared.sendDataQueues.remove(self.mailbox) + print 'sendDataThread thread (ID:',str(id(self))+') ending now. Was connected to', self.HOST + break + elif command == 'sendinv': + if data not in self.objectsOfWhichThisRemoteNodeIsAlreadyAware: + payload = '\x01' + data + headerData = '\xe9\xbe\xb4\xd9' #magic bits, slighly different from Bitcoin's magic bits. + headerData += 'inv\x00\x00\x00\x00\x00\x00\x00\x00\x00' + headerData += pack('>L',len(payload)) + headerData += hashlib.sha512(payload).digest()[:4] + #To prevent some network analysis, 'leak' the data out to our peer after waiting a random amount of time + random.seed() + time.sleep(random.randrange(0, 10)) + try: + self.sock.sendall(headerData + payload) + self.lastTimeISentData = int(time.time()) + except: + print 'self.sock.sendall failed' + try: + self.sock.shutdown(socket.SHUT_RDWR) + self.sock.close() + except: + pass + shared.sendDataQueues.remove(self.mailbox) + print 'sendDataThread thread (ID:',str(id(self))+') ending now. Was connected to', self.HOST + break + elif command == 'pong': + if self.lastTimeISentData < (int(time.time()) - 298): + #Send out a pong message to keep the connection alive. + shared.printLock.acquire() + print 'Sending pong to', self.HOST, 'to keep connection alive.' + shared.printLock.release() + try: + self.sock.sendall('\xE9\xBE\xB4\xD9\x70\x6F\x6E\x67\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xcf\x83\xe1\x35') + self.lastTimeISentData = int(time.time()) + except: + print 'send pong failed' + try: + self.sock.shutdown(socket.SHUT_RDWR) + self.sock.close() + except: + pass + shared.sendDataQueues.remove(self.mailbox) + print 'sendDataThread thread', self, 'ending now. Was connected to', self.HOST + break + else: + shared.printLock.acquire() + print 'sendDataThread ID:',id(self),'ignoring command', command,'because the thread is not in stream',deststream + shared.printLock.release() + + + +def isInSqlInventory(hash): + t = (hash,) + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''select hash from inventory where hash=?''') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + if queryreturn == []: + return False + else: + return True + +def convertIntToString(n): + a = __builtins__.hex(n) + if a[-1:] == 'L': + a = a[:-1] + if (len(a) % 2) == 0: + return a[2:].decode('hex') + else: + return ('0'+a[2:]).decode('hex') + +def convertStringToInt(s): + return int(s.encode('hex'), 16) + + + +#This function expects that pubkey begin with \x04 +def calculateBitcoinAddressFromPubkey(pubkey): + if len(pubkey)!= 65: + 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') + sha.update(pubkey) + ripe.update(sha.digest()) + ripeWithProdnetPrefix = '\x00' + ripe.digest() + + checksum = hashlib.sha256(hashlib.sha256(ripeWithProdnetPrefix).digest()).digest()[:4] + binaryBitcoinAddress = ripeWithProdnetPrefix + checksum + numberOfZeroBytesOnBinaryBitcoinAddress = 0 + while binaryBitcoinAddress[0] == '\x00': + numberOfZeroBytesOnBinaryBitcoinAddress += 1 + binaryBitcoinAddress = binaryBitcoinAddress[1:] + base58encoded = arithmetic.changebase(binaryBitcoinAddress,256,58) + return "1"*numberOfZeroBytesOnBinaryBitcoinAddress + base58encoded + +def calculateTestnetAddressFromPubkey(pubkey): + if len(pubkey)!= 65: + 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') + sha.update(pubkey) + ripe.update(sha.digest()) + ripeWithProdnetPrefix = '\x6F' + ripe.digest() + + checksum = hashlib.sha256(hashlib.sha256(ripeWithProdnetPrefix).digest()).digest()[:4] + binaryBitcoinAddress = ripeWithProdnetPrefix + checksum + numberOfZeroBytesOnBinaryBitcoinAddress = 0 + while binaryBitcoinAddress[0] == '\x00': + numberOfZeroBytesOnBinaryBitcoinAddress += 1 + binaryBitcoinAddress = binaryBitcoinAddress[1:] + base58encoded = arithmetic.changebase(binaryBitcoinAddress,256,58) + return "1"*numberOfZeroBytesOnBinaryBitcoinAddress + base58encoded + + + +def signal_handler(signal, frame): + if shared.safeConfigGetBoolean('bitmessagesettings','daemon'): + shared.doCleanShutdown() + sys.exit(0) + else: + print 'Unfortunately you cannot use Ctrl+C when running the UI because the UI captures the signal.' + + + +def connectToStream(streamNumber): + selfInitiatedConnections[streamNumber] = {} + if sys.platform[0:3] == 'win': + maximumNumberOfHalfOpenConnections = 9 + else: + maximumNumberOfHalfOpenConnections = 32 + for i in range(maximumNumberOfHalfOpenConnections): + a = outgoingSynSender() + a.setup(streamNumber) + a.start() + +#Does an EC point multiplication; turns a private key into a public key. +def pointMult(secret): + #ctx = OpenSSL.BN_CTX_new() #This value proved to cause Seg Faults on Linux. It turns out that it really didn't speed up EC_POINT_mul anyway. + k = OpenSSL.EC_KEY_new_by_curve_name(OpenSSL.get_curve('secp256k1')) + priv_key = OpenSSL.BN_bin2bn(secret, 32, 0) + 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) + #print 'priv_key',priv_key + #print 'pub_key',pub_key + + size = OpenSSL.i2o_ECPublicKey(k, 0) + mb = ctypes.create_string_buffer(size) + OpenSSL.i2o_ECPublicKey(k, ctypes.byref(ctypes.pointer(mb))) + #print 'mb.raw', mb.raw.encode('hex'), 'length:', len(mb.raw) + #print 'mb.raw', mb.raw, 'length:', len(mb.raw) + + OpenSSL.EC_POINT_free(pub_key) + #OpenSSL.BN_CTX_free(ctx) + OpenSSL.BN_free(priv_key) + OpenSSL.EC_KEY_free(k) + return mb.raw + + + +def assembleVersionMessage(remoteHost,remotePort,myStreamNumber): + shared.softwareVersion + payload = '' + payload += pack('>L',2) #protocol version. + payload += pack('>q',1) #bitflags of the services I offer. + payload += pack('>q',int(time.time())) + + payload += pack('>q',1) #boolservices of remote connection. How can I even know this for sure? This is probably ignored by the remote host. + payload += '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF' + socket.inet_aton(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. + payload += pack('>H',shared.config.getint('bitmessagesettings', 'port'))#my external IPv6 and port + + random.seed() + payload += eightBytesOfRandomDataUsedToDetectConnectionsToSelf + userAgent = '/PyBitmessage:' + shared.softwareVersion + '/' #Length of userAgent must be less than 253. + payload += pack('>B',len(userAgent)) #user agent string length. If the user agent is more than 252 bytes long, this code isn't going to work. + payload += userAgent + payload += encodeVarint(1) #The number of streams about which I care. PyBitmessage currently only supports 1 per connection. + payload += encodeVarint(myStreamNumber) + + datatosend = '\xe9\xbe\xb4\xd9' #magic bits, slighly different from Bitcoin's magic bits. + datatosend = datatosend + 'version\x00\x00\x00\x00\x00' #version command + datatosend = datatosend + pack('>L',len(payload)) #payload length + datatosend = datatosend + hashlib.sha512(payload).digest()[0:4] + return datatosend + payload + +def isHostInPrivateIPRange(host): + if host[:3] == '10.': + return True + if host[:4] == '172.': + if host[6] == '.': + if int(host[4:6]) >= 16 and int(host[4:6]) <= 31: + return True + if host[:8] == '192.168.': + return True + return False + +#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): + def __init__(self): + threading.Thread.__init__(self) + + def run(self): + self.conn = sqlite3.connect(shared.appdata + 'messages.dat' ) + self.conn.text_factory = str + self.cur = self.conn.cursor() + 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, 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, lastactiontime integer, status text, pubkeyretrynumber integer, msgretrynumber integer, folder text, encodingtype int)''' ) + self.cur.execute( '''CREATE TABLE subscriptions (label text, address text, enabled bool)''' ) + self.cur.execute( '''CREATE TABLE addressbook (label text, address text)''' ) + self.cur.execute( '''CREATE TABLE blacklist (label text, address text, enabled bool)''' ) + self.cur.execute( '''CREATE TABLE whitelist (label text, address text, enabled bool)''' ) + #Explanation of what is in the pubkeys table: + # The hash is the RIPEMD160 hash that is encoded in the Bitmessage address. + # transmitdata is literally the data that was included in the Bitmessage pubkey message when it arrived, except for the 24 byte protocol header- ie, it starts with the POW nonce. + # time is the time that the pubkey was broadcast on the network same as with every other type of Bitmessage object. + # usedpersonally is set to "yes" if we have used the key personally. This keeps us from deleting it because we may want to reply to a message in the future. This field is not a bool because we may need more flexability in the future and it doesn't take up much more space anyway. + self.cur.execute( '''CREATE TABLE pubkeys (hash blob, transmitdata blob, time int, usedpersonally text, UNIQUE(hash) ON CONFLICT REPLACE)''' ) + self.cur.execute( '''CREATE TABLE inventory (hash blob, objecttype text, streamnumber int, payload blob, receivedtime integer, UNIQUE(hash) ON CONFLICT REPLACE)''' ) + self.cur.execute( '''CREATE TABLE knownnodes (timelastseen int, stream int, services blob, host blob, port blob, UNIQUE(host, stream, port) ON CONFLICT REPLACE)''' ) #This table isn't used in the program yet but I have a feeling that we'll need it. + self.cur.execute( '''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','1')''') + self.cur.execute( '''INSERT INTO settings VALUES('lastvacuumtime',?)''',(int(time.time()),)) + self.conn.commit() + print 'Created messages database file' + except Exception, err: + if str(err) == 'table inbox already exists': + shared.printLock.acquire() + print 'Database file already exists.' + shared.printLock.release() + else: + sys.stderr.write('ERROR trying to create database file (message.dat). Error message: %s\n' % str(err)) + os._exit(0) + + #People running earlier versions of PyBitmessage do not have the usedpersonally field in their pubkeys table. Let's add it. + 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() + + shared.config.set('bitmessagesettings','settingsversion','3') + with open(shared.appdata + 'keys.dat', 'wb') as configfile: + shared.config.write(configfile) + + #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 shared.config.getint('bitmessagesettings','settingsversion') == 3: + item = '''ALTER TABLE inbox ADD encodingtype int DEFAULT '2' ''' + parameters = '' + self.cur.execute(item, parameters) + + item = '''ALTER TABLE inbox ADD read bool DEFAULT '1' ''' + parameters = '' + self.cur.execute(item, parameters) + + item = '''ALTER TABLE sent ADD encodingtype int DEFAULT '2' ''' + parameters = '' + self.cur.execute(item, parameters) + self.conn.commit() + + shared.config.set('bitmessagesettings','settingsversion','4') + with open(shared.appdata + 'keys.dat', 'wb') as configfile: + shared.config.write(configfile) + + 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') + with open(shared.appdata + 'keys.dat', 'wb') as configfile: + shared.config.write(configfile) + + #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 version we are on can stay embedded in the messages.dat file. Let us check to see if the settings table exists yet. + item = '''SELECT name FROM sqlite_master WHERE type='table' AND name='settings';''' + parameters = '' + self.cur.execute(item, parameters) + if self.cur.fetchall() == []: + #The settings table doesn't exist. We need to make it. + print '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',?)''',(int(time.time()),)) + print '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);''') + self.cur.execute( '''INSERT INTO pubkeys_backup SELECT hash, transmitdata, time, usedpersonally FROM 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)''' ) + self.cur.execute( '''INSERT INTO pubkeys SELECT hash, transmitdata, time, usedpersonally FROM pubkeys_backup;''') + self.cur.execute( '''DROP TABLE pubkeys_backup;''') + print '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';''') + print '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)''') + print 'Commiting.' + self.conn.commit() + print 'Vacuuming message.dat. You might notice that the file size gets much smaller.' + self.cur.execute( ''' VACUUM ''') + + try: + testpayload = '\x00\x00' + t = ('1234',testpayload,'12345678','no') + self.cur.execute( '''INSERT INTO pubkeys VALUES(?,?,?,?)''',t) + self.conn.commit() + self.cur.execute('''SELECT transmitdata FROM pubkeys WHERE hash='1234' ''') + queryreturn = self.cur.fetchall() + for row in queryreturn: + transmitdata, = row + self.cur.execute('''DELETE FROM pubkeys WHERE hash='1234' ''') + self.conn.commit() + if transmitdata == '': + sys.stderr.write('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') + sys.stderr.write('PyBitmessage will now exist 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, err: + print err + + #Let us check to see the last time we vaccumed the messages.dat file. If it has been more than a month let's do it now. + item = '''SELECT value FROM settings WHERE key='lastvacuumtime';''' + parameters = '' + self.cur.execute(item, parameters) + queryreturn = self.cur.fetchall() + for row in queryreturn: + value, = row + if int(value) < int(time.time()) - 2592000: + print 'It has been a long time since the messages.dat file has been vacuumed. Vacuuming now...' + self.cur.execute( ''' VACUUM ''') + item = '''update settings set value=? WHERE key='lastvacuumtime';''' + parameters = (int(time.time()),) + self.cur.execute(item, parameters) + + while True: + item = shared.sqlSubmitQueue.get() + if item == 'commit': + self.conn.commit() + elif item == 'exit': + self.conn.close() + print 'sqlThread exiting gracefully.' + return + elif item == 'movemessagstoprog': + shared.printLock.acquire() + print 'the sqlThread is moving the messages.dat file to the local program directory.' + shared.printLock.release() + self.conn.commit() + self.conn.close() + shutil.move(shared.lookupAppdataFolder()+'messages.dat','messages.dat') + self.conn = sqlite3.connect('messages.dat' ) + self.conn.text_factory = str + self.cur = self.conn.cursor() + elif item == 'movemessagstoappdata': + shared.printLock.acquire() + print 'the sqlThread is moving the messages.dat file to the Appdata folder.' + shared.printLock.release() + self.conn.commit() + self.conn.close() + shutil.move('messages.dat',shared.lookupAppdataFolder()+'messages.dat') + self.conn = sqlite3.connect(shared.appdata + 'messages.dat' ) + self.conn.text_factory = str + self.cur = self.conn.cursor() + else: + parameters = shared.sqlSubmitQueue.get() + #print 'item', item + #print 'parameters', parameters + try: + self.cur.execute(item, parameters) + except Exception, err: + shared.printLock.acquire() + sys.stderr.write('\nMajor 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: "'+str(item) + '" Here are the parameters; you might want to censor this data with asterisks (***) as it can contain private information: '+str(repr(parameters))+'\nHere is the actual error message thrown by the sqlThread: '+ str(err)+'\n') + sys.stderr.write('This program shall now abruptly exit!\n') + shared.printLock.release() + os._exit(0) + + shared.sqlReturnQueue.put(self.cur.fetchall()) + #shared.sqlSubmitQueue.task_done() + + + +'''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) + +It cleans these tables on the disk: + inventory (clears data more than 2 days and 12 hours old) + pubkeys (clears pubkeys older than 4 weeks old which we have not used personally) + +It resends messages when there has been no response: + resends getpubkey messages in 4 days (then 8 days, then 16 days, etc...) + resends msg messages in 4 days (then 8 days, then 16 days, etc...) + +''' +class singleCleaner(threading.Thread): + def __init__(self): + threading.Thread.__init__(self) + + def run(self): + timeWeLastClearedInventoryAndPubkeysTables = 0 + + while True: + shared.sqlLock.acquire() + #self.emit(SIGNAL("updateStatusBar(PyQt_PyObject)"),"Doing housekeeping (Flushing inventory in memory to disk...)") + shared.UISignalQueue.put(('updateStatusBar','Doing housekeeping (Flushing inventory in memory to disk...)')) + for hash, storedValue in shared.inventory.items(): + objectType, streamNumber, payload, receivedTime = storedValue + if int(time.time())- 3600 > receivedTime: + t = (hash,objectType,streamNumber,payload,receivedTime) + shared.sqlSubmitQueue.put('''INSERT INTO inventory VALUES (?,?,?,?,?)''') + shared.sqlSubmitQueue.put(t) + shared.sqlReturnQueue.get() + del shared.inventory[hash] + shared.sqlSubmitQueue.put('commit') + #self.emit(SIGNAL("updateStatusBar(PyQt_PyObject)"),"") + shared.UISignalQueue.put(('updateStatusBar','')) + shared.sqlLock.release() + 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. + if shared.safeConfigGetBoolean('bitmessagesettings','daemon'): + shared.UISignalQueue.queue.clear() + if timeWeLastClearedInventoryAndPubkeysTables < int(time.time()) - 7380: + timeWeLastClearedInventoryAndPubkeysTables = int(time.time()) + #inventory (moves data from the inventory data structure to the on-disk sql database) + shared.sqlLock.acquire() + #inventory (clears data more than 2 days and 12 hours old) + t = (int(time.time())-lengthOfTimeToLeaveObjectsInInventory,int(time.time())-lengthOfTimeToHoldOnToAllPubkeys) + shared.sqlSubmitQueue.put('''DELETE FROM inventory WHERE (receivedtime'pubkey') OR (receivedtime (maximumAgeOfAnObjectThatIAmWillingToAccept * (2 ** (pubkeyretrynumber))): + print 'It has been a long time and we haven\'t heard a response to our getpubkey request. Sending again.' + try: + del neededPubkeys[toripe] #We need to take this entry out of the 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 + shared.workerQueue.put(('sendmessage',toaddress)) + #self.emit(SIGNAL("updateStatusBar(PyQt_PyObject)"),"Doing work necessary to again attempt to request a public key...") + shared.UISignalQueue.put(('updateStatusBar','Doing work necessary to again attempt to request a public key...')) + t = (int(time.time()),pubkeyretrynumber+1,toripe) + shared.sqlSubmitQueue.put('''UPDATE sent SET lastactiontime=?, pubkeyretrynumber=? WHERE toripe=?''') + shared.sqlSubmitQueue.put(t) + shared.sqlReturnQueue.get() + else:# status == sentmessage + if int(time.time()) - lastactiontime > (maximumAgeOfAnObjectThatIAmWillingToAccept * (2 ** (msgretrynumber))): + print 'It has been a long time and we haven\'t heard an acknowledgement to our msg. Sending again.' + t = (int(time.time()),msgretrynumber+1,'findingpubkey',ackdata) + shared.sqlSubmitQueue.put('''UPDATE sent SET lastactiontime=?, msgretrynumber=?, status=? WHERE ackdata=?''') + shared.sqlSubmitQueue.put(t) + shared.sqlReturnQueue.get() + shared.workerQueue.put(('sendmessage',toaddress)) + #self.emit(SIGNAL("updateStatusBar(PyQt_PyObject)"),"Doing work necessary to again attempt to deliver a message...") + shared.UISignalQueue.put(('updateStatusBar','Doing work necessary to again attempt to deliver a message...')) + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() + time.sleep(300) + +#This thread, of which there is only one, does the heavy lifting: calculating POWs. +class singleWorker(threading.Thread): + def __init__(self): + #QThread.__init__(self, parent) + threading.Thread.__init__(self) + + def run(self): + time.sleep(10) + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''SELECT toripe FROM sent WHERE (status=? AND folder='sent')''') + shared.sqlSubmitQueue.put(('findingpubkey',)) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + for row in queryreturn: + toripe, = row + #It is possible for the status of a message in our sent folder (which is also our 'outbox' folder) to have a status of 'findingpubkey' even if we have the pubkey. This can + #happen if the worker thread is working on the POW for an earlier message and does not get to the message in question before the user closes Bitmessage. In this case, the + #status will still be 'findingpubkey' but Bitmessage will never have checked to see whether it actually already has the pubkey. We should therefore check here. + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''SELECT hash FROM pubkeys WHERE hash=? ''') + shared.sqlSubmitQueue.put((toripe,)) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + if queryreturn != []: #If we have the pubkey then send the message otherwise put the hash in the neededPubkeys data structure so that we will pay attention to it if it comes over the wire. + self.sendMsg(toripe) + else: + neededPubkeys[toripe] = 0 + + self.sendBroadcast() #just in case there are any proof of work tasks for Broadcasts that have yet to be sent. + + #Now let us see if there are any proofs of work for msg messages that we have yet to complete.. + shared.sqlLock.acquire() + t = ('doingpow',) + shared.sqlSubmitQueue.put('''SELECT toripe FROM sent WHERE status=? and folder='sent' ''') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + for row in queryreturn: + toripe, = row + #Evidentially there is a remote possibility that we may, for some reason, no longer have the recipient's pubkey. Let us make sure we still have it or else the sendMsg function will appear to freeze. + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''SELECT hash FROM pubkeys WHERE hash=? ''') + shared.sqlSubmitQueue.put((toripe,)) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + if queryreturn != []: + #We have the needed pubkey + self.sendMsg(toripe) + else: + shared.printLock.acquire() + sys.stderr.write('For some reason, the status of a message in our outbox is \'doingpow\' even though we lack the pubkey. Here is the RIPE hash of the needed pubkey: %s\n' % toripe.encode('hex')) + shared.printLock.release() + + while True: + command, data = shared.workerQueue.get() + #statusbar = 'The singleWorker thread is working on work.' + #self.emit(SIGNAL("updateStatusBar(PyQt_PyObject)"),statusbar) + if command == 'sendmessage': + toAddress = data + toStatus,toAddressVersionNumber,toStreamNumber,toRipe = decodeAddress(toAddress) + #print 'message type', type(message) + #print repr(message.toUtf8()) + #print str(message.toUtf8()) + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('SELECT hash FROM pubkeys WHERE hash=?') + shared.sqlSubmitQueue.put((toRipe,)) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + #print 'queryreturn', queryreturn + if queryreturn == []: + #We'll need to request the pub key because we don't have it. + if not toRipe in neededPubkeys: + neededPubkeys[toRipe] = 0 + print 'requesting pubkey:', toRipe.encode('hex') + self.requestPubKey(toAddressVersionNumber,toStreamNumber,toRipe) + else: + print 'We have already requested this pubkey (the ripe hash is in neededPubkeys). We will re-request again soon.' + #self.emit(SIGNAL("updateSentItemStatusByHash(PyQt_PyObject,PyQt_PyObject)"),toRipe,'Public key was requested earlier. Receiver must be offline. Will retry.') + shared.UISignalQueue.put(('updateSentItemStatusByHash',(toRipe,'Public key was requested earlier. Receiver must be offline. Will retry.'))) + + else: + print 'We already have the necessary public key.' + self.sendMsg(toRipe) #by calling this function, we are asserting that we already have the pubkey for toRipe + elif command == 'sendbroadcast': + print 'Within WorkerThread, processing sendbroadcast command.' + fromAddress,subject,message = data + self.sendBroadcast() + elif command == 'doPOWForMyV2Pubkey': + self.doPOWForMyV2Pubkey(data) + elif command == 'doPOWForMyV3Pubkey': + self.doPOWForMyV3Pubkey(data) + elif command == 'newpubkey': + toAddressVersion,toStreamNumber,toRipe = data + if toRipe in neededPubkeys: + print 'We have been awaiting the arrival of this pubkey.' + del neededPubkeys[toRipe] + self.sendMsg(toRipe) + else: + shared.printLock.acquire() + print 'We don\'t need this pub key. We didn\'t ask for it. Pubkey hash:', toRipe.encode('hex') + shared.printLock.release() + else: + shared.printLock.acquire() + sys.stderr.write('Probable programming error: The command sent to the workerThread is weird. It is: %s\n' % command) + shared.printLock.release() + + shared.workerQueue.task_done() + + def doPOWForMyV2Pubkey(self,hash): #This function also broadcasts out the pubkey message once it is done with the POW + #Look up my stream number based on my address hash + """configSections = shared.config.sections() + 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) + embeddedTime = int(time.time()+random.randrange(-300, 300)) #the current time plus or minus five minutes + payload = pack('>I',(embeddedTime)) + payload += encodeVarint(addressVersionNumber) #Address version number + payload += encodeVarint(streamNumber) + payload += '\x00\x00\x00\x01' #bitfield of features supported by me (see the wiki). + + try: + privSigningKeyBase58 = shared.config.get(myAddress, 'privsigningkey') + privEncryptionKeyBase58 = shared.config.get(myAddress, 'privencryptionkey') + except Exception, err: + shared.printLock.acquire() + sys.stderr.write('Error within doPOWForMyV2Pubkey. Could not read the keys from the keys.dat file for a requested address. %s\n' % err) + shared.printLock.release() + return + + privSigningKeyHex = shared.decodeWalletImportFormat(privSigningKeyBase58).encode('hex') + privEncryptionKeyHex = shared.decodeWalletImportFormat(privEncryptionKeyBase58).encode('hex') + pubSigningKey = highlevelcrypto.privToPub(privSigningKeyHex).decode('hex') + pubEncryptionKey = highlevelcrypto.privToPub(privEncryptionKeyHex).decode('hex') + + payload += pubSigningKey[1:] + payload += pubEncryptionKey[1:] + + #Do the POW for this pubkey message + target = 2**64 / ((len(payload)+shared.networkDefaultPayloadLengthExtraBytes+8) * shared.networkDefaultProofOfWorkNonceTrialsPerByte) + print '(For pubkey message) Doing proof of work...' + initialHash = hashlib.sha512(payload).digest() + trialValue, nonce = proofofwork.run(target, initialHash) + print '(For pubkey message) Found proof of work', trialValue, 'Nonce:', nonce + payload = pack('>Q',nonce) + payload + """t = (hash,payload,embeddedTime,'no') + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''INSERT INTO pubkeys VALUES (?,?,?,?)''') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release()""" + + inventoryHash = calculateInventoryHash(payload) + objectType = 'pubkey' + shared.inventory[inventoryHash] = (objectType, streamNumber, payload, embeddedTime) + + shared.printLock.acquire() + print 'broadcasting inv with hash:', inventoryHash.encode('hex') + shared.printLock.release() + shared.broadcastToSendDataQueues((streamNumber, 'sendinv', inventoryHash)) + #self.emit(SIGNAL("updateStatusBar(PyQt_PyObject)"),"") + shared.UISignalQueue.put(('updateStatusBar','')) + shared.config.set(myAddress,'lastpubkeysendtime',str(int(time.time()))) + with open(shared.appdata + 'keys.dat', 'wb') as configfile: + shared.config.write(configfile) + + def doPOWForMyV3Pubkey(self,hash): #This function also broadcasts out the pubkey message once it is done with the POW + myAddress = shared.myAddressesByHash[hash] + status,addressVersionNumber,streamNumber,hash = decodeAddress(myAddress) + embeddedTime = int(time.time()+random.randrange(-300, 300)) #the current time plus or minus five minutes + payload = pack('>I',(embeddedTime)) + payload += encodeVarint(addressVersionNumber) #Address version number + payload += encodeVarint(streamNumber) + payload += '\x00\x00\x00\x01' #bitfield of features supported by me (see the wiki). + + try: + privSigningKeyBase58 = shared.config.get(myAddress, 'privsigningkey') + privEncryptionKeyBase58 = shared.config.get(myAddress, 'privencryptionkey') + except Exception, err: + shared.printLock.acquire() + sys.stderr.write('Error within doPOWForMyV3Pubkey. Could not read the keys from the keys.dat file for a requested address. %s\n' % err) + shared.printLock.release() + return + + privSigningKeyHex = shared.decodeWalletImportFormat(privSigningKeyBase58).encode('hex') + privEncryptionKeyHex = shared.decodeWalletImportFormat(privEncryptionKeyBase58).encode('hex') + pubSigningKey = highlevelcrypto.privToPub(privSigningKeyHex).decode('hex') + pubEncryptionKey = highlevelcrypto.privToPub(privEncryptionKeyHex).decode('hex') + + payload += pubSigningKey[1:] + payload += pubEncryptionKey[1:] + + payload += encodeVarint(shared.config.getint(myAddress,'noncetrialsperbyte')) + payload += encodeVarint(shared.config.getint(myAddress,'payloadlengthextrabytes')) + signature = highlevelcrypto.sign(payload,privSigningKeyHex) + payload += encodeVarint(len(signature)) + payload += signature + + #Do the POW for this pubkey message + target = 2**64 / ((len(payload)+shared.networkDefaultPayloadLengthExtraBytes+8) * shared.networkDefaultProofOfWorkNonceTrialsPerByte) + print '(For pubkey message) Doing proof of work...' + initialHash = hashlib.sha512(payload).digest() + trialValue, nonce = proofofwork.run(target, initialHash) + print '(For pubkey message) Found proof of work', trialValue, 'Nonce:', nonce + + payload = pack('>Q',nonce) + payload + """t = (hash,payload,embeddedTime,'no') + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''INSERT INTO pubkeys VALUES (?,?,?,?)''') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release()""" + + inventoryHash = calculateInventoryHash(payload) + objectType = 'pubkey' + shared.inventory[inventoryHash] = (objectType, streamNumber, payload, embeddedTime) + + shared.printLock.acquire() + print 'broadcasting inv with hash:', inventoryHash.encode('hex') + shared.printLock.release() + shared.broadcastToSendDataQueues((streamNumber, 'sendinv', inventoryHash)) + #self.emit(SIGNAL("updateStatusBar(PyQt_PyObject)"),"") + shared.UISignalQueue.put(('updateStatusBar','')) + shared.config.set(myAddress,'lastpubkeysendtime',str(int(time.time()))) + with open(shared.appdata + 'keys.dat', 'wb') as configfile: + shared.config.write(configfile) + + def sendBroadcast(self): + shared.sqlLock.acquire() + t = ('broadcastpending',) + shared.sqlSubmitQueue.put('''SELECT fromaddress, subject, message, ackdata FROM sent WHERE status=? and folder='sent' ''') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + for row in queryreturn: + fromaddress, subject, body, ackdata = row + status,addressVersionNumber,streamNumber,ripe = decodeAddress(fromaddress) + if addressVersionNumber == 2 and int(time.time()) < encryptedBroadcastSwitchoverTime: + #We need to convert our private keys to public keys in order to include them. + try: + privSigningKeyBase58 = shared.config.get(fromaddress, 'privsigningkey') + privEncryptionKeyBase58 = shared.config.get(fromaddress, 'privencryptionkey') + except: + #self.emit(SIGNAL("updateSentItemStatusByAckdata(PyQt_PyObject,PyQt_PyObject)"),ackdata,'Error! Could not find sender address (your address) in the keys.dat file.') + shared.UISignalQueue.put(('updateSentItemStatusByAckdata',(ackdata,'Error! Could not find sender address (your address) in the keys.dat file.'))) + continue + + privSigningKeyHex = shared.decodeWalletImportFormat(privSigningKeyBase58).encode('hex') + privEncryptionKeyHex = shared.decodeWalletImportFormat(privEncryptionKeyBase58).encode('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 = highlevelcrypto.privToPub(privEncryptionKeyHex).decode('hex') + + payload = pack('>I',(int(time.time())+random.randrange(-300, 300)))#the current time plus or minus five minutes + payload += encodeVarint(1) #broadcast version + payload += encodeVarint(addressVersionNumber) + payload += encodeVarint(streamNumber) + payload += '\x00\x00\x00\x01' #behavior bitfield + payload += pubSigningKey[1:] + payload += pubEncryptionKey[1:] + payload += ripe + payload += '\x02' #message encoding type + payload += encodeVarint(len('Subject:' + subject + '\n' + 'Body:' + body)) #Type 2 is simple UTF-8 message encoding. + payload += 'Subject:' + subject + '\n' + 'Body:' + body + + signature = highlevelcrypto.sign(payload,privSigningKeyHex) + payload += encodeVarint(len(signature)) + payload += signature + + target = 2**64 / ((len(payload)+shared.networkDefaultPayloadLengthExtraBytes+8) * shared.networkDefaultProofOfWorkNonceTrialsPerByte) + print '(For broadcast message) Doing proof of work...' + #self.emit(SIGNAL("updateSentItemStatusByAckdata(PyQt_PyObject,PyQt_PyObject)"),ackdata,'Doing work necessary to send broadcast...') + shared.UISignalQueue.put(('updateSentItemStatusByAckdata',(ackdata,'Doing work necessary to send broadcast...'))) + initialHash = hashlib.sha512(payload).digest() + trialValue, nonce = proofofwork.run(target, initialHash) + print '(For broadcast message) Found proof of work', trialValue, 'Nonce:', nonce + + payload = pack('>Q',nonce) + payload + + inventoryHash = calculateInventoryHash(payload) + objectType = 'broadcast' + shared.inventory[inventoryHash] = (objectType, streamNumber, payload, int(time.time())) + print 'sending inv (within sendBroadcast function)' + shared.broadcastToSendDataQueues((streamNumber, 'sendinv', inventoryHash)) + + #self.emit(SIGNAL("updateSentItemStatusByAckdata(PyQt_PyObject,PyQt_PyObject)"),ackdata,'Broadcast sent on '+unicode(strftime(shared.config.get('bitmessagesettings', 'timeformat'),localtime(int(time.time()))),'utf-8')) + shared.UISignalQueue.put(('updateSentItemStatusByAckdata',(ackdata,'Broadcast sent on '+unicode(strftime(shared.config.get('bitmessagesettings', 'timeformat'),localtime(int(time.time()))),'utf-8')))) + + #Update the status of the message in the 'sent' table to have a 'broadcastsent' status + shared.sqlLock.acquire() + t = ('broadcastsent',int(time.time()),fromaddress, subject, body,'broadcastpending') + shared.sqlSubmitQueue.put('UPDATE sent SET status=?, lastactiontime=? WHERE fromaddress=? AND subject=? AND message=? AND status=?') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() + elif addressVersionNumber == 3 or int(time.time()) > encryptedBroadcastSwitchoverTime: + #We need to convert our private keys to public keys in order to include them. + try: + privSigningKeyBase58 = shared.config.get(fromaddress, 'privsigningkey') + privEncryptionKeyBase58 = shared.config.get(fromaddress, 'privencryptionkey') + except: + #self.emit(SIGNAL("updateSentItemStatusByAckdata(PyQt_PyObject,PyQt_PyObject)"),ackdata,'Error! Could not find sender address (your address) in the keys.dat file.') + shared.UISignalQueue.put(('updateSentItemStatusByAckdata',(ackdata,'Error! Could not find sender address (your address) in the keys.dat file.'))) + continue + + privSigningKeyHex = shared.decodeWalletImportFormat(privSigningKeyBase58).encode('hex') + privEncryptionKeyHex = shared.decodeWalletImportFormat(privEncryptionKeyBase58).encode('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 = highlevelcrypto.privToPub(privEncryptionKeyHex).decode('hex') + + payload = pack('>I',(int(time.time())+random.randrange(-300, 300)))#the current time plus or minus five minutes + payload += encodeVarint(2) #broadcast version + payload += encodeVarint(streamNumber) + + dataToEncrypt = encodeVarint(2) #broadcast version + dataToEncrypt += encodeVarint(addressVersionNumber) + dataToEncrypt += encodeVarint(streamNumber) + dataToEncrypt += '\x00\x00\x00\x01' #behavior bitfield + dataToEncrypt += pubSigningKey[1:] + dataToEncrypt += pubEncryptionKey[1:] + if addressVersionNumber >= 3: + 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. + dataToEncrypt += 'Subject:' + subject + '\n' + 'Body:' + body + + signature = highlevelcrypto.sign(payload,privSigningKeyHex) + dataToEncrypt += encodeVarint(len(signature)) + dataToEncrypt += signature + privEncryptionKey = hashlib.sha512(encodeVarint(addressVersionNumber)+encodeVarint(streamNumber)+ripe).digest()[:32] + pubEncryptionKey = pointMult(privEncryptionKey) + payload += highlevelcrypto.encrypt(dataToEncrypt,pubEncryptionKey.encode('hex')) + + target = 2**64 / ((len(payload)+shared.networkDefaultPayloadLengthExtraBytes+8) * shared.networkDefaultProofOfWorkNonceTrialsPerByte) + print '(For broadcast message) Doing proof of work...' + #self.emit(SIGNAL("updateSentItemStatusByAckdata(PyQt_PyObject,PyQt_PyObject)"),ackdata,'Doing work necessary to send broadcast...') + shared.UISignalQueue.put(('updateSentItemStatusByAckdata',(ackdata,'Doing work necessary to send broadcast...'))) + initialHash = hashlib.sha512(payload).digest() + trialValue, nonce = proofofwork.run(target, initialHash) + print '(For broadcast message) Found proof of work', trialValue, 'Nonce:', nonce + + payload = pack('>Q',nonce) + payload + + inventoryHash = calculateInventoryHash(payload) + objectType = 'broadcast' + shared.inventory[inventoryHash] = (objectType, streamNumber, payload, int(time.time())) + print 'sending inv (within sendBroadcast function)' + shared.broadcastToSendDataQueues((streamNumber, 'sendinv', inventoryHash)) + + #self.emit(SIGNAL("updateSentItemStatusByAckdata(PyQt_PyObject,PyQt_PyObject)"),ackdata,'Broadcast sent on '+unicode(strftime(shared.config.get('bitmessagesettings', 'timeformat'),localtime(int(time.time()))),'utf-8')) + shared.UISignalQueue.put(('updateSentItemStatusByAckdata',(ackdata,'Broadcast sent on '+unicode(strftime(shared.config.get('bitmessagesettings', 'timeformat'),localtime(int(time.time()))),'utf-8')))) + + #Update the status of the message in the 'sent' table to have a 'broadcastsent' status + shared.sqlLock.acquire() + t = ('broadcastsent',int(time.time()),fromaddress, subject, body,'broadcastpending') + shared.sqlSubmitQueue.put('UPDATE sent SET status=?, lastactiontime=? WHERE fromaddress=? AND subject=? AND message=? AND status=?') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() + else: + shared.printLock.acquire() + sys.stderr.write('Error: In the singleWorker thread, the sendBroadcast function doesn\'t understand the address version.\n') + shared.printLock.release() + + def sendMsg(self,toRipe): + shared.sqlLock.acquire() + t = ('doingpow','findingpubkey',toRipe) + shared.sqlSubmitQueue.put('''UPDATE sent SET status=? WHERE status=? AND toripe=? and folder='sent' ''') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + + t = ('doingpow',toRipe) + shared.sqlSubmitQueue.put('''SELECT toaddress, fromaddress, subject, message, ackdata FROM sent WHERE status=? AND toripe=? and folder='sent' ''') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + for row in queryreturn: + toaddress, fromaddress, subject, message, ackdata = row + ackdataForWhichImWatching[ackdata] = 0 + toStatus,toAddressVersionNumber,toStreamNumber,toHash = decodeAddress(toaddress) + fromStatus,fromAddressVersionNumber,fromStreamNumber,fromHash = decodeAddress(fromaddress) + #self.emit(SIGNAL("updateSentItemStatusByAckdata(PyQt_PyObject,PyQt_PyObject)"),ackdata,'Doing work necessary to send the message.') + shared.UISignalQueue.put(('updateSentItemStatusByAckdata',(ackdata,'Doing work necessary to send the message.'))) + shared.printLock.acquire() + print 'Found a message in our database that needs to be sent with this pubkey.' + print 'First 150 characters of message:', message[:150] + shared.printLock.release() + embeddedTime = pack('>I',(int(time.time())+random.randrange(-300, 300)))#the current time plus or minus five minutes. We will use this time both for our message and for the ackdata packed within our message. + if fromAddressVersionNumber == 2: + payload = '\x01' #Message version. + payload += encodeVarint(fromAddressVersionNumber) + payload += encodeVarint(fromStreamNumber) + payload += '\x00\x00\x00\x01' #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: + privSigningKeyBase58 = shared.config.get(fromaddress, 'privsigningkey') + privEncryptionKeyBase58 = shared.config.get(fromaddress, 'privencryptionkey') + except: + #self.emit(SIGNAL("updateSentItemStatusByAckdata(PyQt_PyObject,PyQt_PyObject)"),ackdata,'Error! Could not find sender address (your address) in the keys.dat file.') + shared.UISignalQueue.put(('updateSentItemStatusByAckdata',(ackdata,'Error! Could not find sender address (your address) in the keys.dat file.'))) + continue + + privSigningKeyHex = shared.decodeWalletImportFormat(privSigningKeyBase58).encode('hex') + privEncryptionKeyHex = shared.decodeWalletImportFormat(privEncryptionKeyBase58).encode('hex') + + pubSigningKey = highlevelcrypto.privToPub(privSigningKeyHex).decode('hex') + pubEncryptionKey = highlevelcrypto.privToPub(privEncryptionKeyHex).decode('hex') + + 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:] + + payload += toHash #This hash will be checked by the receiver of the message to verify that toHash 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 + fullAckPayload = self.generateFullAckMessage(ackdata,toStreamNumber,embeddedTime)#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 + signature = highlevelcrypto.sign(payload,privSigningKeyHex) + payload += encodeVarint(len(signature)) + payload += signature + + if fromAddressVersionNumber == 3: + payload = '\x01' #Message version. + payload += encodeVarint(fromAddressVersionNumber) + payload += encodeVarint(fromStreamNumber) + payload += '\x00\x00\x00\x01' #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: + privSigningKeyBase58 = shared.config.get(fromaddress, 'privsigningkey') + privEncryptionKeyBase58 = shared.config.get(fromaddress, 'privencryptionkey') + except: + #self.emit(SIGNAL("updateSentItemStatusByAckdata(PyQt_PyObject,PyQt_PyObject)"),ackdata,'Error! Could not find sender address (your address) in the keys.dat file.') + shared.UISignalQueue.put(('updateSentItemStatusByAckdata',(ackdata,'Error! Could not find sender address (your address) in the keys.dat file.'))) + continue + + privSigningKeyHex = shared.decodeWalletImportFormat(privSigningKeyBase58).encode('hex') + privEncryptionKeyHex = shared.decodeWalletImportFormat(privEncryptionKeyBase58).encode('hex') + + pubSigningKey = highlevelcrypto.privToPub(privSigningKeyHex).decode('hex') + pubEncryptionKey = highlevelcrypto.privToPub(privEncryptionKeyHex).decode('hex') + + 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 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): + payload += encodeVarint(shared.networkDefaultProofOfWorkNonceTrialsPerByte) + payload += encodeVarint(shared.networkDefaultPayloadLengthExtraBytes) + else: + payload += encodeVarint(shared.config.getint(fromaddress,'noncetrialsperbyte')) + payload += encodeVarint(shared.config.getint(fromaddress,'payloadlengthextrabytes')) + + payload += toHash #This hash will be checked by the receiver of the message to verify that toHash 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 + fullAckPayload = self.generateFullAckMessage(ackdata,toStreamNumber,embeddedTime)#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 + signature = highlevelcrypto.sign(payload,privSigningKeyHex) + payload += encodeVarint(len(signature)) + payload += signature + + + #We have assembled the data that will be encrypted. Now let us fetch the recipient's public key out of our database and do the encryption. + + if toAddressVersionNumber == 2 or toAddressVersionNumber == 3: + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('SELECT transmitdata FROM pubkeys WHERE hash=?') + shared.sqlSubmitQueue.put((toRipe,)) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + if queryreturn == []: + shared.printLock.acquire() + sys.stderr.write('(within sendMsg) The needed pubkey was not found. This should never happen. Aborting send.\n') + shared.printLock.release() + return + for row in queryreturn: + pubkeyPayload, = row + + #The pubkey is stored the way we originally received it which means that we need to read beyond things like the nonce and time to get to the public keys. + readPosition = 8 #to bypass the nonce + readPosition += 4 #to bypass the embedded time + 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] + readPosition += 4 #to bypass the bitfield of behaviors + #pubSigningKeyBase256 = pubkeyPayload[readPosition:readPosition+64] #We don't use this key for anything here. + readPosition += 64 + pubEncryptionKeyBase256 = pubkeyPayload[readPosition:readPosition+64] + readPosition += 64 + if toAddressVersionNumber == 2: + requiredAverageProofOfWorkNonceTrialsPerByte = shared.networkDefaultProofOfWorkNonceTrialsPerByte + requiredPayloadLengthExtraBytes = shared.networkDefaultPayloadLengthExtraBytes + elif toAddressVersionNumber == 3: + requiredAverageProofOfWorkNonceTrialsPerByte, varintLength = decodeVarint(pubkeyPayload[readPosition:readPosition+10]) + readPosition += varintLength + requiredPayloadLengthExtraBytes, varintLength = decodeVarint(pubkeyPayload[readPosition:readPosition+10]) + readPosition += varintLength + 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 + encrypted = highlevelcrypto.encrypt(payload,"04"+pubEncryptionKeyBase256.encode('hex')) + + #We are now dropping the unencrypted data in payload since it has already been encrypted and replacing it with the encrypted payload that we will send out. + payload = embeddedTime + encodeVarint(toStreamNumber) + encrypted + target = 2**64 / ((len(payload)+requiredPayloadLengthExtraBytes+8) * requiredAverageProofOfWorkNonceTrialsPerByte) + shared.printLock.acquire() + print '(For msg message) Doing proof of work. Total required difficulty:', float(requiredAverageProofOfWorkNonceTrialsPerByte)/shared.networkDefaultProofOfWorkNonceTrialsPerByte,'Required small message difficulty:', float(requiredPayloadLengthExtraBytes)/shared.networkDefaultPayloadLengthExtraBytes + shared.printLock.release() + powStartTime = time.time() + initialHash = hashlib.sha512(payload).digest() + trialValue, nonce = proofofwork.run(target, initialHash) + print '(For msg message) 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 + payload = pack('>Q',nonce) + payload + + inventoryHash = calculateInventoryHash(payload) + objectType = 'msg' + shared.inventory[inventoryHash] = (objectType, toStreamNumber, payload, int(time.time())) + #self.emit(SIGNAL("updateSentItemStatusByAckdata(PyQt_PyObject,PyQt_PyObject)"),ackdata,'Message sent. Waiting on acknowledgement. Sent on ' + unicode(strftime(shared.config.get('bitmessagesettings', 'timeformat'),localtime(int(time.time()))),'utf-8')) + shared.UISignalQueue.put(('updateSentItemStatusByAckdata',(ackdata,'Message sent. Waiting on acknowledgement. Sent on ' + unicode(strftime(shared.config.get('bitmessagesettings', 'timeformat'),localtime(int(time.time()))),'utf-8')))) + print 'sending inv (within sendmsg function)' + shared.broadcastToSendDataQueues((streamNumber, 'sendinv', inventoryHash)) + + #Update the status of the message in the 'sent' table to have a 'sent' status + shared.sqlLock.acquire() + t = ('sentmessage',toaddress, fromaddress, subject, message,'doingpow') + shared.sqlSubmitQueue.put('UPDATE sent SET status=? WHERE toaddress=? AND fromaddress=? AND subject=? AND message=? AND status=?') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + + t = (toRipe,) + shared.sqlSubmitQueue.put('''UPDATE pubkeys SET usedpersonally='yes' WHERE hash=?''') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() + + + def requestPubKey(self,addressVersionNumber,streamNumber,ripe): + payload = pack('>I',(int(time.time())+random.randrange(-300, 300)))#the current time plus or minus five minutes. + payload += encodeVarint(addressVersionNumber) + payload += encodeVarint(streamNumber) + payload += ripe + shared.printLock.acquire() + print 'making request for pubkey with ripe:', ripe.encode('hex') + shared.printLock.release() + #print 'trial value', trialValue + statusbar = 'Doing the computations necessary to request the recipient\'s public key.' + #self.emit(SIGNAL("updateStatusBar(PyQt_PyObject)"),statusbar) + shared.UISignalQueue.put(('updateStatusBar',statusbar)) + #self.emit(SIGNAL("updateSentItemStatusByHash(PyQt_PyObject,PyQt_PyObject)"),ripe,'Doing work necessary to request public key.') + shared.UISignalQueue.put(('updateSentItemStatusByHash',(ripe,'Doing work necessary to request public key.'))) + print 'Doing proof-of-work necessary to send getpubkey message.' + target = 2**64 / ((len(payload)+shared.networkDefaultPayloadLengthExtraBytes+8) * shared.networkDefaultProofOfWorkNonceTrialsPerByte) + initialHash = hashlib.sha512(payload).digest() + trialValue, nonce = proofofwork.run(target, initialHash) + shared.printLock.acquire() + print 'Found proof of work', trialValue, 'Nonce:', nonce + shared.printLock.release() + + payload = pack('>Q',nonce) + payload + inventoryHash = calculateInventoryHash(payload) + objectType = 'getpubkey' + shared.inventory[inventoryHash] = (objectType, streamNumber, payload, int(time.time())) + print 'sending inv (for the getpubkey message)' + shared.broadcastToSendDataQueues((streamNumber, 'sendinv', inventoryHash)) + + #self.emit(SIGNAL("updateStatusBar(PyQt_PyObject)"),'Broacasting the public key request. This program will auto-retry if they are offline.') + shared.UISignalQueue.put(('updateStatusBar','Broacasting the public key request. This program will auto-retry if they are offline.')) + #self.emit(SIGNAL("updateSentItemStatusByHash(PyQt_PyObject,PyQt_PyObject)"),ripe,'Sending public key request. Waiting for reply. Requested at ' + unicode(strftime(shared.config.get('bitmessagesettings', 'timeformat'),localtime(int(time.time()))),'utf-8')) + shared.UISignalQueue.put(('updateSentItemStatusByHash',(ripe,'Sending public key request. Waiting for reply. Requested at ' + unicode(strftime(shared.config.get('bitmessagesettings', 'timeformat'),localtime(int(time.time()))),'utf-8')))) + + def generateFullAckMessage(self,ackdata,toStreamNumber,embeddedTime): + payload = embeddedTime + encodeVarint(toStreamNumber) + ackdata + target = 2**64 / ((len(payload)+shared.networkDefaultPayloadLengthExtraBytes+8) * shared.networkDefaultProofOfWorkNonceTrialsPerByte) + shared.printLock.acquire() + print '(For ack message) Doing proof of work...' + shared.printLock.release() + powStartTime = time.time() + initialHash = hashlib.sha512(payload).digest() + trialValue, nonce = proofofwork.run(target, initialHash) + shared.printLock.acquire() + print '(For ack message) 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 + shared.printLock.release() + payload = pack('>Q',nonce) + payload + headerData = '\xe9\xbe\xb4\xd9' #magic bits, slighly different from Bitcoin's magic bits. + headerData += 'msg\x00\x00\x00\x00\x00\x00\x00\x00\x00' + headerData += pack('>L',len(payload)) + headerData += hashlib.sha512(payload).digest()[:4] + return headerData + payload + +class addressGenerator(threading.Thread): + def __init__(self): + #QThread.__init__(self, parent) + threading.Thread.__init__(self) + + def run(self): + while True: + queueValue = shared.addressGeneratorQueue.get() + nonceTrialsPerByte = 0 + payloadLengthExtraBytes = 0 + if len(queueValue) == 7: + command,addressVersionNumber,streamNumber,label,numberOfAddressesToMake,deterministicPassphrase,eighteenByteRipe = queueValue + elif len(queueValue) == 9: + command,addressVersionNumber,streamNumber,label,numberOfAddressesToMake,deterministicPassphrase,eighteenByteRipe,nonceTrialsPerByte,payloadLengthExtraBytes = queueValue + else: + sys.stderr.write('Programming error: A structure with the wrong number of values was passed into the addressGeneratorQueue. Here is the queueValue: %s\n' % queueValue) + if addressVersionNumber < 3 or addressVersionNumber > 3: + 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 = shared.config.getint('bitmessagesettings','defaultnoncetrialsperbyte') + if nonceTrialsPerByte < shared.networkDefaultProofOfWorkNonceTrialsPerByte: + nonceTrialsPerByte = shared.networkDefaultProofOfWorkNonceTrialsPerByte + if payloadLengthExtraBytes == 0: + payloadLengthExtraBytes = shared.config.getint('bitmessagesettings','defaultpayloadlengthextrabytes') + if payloadLengthExtraBytes < shared.networkDefaultPayloadLengthExtraBytes: + payloadLengthExtraBytes = shared.networkDefaultPayloadLengthExtraBytes + if addressVersionNumber == 3: #currently the only one supported. + if command == 'createRandomAddress': + shared.UISignalQueue.put(('updateStatusBar','Generating one new address')) + #This next section is a little bit strange. We're going to generate keys over and over until we + #find one that starts with either \x00 or \x00\x00. Then when we pack them into a Bitmessage address, + #we won't store the \x00 or \x00\x00 bytes thus making the address shorter. + startTime = time.time() + numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix = 0 + potentialPrivSigningKey = OpenSSL.rand(32) + potentialPubSigningKey = pointMult(potentialPrivSigningKey) + while True: + numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix += 1 + potentialPrivEncryptionKey = OpenSSL.rand(32) + potentialPubEncryptionKey = pointMult(potentialPrivEncryptionKey) + #print 'potentialPubSigningKey', potentialPubSigningKey.encode('hex') + #print 'potentialPubEncryptionKey', potentialPubEncryptionKey.encode('hex') + ripe = hashlib.new('ripemd160') + sha = hashlib.new('sha512') + sha.update(potentialPubSigningKey+potentialPubEncryptionKey) + ripe.update(sha.digest()) + #print 'potential ripe.digest', ripe.digest().encode('hex') + if eighteenByteRipe: + if ripe.digest()[:2] == '\x00\x00': + break + else: + if ripe.digest()[:1] == '\x00': + break + print 'Generated address with ripe digest:', ripe.digest().encode('hex') + print 'Address generator calculated', numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix, 'addresses at', numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix/(time.time()-startTime),'addresses per second before finding one with the correct ripe-prefix.' + address = encodeAddress(3,streamNumber,ripe.digest()) + + #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) + #print 'privSigningKeyWIF',privSigningKeyWIF + + privEncryptionKey = '\x80'+potentialPrivEncryptionKey + checksum = hashlib.sha256(hashlib.sha256(privEncryptionKey).digest()).digest()[0:4] + privEncryptionKeyWIF = arithmetic.changebase(privEncryptionKey + checksum,256,58) + #print 'privEncryptionKeyWIF',privEncryptionKeyWIF + + 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)) + shared.config.set(address,'payloadlengthextrabytes',str(payloadLengthExtraBytes)) + shared.config.set(address,'privSigningKey',privSigningKeyWIF) + shared.config.set(address,'privEncryptionKey',privEncryptionKeyWIF) + with open(shared.appdata + 'keys.dat', 'wb') as configfile: + shared.config.write(configfile) + + #It may be the case that this address is being generated as a result of a call to the API. Let us put the result in the necessary queue. + apiAddressGeneratorReturnQueue.put(address) + + #self.emit(SIGNAL("updateStatusBar(PyQt_PyObject)"),'Done generating address. Doing work necessary to broadcast it...') + shared.UISignalQueue.put(('updateStatusBar','Done generating address. Doing work necessary to broadcast it...')) + #self.emit(SIGNAL("writeNewAddressToTable(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"),self.label,address,str(streamNumber)) + shared.UISignalQueue.put(('writeNewAddressToTable',(label,address,streamNumber))) + shared.reloadMyAddressHashes() + shared.workerQueue.put(('doPOWForMyV3Pubkey',ripe.digest())) + + elif command == 'createDeterministicAddresses' or command == 'getDeterministicAddress': + 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': + statusbar = 'Generating '+str(numberOfAddressesToMake) + ' new addresses.' + shared.UISignalQueue.put(('updateStatusBar',statusbar)) + signingKeyNonce = 0 + encryptionKeyNonce = 1 + 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 i in range(numberOfAddressesToMake): + #This next section is a little bit strange. We're going to generate keys over and over until we + #find one that has a RIPEMD hash that starts with either \x00 or \x00\x00. Then when we pack them + #into a Bitmessage address, we won't store the \x00 or \x00\x00 bytes thus making the address shorter. + startTime = time.time() + numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix = 0 + while True: + numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix += 1 + potentialPrivSigningKey = hashlib.sha512(deterministicPassphrase + encodeVarint(signingKeyNonce)).digest()[:32] + potentialPrivEncryptionKey = hashlib.sha512(deterministicPassphrase + encodeVarint(encryptionKeyNonce)).digest()[:32] + potentialPubSigningKey = pointMult(potentialPrivSigningKey) + potentialPubEncryptionKey = pointMult(potentialPrivEncryptionKey) + #print 'potentialPubSigningKey', potentialPubSigningKey.encode('hex') + #print 'potentialPubEncryptionKey', potentialPubEncryptionKey.encode('hex') + signingKeyNonce += 2 + encryptionKeyNonce += 2 + ripe = hashlib.new('ripemd160') + sha = hashlib.new('sha512') + sha.update(potentialPubSigningKey+potentialPubEncryptionKey) + ripe.update(sha.digest()) + #print 'potential ripe.digest', ripe.digest().encode('hex') + if eighteenByteRipe: + if ripe.digest()[:2] == '\x00\x00': + break + else: + if ripe.digest()[:1] == '\x00': + break + + print 'ripe.digest', ripe.digest().encode('hex') + print 'Address generator calculated', numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix, 'addresses at', numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix/(time.time()-startTime),'keys per second.' + address = encodeAddress(3,streamNumber,ripe.digest()) + + if command == 'createDeterministicAddresses': + #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: + shared.config.add_section(address) + print 'label:', label + 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)) + shared.config.set(address,'payloadlengthextrabytes',str(payloadLengthExtraBytes)) + shared.config.set(address,'privSigningKey',privSigningKeyWIF) + shared.config.set(address,'privEncryptionKey',privEncryptionKeyWIF) + with open(shared.appdata + 'keys.dat', 'wb') as configfile: + shared.config.write(configfile) + + #self.emit(SIGNAL("writeNewAddressToTable(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"),self.label,address,str(self.streamNumber)) + shared.UISignalQueue.put(('writeNewAddressToTable',(label,address,str(streamNumber)))) + listOfNewAddressesToSendOutThroughTheAPI.append(address) + if eighteenByteRipe: + shared.reloadMyAddressHashes()#This is necessary here (rather than just at the end) because otherwise if the human generates a large number of new addresses and uses one before they are done generating, the program will receive a getpubkey message and will ignore it. + except: + print address,'already exists. Not adding it again.' + + #Done generating addresses. + #It may be the case that this address is being generated as a result of a call to the API. Let us put the result in the necessary queue. + if command == 'createDeterministicAddresses': + apiAddressGeneratorReturnQueue.put(listOfNewAddressesToSendOutThroughTheAPI) + shared.UISignalQueue.put(('updateStatusBar','Done generating address')) + shared.reloadMyAddressHashes() + elif command == 'getDeterministicAddress': + apiAddressGeneratorReturnQueue.put(address) + else: + raise Exception("Error in the addressGenerator thread. Thread was given a command it could not understand: "+command) + +#This is one of several classes that constitute the API +#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): + def do_POST(self): + #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. + + #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(): + self.report_404() + return + + try: + # Get arguments by reading body of request. + # We read this in chunks to avoid straining + # socket.read(); around the 10 or 15Mb mark, some platforms + # begin to have problems (bug #792570). + max_chunk_size = 10*1024*1024 + size_remaining = int(self.headers["content-length"]) + L = [] + while size_remaining: + chunk_size = min(size_remaining, max_chunk_size) + L.append(self.rfile.read(chunk_size)) + size_remaining -= len(L[-1]) + data = ''.join(L) + + # 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(500) + self.end_headers() + else: + # got a valid XML RPC response + self.send_response(200) + self.send_header("Content-type", "text/xml") + self.send_header("Content-length", str(len(response))) + + # HACK :start -> sends cookies here + if self.cookies: + for cookie in self.cookies: + self.send_header('Set-Cookie',cookie.output(header='')) + # HACK :end + + self.end_headers() + self.wfile.write(response) + + # shut down the connection + self.wfile.flush() + self.connection.shutdown(1) + + + def APIAuthenticateClient(self): + if self.headers.has_key('Authorization'): + # handle Basic authentication + (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: + print 'Authentication failed because header lacks Authentication field' + time.sleep(2) + return False + + return False + + def _dispatch(self, method, params): + self.cookies = [] + + validuser = self.APIAuthenticateClient() + if not validuser: + time.sleep(2) + return "RPC Username or password incorrect or HTTP header lacks authentication at all." + # handle request + if method == 'helloWorld': + (a,b) = params + return a+'-'+b + elif method == 'add': + (a,b) = params + return a+b + elif method == 'statusBar': + message, = params + shared.UISignalQueue.put(('updateStatusBar',message)) + elif method == 'listAddresses': + data = '{"addresses":[' + configSections = shared.config.sections() + for addressInKeysFile in configSections: + if addressInKeysFile <> 'bitmessagesettings': + status,addressVersionNumber,streamNumber,hash = decodeAddress(addressInKeysFile) + data + if len(data) > 20: + data += ',' + data += json.dumps({'label':shared.config.get(addressInKeysFile,'label'),'address':addressInKeysFile,'stream':streamNumber,'enabled':shared.config.getboolean(addressInKeysFile,'enabled')},indent=4, separators=(',', ': ')) + data += ']}' + return data + elif method == 'createRandomAddress': + if len(params) == 0: + return 'API Error 0000: 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: + return 'API Error 0000: Too many parameters!' + label = label.decode('base64') + apiAddressGeneratorReturnQueue.queue.clear() + streamNumberForAddress = 1 + shared.addressGeneratorQueue.put(('createRandomAddress',3,streamNumberForAddress,label,1,"",eighteenByteRipe,nonceTrialsPerByte,payloadLengthExtraBytes)) + return apiAddressGeneratorReturnQueue.get() + elif method == 'createDeterministicAddresses': + if len(params) == 0: + return 'API Error 0000: 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: + return 'API Error 0000: Too many parameters!' + if len(passphrase) == 0: + return 'API Error 0001: The specified passphrase is blank.' + passphrase = passphrase.decode('base64') + if addressVersionNumber == 0: #0 means "just use the proper addressVersionNumber" + addressVersionNumber = 3 + if addressVersionNumber != 3: + return 'API Error 0002: The address version number currently must be 3 (or 0 which means auto-select). '+ addressVersionNumber+' isn\'t supported.' + if streamNumber == 0: #0 means "just use the most available stream" + streamNumber = 1 + if streamNumber != 1: + return 'API Error 0003: The stream number must be 1 (or 0 which means auto-select). Others aren\'t supported.' + if numberOfAddresses == 0: + return 'API Error 0004: Why would you ask me to generate 0 addresses for you?' + if numberOfAddresses > 999: + return 'API Error 0005: 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.' + apiAddressGeneratorReturnQueue.queue.clear() + print 'Requesting that the addressGenerator create', numberOfAddresses, 'addresses.' + shared.addressGeneratorQueue.put(('createDeterministicAddresses',addressVersionNumber,streamNumber,'unused API address',numberOfAddresses,passphrase,eighteenByteRipe,nonceTrialsPerByte,payloadLengthExtraBytes)) + data = '{"addresses":[' + queueReturn = apiAddressGeneratorReturnQueue.get() + for item in queueReturn: + if len(data) > 20: + data += ',' + data += "\""+item+ "\"" + data += ']}' + return data + elif method == 'getDeterministicAddress': + if len(params) != 3: + return 'API Error 0000: I need exactly 3 parameters.' + passphrase, addressVersionNumber, streamNumber = params + numberOfAddresses = 1 + eighteenByteRipe = False + if len(passphrase) == 0: + return 'API Error 0001: The specified passphrase is blank.' + passphrase = passphrase.decode('base64') + if addressVersionNumber != 3: + return 'API Error 0002: The address version number currently must be 3. '+ addressVersionNumber +' isn\'t supported.' + if streamNumber != 1: + return 'API Error 0003: The stream number must be 1. Others aren\'t supported.' + apiAddressGeneratorReturnQueue.queue.clear() + print 'Requesting that the addressGenerator create', numberOfAddresses, 'addresses.' + shared.addressGeneratorQueue.put(('getDeterministicAddress',addressVersionNumber,streamNumber,'unused API address',numberOfAddresses,passphrase,eighteenByteRipe)) + return apiAddressGeneratorReturnQueue.get() + elif method == 'getAllInboxMessages': + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''SELECT msgid, toaddress, fromaddress, subject, received, message FROM inbox where folder='inbox' ORDER BY received''') + shared.sqlSubmitQueue.put('') + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + data = '{"inboxMessages":[' + for row in queryreturn: + msgid, toAddress, fromAddress, subject, received, message, = row + if len(data) > 25: + data += ',' + data += json.dumps({'msgid':msgid.encode('hex'),'toAddress':toAddress,'fromAddress':fromAddress,'subject':subject.encode('base64'),'message':message.encode('base64'),'encodingType':2,'receivedTime':received},indent=4, separators=(',', ': ')) + data += ']}' + return data + elif method == 'trashMessage': + if len(params) == 0: + return 'API Error 0000: I need parameters!' + msgid = params[0].decode('hex') + t = (msgid,) + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''UPDATE inbox SET folder='trash' WHERE msgid=?''') + shared.sqlSubmitQueue.put(t) + shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() + shared.UISignalQueue.put(('updateStatusBar','Per API: Trashed message (assuming message existed). UI not updated.')) + return 'Trashed message (assuming message existed). UI not updated. To double check, run getAllInboxMessages to see that the message disappeared, or restart Bitmessage and look in the normal Bitmessage GUI.' + elif method == 'sendMessage': + if len(params) == 0: + return 'API Error 0000: I need parameters!' + elif len(params) == 4: + toAddress, fromAddress, subject, message = params + encodingType = 2 + elif len(params) == 5: + toAddress, fromAddress, subject, message, encodingType = params + if encodingType != 2: + return 'API Error 0006: The encoding type must be 2 because that is the only one this program currently supports.' + subject = subject.decode('base64') + message = message.decode('base64') + status,addressVersionNumber,streamNumber,toRipe = decodeAddress(toAddress) + if status <> 'success': + shared.printLock.acquire() + print 'API Error 0007: Could not decode address:', toAddress, ':', status + shared.printLock.release() + if status == 'checksumfailed': + return 'API Error 0008: Checksum failed for address: ' + toAddress + if status == 'invalidcharacters': + return 'API Error 0009: Invalid characters in address: '+ toAddress + if status == 'versiontoohigh': + return 'API Error 0010: Address version number too high (or zero) in address: ' + toAddress + return 'API Error 0007: Could not decode address: ' + toAddress+ ' : '+ status + if addressVersionNumber < 2 or addressVersionNumber > 3: + return 'API Error 0011: The address version number currently must be 2 or 3. Others aren\'t supported. Check the toAddress.' + if streamNumber != 1: + return 'API Error 0012: The stream number must be 1. Others aren\'t supported. Check the toAddress.' + status,addressVersionNumber,streamNumber,fromRipe = decodeAddress(fromAddress) + if status <> 'success': + shared.printLock.acquire() + print 'API Error 0007: Could not decode address:', fromAddress, ':', status + shared.printLock.release() + if status == 'checksumfailed': + return 'API Error 0008: Checksum failed for address: ' + fromAddress + if status == 'invalidcharacters': + return 'API Error 0009: Invalid characters in address: '+ fromAddress + if status == 'versiontoohigh': + return 'API Error 0010: Address version number too high (or zero) in address: ' + fromAddress + return 'API Error 0007: Could not decode address: ' + fromAddress+ ' : '+ status + if addressVersionNumber < 2 or addressVersionNumber > 3: + return 'API Error 0011: The address version number currently must be 2 or 3. Others aren\'t supported. Check the fromAddress.' + if streamNumber != 1: + return 'API Error 0012: The stream number must be 1. Others aren\'t supported. Check the fromAddress.' + toAddress = addBMIfNotPresent(toAddress) + fromAddress = addBMIfNotPresent(fromAddress) + try: + fromAddressEnabled = shared.config.getboolean(fromAddress,'enabled') + except: + return 'API Error 0013: Could not find your fromAddress in the keys.dat file.' + if not fromAddressEnabled: + return 'API Error 0014: Your fromAddress is disabled. Cannot send.' + + ackdata = OpenSSL.rand(32) + shared.sqlLock.acquire() + t = ('',toAddress,toRipe,fromAddress,subject,message,ackdata,int(time.time()),'findingpubkey',1,1,'sent',2) + shared.sqlSubmitQueue.put('''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)''') + shared.sqlSubmitQueue.put(t) + shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() + + toLabel = '' + t = (toAddress,) + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''select label from addressbook where address=?''') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + 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))) + + shared.workerQueue.put(('sendmessage',toAddress)) + + return ackdata.encode('hex') + + elif method == 'sendBroadcast': + if len(params) == 0: + return 'API Error 0000: I need parameters!' + if len(params) == 3: + fromAddress, subject, message = params + encodingType = 2 + elif len(params) == 4: + fromAddress, subject, message, encodingType = params + if encodingType != 2: + return 'API Error 0006: The encoding type must be 2 because that is the only one this program currently supports.' + subject = subject.decode('base64') + message = message.decode('base64') + + status,addressVersionNumber,streamNumber,fromRipe = decodeAddress(fromAddress) + if status <> 'success': + shared.printLock.acquire() + print 'API Error 0007: Could not decode address:', fromAddress, ':', status + shared.printLock.release() + if status == 'checksumfailed': + return 'API Error 0008: Checksum failed for address: ' + fromAddress + if status == 'invalidcharacters': + return 'API Error 0009: Invalid characters in address: '+ fromAddress + if status == 'versiontoohigh': + return 'API Error 0010: Address version number too high (or zero) in address: ' + fromAddress + return 'API Error 0007: Could not decode address: ' + fromAddress+ ' : '+ status + if addressVersionNumber < 2 or addressVersionNumber > 3: + return 'API Error 0011: the address version number currently must be 2 or 3. Others aren\'t supported. Check the fromAddress.' + if streamNumber != 1: + return 'API Error 0012: the stream number must be 1. Others aren\'t supported. Check the fromAddress.' + fromAddress = addBMIfNotPresent(fromAddress) + try: + fromAddressEnabled = shared.config.getboolean(fromAddress,'enabled') + except: + return 'API Error 0013: could not find your fromAddress in the keys.dat file.' + ackdata = OpenSSL.rand(32) + toAddress = '[Broadcast subscribers]' + ripe = '' + + shared.sqlLock.acquire() + t = ('',toAddress,ripe,fromAddress,subject,message,ackdata,int(time.time()),'broadcastpending',1,1,'sent',2) + shared.sqlSubmitQueue.put('''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)''') + shared.sqlSubmitQueue.put(t) + shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() + + toLabel = '[Broadcast subscribers]' + #apiSignalQueue.put(('displayNewSentMessage',(toAddress,toLabel,fromAddress,subject,message,ackdata))) + #self.emit(SIGNAL("displayNewSentMessage(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"),toAddress,toLabel,fromAddress,subject,message,ackdata) + shared.UISignalQueue.put(('displayNewSentMessage',(toAddress,toLabel,fromAddress,subject,message,ackdata))) + shared.workerQueue.put(('sendbroadcast',(fromAddress,subject,message))) + + return ackdata.encode('hex') + elif method == 'getStatus': + if len(params) != 1: + return 'API Error 0000: I need one parameter!' + ackdata, = params + if len(ackdata) != 64: + return 'API Error 0015: The length of ackData should be 32 bytes (encoded in hex thus 64 characters).' + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''SELECT status FROM sent where ackdata=?''') + shared.sqlSubmitQueue.put((ackdata.decode('hex'),)) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + if queryreturn == []: + return 'notFound' + for row in queryreturn: + status, = row + if status == 'findingpubkey': + return 'findingPubkey' + if status == 'doingpow': + return 'doingPow' + if status == 'sentmessage': + return 'sentMessage' + if status == 'ackreceived': + return 'ackReceived' + else: + return 'otherStatus: '+status + elif method == 'addSubscription': + if len(params) == 0: + return 'API Error 0000: I need parameters!' + if len(params) == 1: + address, = params + label == '' + if len(params) == 2: + address, label = params + label = label.decode('base64') + try: + label.decode('utf-8') + except UnicodeDecodeError: + return 'API Error 0017: Label is not valid UTF-8 data.' + if len(params) >2: + return 'API Error 0000: I need either 1 or 2 parameters!' + address = addBMIfNotPresent(address) + status,addressVersionNumber,streamNumber,toRipe = decodeAddress(address) + if status <> 'success': + shared.printLock.acquire() + print 'API Error 0007: Could not decode address:', address, ':', status + shared.printLock.release() + if status == 'checksumfailed': + return 'API Error 0008: Checksum failed for address: ' + address + if status == 'invalidcharacters': + return 'API Error 0009: Invalid characters in address: '+ address + if status == 'versiontoohigh': + return 'API Error 0010: Address version number too high (or zero) in address: ' + address + return 'API Error 0007: Could not decode address: ' + address+ ' : '+ status + if addressVersionNumber < 2 or addressVersionNumber > 3: + return 'API Error 0011: The address version number currently must be 2 or 3. Others aren\'t supported.' + if streamNumber != 1: + return 'API Error 0012: The stream number must be 1. Others aren\'t supported.' + #First we must check to see if the address is already in the subscriptions list. + shared.sqlLock.acquire() + t = (address,) + shared.sqlSubmitQueue.put('''select * from subscriptions where address=?''') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + if queryreturn != []: + return 'API Error 0016: You are already subscribed to that address.' + t = (label,address,True) + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''INSERT INTO subscriptions VALUES (?,?,?)''') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() + shared.reloadBroadcastSendersForWhichImWatching() + shared.UISignalQueue.put(('rerenderInboxFromLabels','')) + shared.UISignalQueue.put(('rerenderSubscriptions','')) + return 'Added subscription.' + + elif method == 'deleteSubscription': + if len(params) != 1: + return 'API Error 0000: I need 1 parameter!' + address, = params + address = addBMIfNotPresent(address) + t = (address,) + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''DELETE FROM subscriptions WHERE address=?''') + shared.sqlSubmitQueue.put(t) + shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() + shared.reloadBroadcastSendersForWhichImWatching() + shared.UISignalQueue.put(('rerenderInboxFromLabels','')) + shared.UISignalQueue.put(('rerenderSubscriptions','')) + return 'Deleted subscription if it existed.' + else: + return 'Invalid Method: %s'%method + +#This thread, of which there is only one, runs the API. +class singleAPI(threading.Thread): + def __init__(self): + threading.Thread.__init__(self) + + def run(self): + se = SimpleXMLRPCServer((shared.config.get('bitmessagesettings', 'apiinterface'),shared.config.getint('bitmessagesettings', 'apiport')), MySimpleXMLRPCRequestHandler, True, True) + se.register_introspection_functions() + se.serve_forever() + +selfInitiatedConnections = {} #This is a list of current connections (the thread pointers at least) +alreadyAttemptedConnectionsList = {} #This is a list of nodes to which we have already attempted a connection +ackdataForWhichImWatching = {} +alreadyAttemptedConnectionsListLock = threading.Lock() +eightBytesOfRandomDataUsedToDetectConnectionsToSelf = pack('>Q',random.randrange(1, 18446744073709551615)) +neededPubkeys = {} +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. +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. +numberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHavePerPeer = {} + +if useVeryEasyProofOfWorkForTesting: + shared.networkDefaultProofOfWorkNonceTrialsPerByte = int(shared.networkDefaultProofOfWorkNonceTrialsPerByte / 16) + shared.networkDefaultPayloadLengthExtraBytes = int(shared.networkDefaultPayloadLengthExtraBytes / 7000) if __name__ == "__main__": - main() + # is the application already running? If yes then exit. + thisapp = singleton.singleinstance() + + signal.signal(signal.SIGINT, signal_handler) + #signal.signal(signal.SIGINT, signal.SIG_DFL) + + # Check the Major version, the first element in the array + if sqlite3.sqlite_version_info[0] < 3: + print 'This program requires sqlite version 3 or higher because 2 and lower cannot store NULL values. I see version:', sqlite3.sqlite_version_info + os._exit(0) + + #First try to load the config file (the keys.dat file) from the program directory + shared.config = ConfigParser.SafeConfigParser() + shared.config.read('keys.dat') + try: + shared.config.get('bitmessagesettings', 'settingsversion') + print 'Loading config files from same directory as program' + shared.appdata = '' + 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 = ConfigParser.SafeConfigParser() + shared.config.read(shared.appdata + 'keys.dat') + try: + shared.config.get('bitmessagesettings', 'settingsversion') + print 'Loading existing config files from', shared.appdata + except: + #This appears to be the first time running the program; there is no config file (or it cannot be accessed). Create config file. + shared.config.add_section('bitmessagesettings') + shared.config.set('bitmessagesettings','settingsversion','5') + 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: + 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: + 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','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') + + if storeConfigFilesInSameDirectoryAsProgramByDefault: + #Just use the same directory as the program and forget about the appdata folder + shared.appdata = '' + print 'Creating new config files in same directory as program.' + else: + print 'Creating new config files in', shared.appdata + if not os.path.exists(shared.appdata): + os.makedirs(shared.appdata) + with open(shared.appdata + 'keys.dat', 'wb') as configfile: + shared.config.write(configfile) + + + if shared.config.getint('bitmessagesettings','settingsversion') == 1: + shared.config.set('bitmessagesettings','settingsversion','4') #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','keysencrypted','false') + shared.config.set('bitmessagesettings','messagesencrypted','false') + with open(shared.appdata + 'keys.dat', 'wb') as configfile: + shared.config.write(configfile) + + 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') + shared.knownNodes = pickle.load(pickleFile) + pickleFile.close() + except: + createDefaultKnownNodes(shared.appdata) + pickleFile = open(shared.appdata + 'knownnodes.dat', 'rb') + shared.knownNodes = pickle.load(pickleFile) + pickleFile.close() + if shared.config.getint('bitmessagesettings', 'settingsversion') > 5: + print 'Bitmessage cannot read future versions of the keys file (keys.dat). Run the newer version of Bitmessage.' + raise SystemExit + + #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): + print 'Adding', item[4][0],'to knownNodes based on DNS boostrap method' + shared.knownNodes[1][item[4][0]] = (8080,int(time.time())) + except: + print 'bootstrap8080.bitmessage.org DNS bootstraping failed.' + try: + for item in socket.getaddrinfo('bootstrap8444.bitmessage.org',80): + print 'Adding', item[4][0],'to knownNodes based on DNS boostrap method' + shared.knownNodes[1][item[4][0]] = (8444,int(time.time())) + except: + print 'bootstrap8444.bitmessage.org DNS bootstrapping failed.' + else: + print 'DNS bootstrap skipped because SOCKS is used.' + #Start the address generation thread + addressGeneratorThread = addressGenerator() + addressGeneratorThread.daemon = True # close the main program even if there are threads left + addressGeneratorThread.start() + + #Start the thread that calculates POWs + singleWorkerThread = singleWorker() + singleWorkerThread.daemon = True # close the main program even if there are threads left + singleWorkerThread.start() + + #Start the SQL thread + sqlLookup = sqlThread() + 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() + + #Start the cleanerThread + singleCleanerThread = singleCleaner() + singleCleanerThread.daemon = True # close the main program even if there are threads left + singleCleanerThread.start() + + shared.reloadMyAddressHashes() + shared.reloadBroadcastSendersForWhichImWatching() + + #Initialize the ackdataForWhichImWatching data structure using data from the sql database. + shared.sqlSubmitQueue.put('''SELECT ackdata FROM sent where (status='sentmessage' OR status='doingpow')''') + shared.sqlSubmitQueue.put('') + queryreturn = shared.sqlReturnQueue.get() + for row in queryreturn: + ackdata, = row + print 'Watching for ackdata', ackdata.encode('hex') + ackdataForWhichImWatching[ackdata] = 0 + + if shared.safeConfigGetBoolean('bitmessagesettings','apienabled'): + try: + apiNotifyPath = shared.config.get('bitmessagesettings','apinotifypath') + except: + apiNotifyPath = '' + if apiNotifyPath != '': + shared.printLock.acquire() + print 'Trying to call', apiNotifyPath + shared.printLock.release() + call([apiNotifyPath, "startingUp"]) + singleAPIThread = singleAPI() + singleAPIThread.daemon = True #close the main program even if there are threads left + singleAPIThread.start() + #self.singleAPISignalHandlerThread = singleAPISignalHandler() + #self.singleAPISignalHandlerThread.start() + #QtCore.QObject.connect(self.singleAPISignalHandlerThread, QtCore.SIGNAL("updateStatusBar(PyQt_PyObject)"), self.updateStatusBar) + #QtCore.QObject.connect(self.singleAPISignalHandlerThread, QtCore.SIGNAL("passAddressGeneratorObjectThrough(PyQt_PyObject)"), self.connectObjectToAddressGeneratorSignals) + #QtCore.QObject.connect(self.singleAPISignalHandlerThread, QtCore.SIGNAL("displayNewSentMessage(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), self.displayNewSentMessage) + + connectToStream(1) + + singleListenerThread = singleListener() + singleListenerThread.daemon = True # close the main program even if there are threads left + singleListenerThread.start() + + if not shared.safeConfigGetBoolean('bitmessagesettings','daemon'): + try: + from PyQt4.QtCore import * + from PyQt4.QtGui import * + except Exception, 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) + + import bitmessageqt + bitmessageqt.run() + else: + print 'Running as a daemon. You can use Ctrl+C to exit.' + while True: + time.sleep(20) -# So far, the creation of and management of the Bitmessage protocol and this -# client is a one-man operation. Bitcoin tips are quite appreciated. +# So far, the Bitmessage protocol, this client, the Wiki, and the forums +# are all a one-man operation. Bitcoin tips are quite appreciated! # 1H5XaDA6fYENLbknwZyjiYXYPQaFjjLX2u diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 40113b5a..add21421 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -1,956 +1,484 @@ -""" -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 try: - from plugins.plugin import get_plugin, get_plugins + from PyQt4.QtCore import * + from PyQt4.QtGui import * +except Exception, err: + print 'PyBitmessage requires PyQt. You can download it from http://www.riverbankcomputing.com/software/pyqt/download or by searching Google for \'PyQt Download\' (without quotes).' + print 'Error message:', err + sys.exit() + +withMessagingMenu = False +try: + from gi.repository import MessagingMenu + from gi.repository import Notify + withMessagingMenu = True except ImportError: - get_plugins = False + MessagingMenu = None +from addresses import * +import shared +from bitmessageui import * +from newaddressdialog import * +from newsubscriptiondialog import * +from regenerateaddresses import * +from specialaddressbehavior import * +from settings import * +from about import * +from help import * +from iconglossary import * +import sys +from time import strftime, localtime, gmtime +import time +import os +from pyelliptic.openssl import OpenSSL +import pickle +import platform -# TODO: rewrite -def powQueueSize(): - """Returns the size of queues.workerQueue including current unfinished work""" - queue_len = queues.workerQueue.qsize() - for thread in threading.enumerate(): - try: - if thread.name == "singleWorker": - queue_len += thread.busy - except Exception as err: - logger.info('Thread error %s', err) - return queue_len +class MyForm(QtGui.QMainWindow): - -def openKeysFile(): - """Open keys file with an external editor""" - keysfile = os.path.join(state.appdata, 'keys.dat') - if 'linux' in sys.platform: - subprocess.call(["xdg-open", keysfile]) - elif sys.platform.startswith('win'): - os.startfile(keysfile) # pylint: disable=no-member - - -class MyForm(settingsmixin.SMainWindow): - - # the maximum frequency of message sounds in seconds - maxSoundFrequencySec = 60 - - REPLY_TYPE_SENDER = 0 - REPLY_TYPE_CHAN = 1 - REPLY_TYPE_UPD = 2 - - def change_translation(self, newlocale=None): - """Change translation language for the application""" - if newlocale is None: - newlocale = l10n.getTranslationLanguage() - try: - if not self.qmytranslator.isEmpty(): - QtGui.QApplication.removeTranslator(self.qmytranslator) - except: - pass - try: - if not self.qsystranslator.isEmpty(): - QtGui.QApplication.removeTranslator(self.qsystranslator) - except: - pass - - self.qmytranslator = QtCore.QTranslator() - translationpath = os.path.join( - paths.codePath(), 'translations', 'bitmessage_' + newlocale) - self.qmytranslator.load(translationpath) - QtGui.QApplication.installTranslator(self.qmytranslator) - - self.qsystranslator = QtCore.QTranslator() - if paths.frozen: - translationpath = os.path.join( - paths.codePath(), 'translations', 'qt_' + newlocale) - else: - translationpath = os.path.join( - str(QtCore.QLibraryInfo.location( - QtCore.QLibraryInfo.TranslationsPath)), 'qt_' + newlocale) - self.qsystranslator.load(translationpath) - QtGui.QApplication.installTranslator(self.qsystranslator) - - # TODO: move this block into l10n - # FIXME: shouldn't newlocale be used here? - lang = locale.normalize(l10n.getTranslationLanguage()) - langs = [ - lang.split(".")[0] + "." + l10n.encoding, - lang.split(".")[0] + "." + 'UTF-8', - lang - ] - if 'win32' in sys.platform or 'win64' in sys.platform: - langs = [l10n.getWindowsLocale(lang)] - for lang in langs: - try: - l10n.setlocale(lang) - if 'win32' not in sys.platform and 'win64' not in sys.platform: - l10n.encoding = locale.nl_langinfo(locale.CODESET) - else: - l10n.encoding = locale.getlocale()[1] - logger.info("Successfully set locale to %s", lang) - break - except: - logger.error("Failed to set locale to %s", lang, exc_info=True) - - def init_file_menu(self): - QtCore.QObject.connect(self.ui.actionExit, QtCore.SIGNAL( - "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, - QtCore.SIGNAL( - "triggered()"), - self.click_actionDeleteAllTrashedMessages) - QtCore.QObject.connect(self.ui.actionRegenerateDeterministicAddresses, - 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.pushButtonNewAddress, QtCore.SIGNAL( - "clicked()"), self.click_NewAddressDialog) - QtCore.QObject.connect(self.ui.pushButtonAddAddressBook, QtCore.SIGNAL( - "clicked()"), self.click_pushButtonAddAddressBook) - QtCore.QObject.connect(self.ui.pushButtonAddSubscription, QtCore.SIGNAL( - "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( - "clicked()"), self.click_pushButtonFetchNamecoinID) - QtCore.QObject.connect(self.ui.actionSettings, QtCore.SIGNAL( - "triggered()"), self.click_actionSettings) - QtCore.QObject.connect(self.ui.actionAbout, QtCore.SIGNAL( - "triggered()"), self.click_actionAbout) - QtCore.QObject.connect(self.ui.actionSupport, QtCore.SIGNAL( - "triggered()"), self.click_actionSupport) - QtCore.QObject.connect(self.ui.actionHelp, QtCore.SIGNAL( - "triggered()"), self.click_actionHelp) - - def init_inbox_popup_menu(self, connectSignal=True): - # Popup menu for the Inbox tab - self.ui.inboxContextMenuToolbar = QtGui.QToolBar() - # Actions - self.actionReply = self.ui.inboxContextMenuToolbar.addAction(_translate( - "MainWindow", "Reply to sender"), self.on_action_InboxReply) - self.actionReplyChan = self.ui.inboxContextMenuToolbar.addAction(_translate( - "MainWindow", "Reply to channel"), self.on_action_InboxReplyChan) - self.actionAddSenderToAddressBook = self.ui.inboxContextMenuToolbar.addAction( - _translate( - "MainWindow", "Add sender to your Address Book"), - self.on_action_InboxAddSenderToAddressBook) - self.actionAddSenderToBlackList = self.ui.inboxContextMenuToolbar.addAction( - _translate( - "MainWindow", "Add sender to your Blacklist"), - self.on_action_InboxAddSenderToBlackList) - self.actionTrashInboxMessage = self.ui.inboxContextMenuToolbar.addAction( - _translate("MainWindow", "Move to Trash"), - self.on_action_InboxTrash) - self.actionUndeleteTrashedMessage = self.ui.inboxContextMenuToolbar.addAction( - _translate("MainWindow", "Undelete"), - self.on_action_TrashUndelete) - self.actionForceHtml = self.ui.inboxContextMenuToolbar.addAction( - _translate( - "MainWindow", "View HTML code as formatted text"), - self.on_action_InboxMessageForceHtml) - self.actionSaveMessageAs = self.ui.inboxContextMenuToolbar.addAction( - _translate( - "MainWindow", "Save message as..."), - self.on_action_InboxSaveMessageAs) - self.actionMarkUnread = self.ui.inboxContextMenuToolbar.addAction( - _translate( - "MainWindow", "Mark Unread"), self.on_action_InboxMarkUnread) - - # contextmenu messagelists - self.ui.tableWidgetInbox.setContextMenuPolicy( - QtCore.Qt.CustomContextMenu) - if connectSignal: - self.connect(self.ui.tableWidgetInbox, QtCore.SIGNAL( - 'customContextMenuRequested(const QPoint&)'), - 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.ui.tableWidgetInboxChans.setContextMenuPolicy( - QtCore.Qt.CustomContextMenu) - if connectSignal: - self.connect(self.ui.tableWidgetInboxChans, QtCore.SIGNAL( - 'customContextMenuRequested(const QPoint&)'), - self.on_context_menuInbox) - - def init_identities_popup_menu(self, connectSignal=True): - # Popup menu for the Your Identities tab - self.ui.addressContextMenuToolbarYourIdentities = QtGui.QToolBar() - # Actions - self.actionNewYourIdentities = self.ui.addressContextMenuToolbarYourIdentities.addAction(_translate( - "MainWindow", "New"), self.on_action_YourIdentitiesNew) - self.actionEnableYourIdentities = self.ui.addressContextMenuToolbarYourIdentities.addAction( - _translate( - "MainWindow", "Enable"), self.on_action_Enable) - self.actionDisableYourIdentities = self.ui.addressContextMenuToolbarYourIdentities.addAction( - _translate( - "MainWindow", "Disable"), self.on_action_Disable) - self.actionSetAvatarYourIdentities = self.ui.addressContextMenuToolbarYourIdentities.addAction( - _translate( - "MainWindow", "Set avatar..."), - self.on_action_TreeWidgetSetAvatar) - self.actionClipboardYourIdentities = self.ui.addressContextMenuToolbarYourIdentities.addAction( - _translate( - "MainWindow", "Copy address to clipboard"), - self.on_action_Clipboard) - self.actionSpecialAddressBehaviorYourIdentities = self.ui.addressContextMenuToolbarYourIdentities.addAction( - _translate( - "MainWindow", "Special address behavior..."), - self.on_action_SpecialAddressBehaviorDialog) - self.actionEmailGateway = self.ui.addressContextMenuToolbarYourIdentities.addAction( - _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 - )) - - def init_chan_popup_menu(self, connectSignal=True): - # Actions - self.actionNew = self.ui.addressContextMenuToolbar.addAction(_translate( - "MainWindow", "New"), self.on_action_YourIdentitiesNew) - self.actionDelete = self.ui.addressContextMenuToolbar.addAction( - _translate("MainWindow", "Delete"), - self.on_action_YourIdentitiesDelete) - self.actionEnable = self.ui.addressContextMenuToolbar.addAction( - _translate( - "MainWindow", "Enable"), self.on_action_Enable) - self.actionDisable = self.ui.addressContextMenuToolbar.addAction( - _translate( - "MainWindow", "Disable"), self.on_action_Disable) - self.actionSetAvatar = self.ui.addressContextMenuToolbar.addAction( - _translate( - "MainWindow", "Set avatar..."), - self.on_action_TreeWidgetSetAvatar) - self.actionClipboard = self.ui.addressContextMenuToolbar.addAction( - _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..."), - self.on_action_SpecialAddressBehaviorDialog) - - self.ui.treeWidgetChans.setContextMenuPolicy( - QtCore.Qt.CustomContextMenu) - if connectSignal: - self.connect(self.ui.treeWidgetChans, QtCore.SIGNAL( - 'customContextMenuRequested(const QPoint&)'), - self.on_context_menuChan) - - def init_addressbook_popup_menu(self, connectSignal=True): - # Popup menu for the Address Book page - self.ui.addressBookContextMenuToolbar = QtGui.QToolBar() - # Actions - self.actionAddressBookSend = self.ui.addressBookContextMenuToolbar.addAction( - _translate( - "MainWindow", "Send message to this address"), - self.on_action_AddressBookSend) - self.actionAddressBookClipboard = self.ui.addressBookContextMenuToolbar.addAction( - _translate( - "MainWindow", "Copy address to clipboard"), - self.on_action_AddressBookClipboard) - self.actionAddressBookSubscribe = self.ui.addressBookContextMenuToolbar.addAction( - _translate( - "MainWindow", "Subscribe to this address"), - self.on_action_AddressBookSubscribe) - self.actionAddressBookSetAvatar = self.ui.addressBookContextMenuToolbar.addAction( - _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) - self.actionAddressBookDelete = self.ui.addressBookContextMenuToolbar.addAction( - _translate( - "MainWindow", "Delete"), self.on_action_AddressBookDelete) - self.ui.tableWidgetAddressBook.setContextMenuPolicy( - QtCore.Qt.CustomContextMenu) - if connectSignal: - self.connect(self.ui.tableWidgetAddressBook, QtCore.SIGNAL( - 'customContextMenuRequested(const QPoint&)'), - self.on_context_menuAddressBook) - - def init_subscriptions_popup_menu(self, connectSignal=True): - # Actions - self.actionsubscriptionsNew = self.ui.subscriptionsContextMenuToolbar.addAction( - _translate("MainWindow", "New"), self.on_action_SubscriptionsNew) - self.actionsubscriptionsDelete = self.ui.subscriptionsContextMenuToolbar.addAction( - _translate("MainWindow", "Delete"), - self.on_action_SubscriptionsDelete) - self.actionsubscriptionsClipboard = self.ui.subscriptionsContextMenuToolbar.addAction( - _translate("MainWindow", "Copy address to clipboard"), - self.on_action_SubscriptionsClipboard) - self.actionsubscriptionsEnable = self.ui.subscriptionsContextMenuToolbar.addAction( - _translate("MainWindow", "Enable"), - self.on_action_SubscriptionsEnable) - self.actionsubscriptionsDisable = self.ui.subscriptionsContextMenuToolbar.addAction( - _translate("MainWindow", "Disable"), - self.on_action_SubscriptionsDisable) - 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) - - def init_sent_popup_menu(self, connectSignal=True): - # Actions - self.actionTrashSentMessage = self.ui.sentContextMenuToolbar.addAction( - _translate( - "MainWindow", "Move to Trash"), self.on_action_SentTrash) - self.actionSentClipboard = self.ui.sentContextMenuToolbar.addAction( - _translate( - "MainWindow", "Copy destination address to clipboard"), - self.on_action_SentClipboard) - 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 ) - - def rerenderTabTreeSubscriptions(self): - treeWidget = self.ui.treeWidgetSubscriptions - folders = Ui_FolderWidget.folderWeight.keys() - folders.remove("new") - - # sort ascending when creating - if treeWidget.topLevelItemCount() == 0: - treeWidget.header().setSortIndicator( - 0, QtCore.Qt.AscendingOrder) - # init dictionary - - db = getSortedSubscriptions(True) - for address in db: - for folder in folders: - if folder not in db[address]: - db[address][folder] = {} - - if treeWidget.isSortingEnabled(): - treeWidget.setSortingEnabled(False) - - widgets = {} - i = 0 - while i < treeWidget.topLevelItemCount(): - widget = treeWidget.topLevelItem(i) - if widget is not None: - toAddress = widget.address - else: - toAddress = None - - if toAddress not in db: - treeWidget.takeTopLevelItem(i) - # no increment - continue - unread = 0 - j = 0 - while j < widget.childCount(): - subwidget = widget.child(j) - try: - subwidget.setUnreadCount(db[toAddress][subwidget.folderName]['count']) - unread += db[toAddress][subwidget.folderName]['count'] - db[toAddress].pop(subwidget.folderName, None) - except: - widget.takeChild(j) - # no increment - continue - j += 1 - - # add missing folders - if len(db[toAddress]) > 0: - j = 0 - for f, c in db[toAddress].iteritems(): - try: - subwidget = Ui_FolderWidget(widget, j, toAddress, f, c['count']) - except KeyError: - subwidget = Ui_FolderWidget(widget, j, toAddress, f, 0) - j += 1 - 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']) - j = 0 - unread = 0 - for folder in folders: - try: - subwidget = Ui_FolderWidget(widget, j, toAddress, folder, db[toAddress][folder]['count']) - unread += db[toAddress][folder]['count'] - except KeyError: - subwidget = Ui_FolderWidget(widget, j, toAddress, folder, 0) - 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) - # init dictionary - db = {} - enabled = {} - - for toAddress in config.addresses(True): - isEnabled = config.getboolean( - toAddress, 'enabled') - isChan = config.safeGetBoolean( - toAddress, 'chan') - isMaillinglist = config.safeGetBoolean( - toAddress, 'mailinglist') - - if treeWidget == self.ui.treeWidgetYourIdentities: - if isChan: - continue - elif treeWidget == self.ui.treeWidgetChans: - if not isChan: - continue - - 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") - for row in queryreturn: - toaddress, folder, cnt = row - total += cnt - if toaddress in db and folder in db[toaddress]: - db[toaddress][folder] = cnt - if treeWidget == self.ui.treeWidgetYourIdentities: - db[None] = {} - db[None]["inbox"] = total - db[None]["new"] = total - db[None]["sent"] = 0 - db[None]["trash"] = 0 - enabled[None] = True - - if treeWidget.isSortingEnabled(): - treeWidget.setSortingEnabled(False) - - widgets = {} - i = 0 - while i < treeWidget.topLevelItemCount(): - widget = treeWidget.topLevelItem(i) - if widget is not None: - toAddress = widget.address - else: - toAddress = None - - if toAddress not in db: - treeWidget.takeTopLevelItem(i) - # no increment - continue - unread = 0 - j = 0 - while j < widget.childCount(): - subwidget = widget.child(j) - try: - 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: - widget.takeChild(j) - # no increment - continue - j += 1 - - # add missing folders - 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"): - 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": - continue - subwidget = Ui_FolderWidget(widget, j, toAddress, folder, db[toAddress][folder]) - if subwidget.folderName not in ("new", "trash", "sent"): - unread += db[toAddress][folder] - j += 1 - widget.setUnreadCount(unread) - i += 1 - - treeWidget.setSortingEnabled(True) + str_broadcast_subscribers = '[Broadcast subscribers]' def __init__(self, parent=None): QtGui.QWidget.__init__(self, parent) self.ui = Ui_MainWindow() self.ui.setupUi(self) - self.qmytranslator = self.qsystranslator = None - self.indicatorUpdate = None - self.actionStatus = None + #Ask the user if we may delete their old version 1 addresses if they have any. + configSections = shared.config.sections() + for addressInKeysFile in configSections: + if addressInKeysFile <> 'bitmessagesettings': + status,addressVersionNumber,streamNumber,hash = decodeAddress(addressInKeysFile) + if addressVersionNumber == 1: + displayMsg = "One of your addresses, "+addressInKeysFile+", is an old version 1 address. Version 1 addresses are no longer supported. May we delete it now?" + reply = QtGui.QMessageBox.question(self, 'Message',displayMsg, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) + if reply == QtGui.QMessageBox.Yes: + shared.config.remove_section(addressInKeysFile) + with open(shared.appdata + 'keys.dat', 'wb') as configfile: + shared.config.write(configfile) - # the last time that a message arrival sound was played - self.lastSoundTime = datetime.now() - timedelta(days=1) + #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 - # Ask the user if we may delete their old version 1 addresses if they - # have any. - for addressInKeysFile in config.addresses(): - 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) - reply = QtGui.QMessageBox.question( - self, 'Message', displayMsg, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) - if reply == QtGui.QMessageBox.Yes: - config.remove_section(addressInKeysFile) - config.save() + """self.trayIcon = QtGui.QSystemTrayIcon(self) + self.trayIcon.setIcon( QtGui.QIcon(':/newPrefix/images/can-icon-16px.png') ) + traySignal = "activated(QSystemTrayIcon::ActivationReason)" + QtCore.QObject.connect(self.trayIcon, QtCore.SIGNAL(traySignal), self.__icon_activated) + menu = QtGui.QMenu() + self.exitAction = menu.addAction("Quit", self.quit) + self.trayIcon.setContextMenu(menu) + #I'm currently under the impression that Mac users have different expectations for the tray icon. They don't necessairly expect it to open the main window when clicked and they still expect a program showing a tray icon to also be in the dock. + if 'darwin' in sys.platform: + self.trayIcon.show()""" - self.change_translation() + self.ui.labelSendBroadcastWarning.setVisible(False) - # e.g. for editing labels - self.recurDepth = 0 + #FILE MENU and other buttons + QtCore.QObject.connect(self.ui.actionExit, QtCore.SIGNAL("triggered()"), self.quit) + QtCore.QObject.connect(self.ui.actionManageKeys, QtCore.SIGNAL("triggered()"), self.click_actionManageKeys) + QtCore.QObject.connect(self.ui.actionDeleteAllTrashedMessages, QtCore.SIGNAL("triggered()"), self.click_actionDeleteAllTrashedMessages) + QtCore.QObject.connect(self.ui.actionRegenerateDeterministicAddresses, QtCore.SIGNAL("triggered()"), self.click_actionRegenerateDeterministicAddresses) + QtCore.QObject.connect(self.ui.pushButtonNewAddress, QtCore.SIGNAL("clicked()"), self.click_NewAddressDialog) + QtCore.QObject.connect(self.ui.comboBoxSendFrom, QtCore.SIGNAL("activated(int)"),self.redrawLabelFrom) + QtCore.QObject.connect(self.ui.pushButtonAddAddressBook, QtCore.SIGNAL("clicked()"), self.click_pushButtonAddAddressBook) + QtCore.QObject.connect(self.ui.pushButtonAddSubscription, QtCore.SIGNAL("clicked()"), self.click_pushButtonAddSubscription) + QtCore.QObject.connect(self.ui.pushButtonAddBlacklist, QtCore.SIGNAL("clicked()"), self.click_pushButtonAddBlacklist) + QtCore.QObject.connect(self.ui.pushButtonSend, QtCore.SIGNAL("clicked()"), self.click_pushButtonSend) + QtCore.QObject.connect(self.ui.pushButtonLoadFromAddressBook, QtCore.SIGNAL("clicked()"), self.click_pushButtonLoadFromAddressBook) + QtCore.QObject.connect(self.ui.radioButtonBlacklist, QtCore.SIGNAL("clicked()"), self.click_radioButtonBlacklist) + QtCore.QObject.connect(self.ui.radioButtonWhitelist, QtCore.SIGNAL("clicked()"), self.click_radioButtonWhitelist) + QtCore.QObject.connect(self.ui.pushButtonStatusIcon, QtCore.SIGNAL("clicked()"), self.click_pushButtonStatusIcon) + QtCore.QObject.connect(self.ui.actionSettings, QtCore.SIGNAL("triggered()"), self.click_actionSettings) + QtCore.QObject.connect(self.ui.actionAbout, QtCore.SIGNAL("triggered()"), self.click_actionAbout) + QtCore.QObject.connect(self.ui.actionHelp, QtCore.SIGNAL("triggered()"), self.click_actionHelp) - # switch back to this when replying - self.replyFromTab = None + #Popup menu for the Inbox tab + self.ui.inboxContextMenuToolbar = QtGui.QToolBar() + # Actions + self.actionReply = self.ui.inboxContextMenuToolbar.addAction("Reply", self.on_action_InboxReply) + self.actionAddSenderToAddressBook = self.ui.inboxContextMenuToolbar.addAction("Add sender to your Address Book", self.on_action_InboxAddSenderToAddressBook) + self.actionTrashInboxMessage = self.ui.inboxContextMenuToolbar.addAction("Move to Trash", self.on_action_InboxTrash) + self.actionForceHtml = self.ui.inboxContextMenuToolbar.addAction("View HTML code as formatted text", self.on_action_InboxMessageForceHtml) + self.ui.tableWidgetInbox.setContextMenuPolicy( QtCore.Qt.CustomContextMenu ) + self.connect(self.ui.tableWidgetInbox, QtCore.SIGNAL('customContextMenuRequested(const QPoint&)'), self.on_context_menuInbox) + self.popMenuInbox = QtGui.QMenu( self ) + self.popMenuInbox.addAction( self.actionForceHtml ) + self.popMenuInbox.addSeparator() + self.popMenuInbox.addAction( self.actionReply ) + self.popMenuInbox.addAction( self.actionAddSenderToAddressBook ) + self.popMenuInbox.addSeparator() + self.popMenuInbox.addAction( self.actionTrashInboxMessage ) - # 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() - self.init_addressbook_popup_menu() - self.init_subscriptions_popup_menu() - self.init_chan_popup_menu() - self.init_sent_popup_menu() + #Popup menu for the Your Identities tab + self.ui.addressContextMenuToolbar = QtGui.QToolBar() + # Actions + self.actionNew = self.ui.addressContextMenuToolbar.addAction("New", self.on_action_YourIdentitiesNew) + self.actionEnable = self.ui.addressContextMenuToolbar.addAction("Enable", self.on_action_YourIdentitiesEnable) + self.actionDisable = self.ui.addressContextMenuToolbar.addAction("Disable", self.on_action_YourIdentitiesDisable) + self.actionClipboard = self.ui.addressContextMenuToolbar.addAction("Copy address to clipboard", self.on_action_YourIdentitiesClipboard) + self.actionSpecialAddressBehavior = self.ui.addressContextMenuToolbar.addAction("Special address behavior...", self.on_action_SpecialAddressBehaviorDialog) + self.ui.tableWidgetYourIdentities.setContextMenuPolicy( QtCore.Qt.CustomContextMenu ) + self.connect(self.ui.tableWidgetYourIdentities, QtCore.SIGNAL('customContextMenuRequested(const QPoint&)'), self.on_context_menuYourIdentities) + self.popMenu = QtGui.QMenu( self ) + self.popMenu.addAction( self.actionNew ) + self.popMenu.addSeparator() + self.popMenu.addAction( self.actionClipboard ) + self.popMenu.addSeparator() + self.popMenu.addAction( self.actionEnable ) + self.popMenu.addAction( self.actionDisable ) + self.popMenu.addAction( self.actionSpecialAddressBehavior ) - # Initialize the user's list of addresses on the 'Chan' tab. - self.rerenderTabTreeChans() + #Popup menu for the Address Book page + self.ui.addressBookContextMenuToolbar = QtGui.QToolBar() + # Actions + self.actionAddressBookSend = self.ui.addressBookContextMenuToolbar.addAction("Send message to this address", self.on_action_AddressBookSend) + self.actionAddressBookClipboard = self.ui.addressBookContextMenuToolbar.addAction("Copy address to clipboard", self.on_action_AddressBookClipboard) + self.actionAddressBookNew = self.ui.addressBookContextMenuToolbar.addAction("Add New Address", self.on_action_AddressBookNew) + self.actionAddressBookDelete = self.ui.addressBookContextMenuToolbar.addAction("Delete", self.on_action_AddressBookDelete) + self.ui.tableWidgetAddressBook.setContextMenuPolicy( QtCore.Qt.CustomContextMenu ) + self.connect(self.ui.tableWidgetAddressBook, QtCore.SIGNAL('customContextMenuRequested(const QPoint&)'), self.on_context_menuAddressBook) + self.popMenuAddressBook = QtGui.QMenu( self ) + self.popMenuAddressBook.addAction( self.actionAddressBookSend ) + self.popMenuAddressBook.addAction( self.actionAddressBookClipboard ) + self.popMenuAddressBook.addSeparator() + self.popMenuAddressBook.addAction( self.actionAddressBookNew ) + self.popMenuAddressBook.addAction( self.actionAddressBookDelete ) - # Initialize the user's list of addresses on the 'Messages' tab. - self.rerenderTabTreeMessages() + #Popup menu for the Subscriptions page + self.ui.subscriptionsContextMenuToolbar = QtGui.QToolBar() + # Actions + self.actionsubscriptionsNew = self.ui.subscriptionsContextMenuToolbar.addAction("New", self.on_action_SubscriptionsNew) + self.actionsubscriptionsDelete = self.ui.subscriptionsContextMenuToolbar.addAction("Delete", self.on_action_SubscriptionsDelete) + self.actionsubscriptionsClipboard = self.ui.subscriptionsContextMenuToolbar.addAction("Copy address to clipboard", self.on_action_SubscriptionsClipboard) + self.actionsubscriptionsEnable = self.ui.subscriptionsContextMenuToolbar.addAction("Enable", self.on_action_SubscriptionsEnable) + self.actionsubscriptionsDisable = self.ui.subscriptionsContextMenuToolbar.addAction("Disable", self.on_action_SubscriptionsDisable) + self.ui.tableWidgetSubscriptions.setContextMenuPolicy( QtCore.Qt.CustomContextMenu ) + self.connect(self.ui.tableWidgetSubscriptions, QtCore.SIGNAL('customContextMenuRequested(const QPoint&)'), self.on_context_menuSubscriptions) + self.popMenuSubscriptions = QtGui.QMenu( self ) + self.popMenuSubscriptions.addAction( self.actionsubscriptionsNew ) + self.popMenuSubscriptions.addAction( self.actionsubscriptionsDelete ) + self.popMenuSubscriptions.addSeparator() + self.popMenuSubscriptions.addAction( self.actionsubscriptionsEnable ) + self.popMenuSubscriptions.addAction( self.actionsubscriptionsDisable ) + self.popMenuSubscriptions.addSeparator() + self.popMenuSubscriptions.addAction( self.actionsubscriptionsClipboard ) - # Set welcome message - self.ui.textEditInboxMessage.setText(_translate("MainWindow", """ - Welcome to easy and secure Bitmessage - * send messages to other people - * send broadcast messages like twitter or - * discuss in chan(nel)s with other people - """)) + #Popup menu for the Sent page + self.ui.sentContextMenuToolbar = QtGui.QToolBar() + # Actions + self.actionTrashSentMessage = self.ui.sentContextMenuToolbar.addAction("Move to Trash", self.on_action_SentTrash) + self.actionSentClipboard = self.ui.sentContextMenuToolbar.addAction("Copy destination address to clipboard", self.on_action_SentClipboard) + self.ui.tableWidgetSent.setContextMenuPolicy( QtCore.Qt.CustomContextMenu ) + self.connect(self.ui.tableWidgetSent, QtCore.SIGNAL('customContextMenuRequested(const QPoint&)'), self.on_context_menuSent) + self.popMenuSent = QtGui.QMenu( self ) + self.popMenuSent.addAction( self.actionSentClipboard ) + self.popMenuSent.addAction( self.actionTrashSentMessage ) - # Initialize the address book - self.rerenderAddressBook() - # Initialize the Subscriptions + #Popup menu for the Blacklist page + self.ui.blacklistContextMenuToolbar = QtGui.QToolBar() + # Actions + self.actionBlacklistNew = self.ui.blacklistContextMenuToolbar.addAction("Add new entry", self.on_action_BlacklistNew) + self.actionBlacklistDelete = self.ui.blacklistContextMenuToolbar.addAction("Delete", self.on_action_BlacklistDelete) + self.actionBlacklistClipboard = self.ui.blacklistContextMenuToolbar.addAction("Copy address to clipboard", self.on_action_BlacklistClipboard) + self.actionBlacklistEnable = self.ui.blacklistContextMenuToolbar.addAction("Enable", self.on_action_BlacklistEnable) + self.actionBlacklistDisable = self.ui.blacklistContextMenuToolbar.addAction("Disable", self.on_action_BlacklistDisable) + self.ui.tableWidgetBlacklist.setContextMenuPolicy( QtCore.Qt.CustomContextMenu ) + self.connect(self.ui.tableWidgetBlacklist, QtCore.SIGNAL('customContextMenuRequested(const QPoint&)'), self.on_context_menuBlacklist) + self.popMenuBlacklist = QtGui.QMenu( self ) + #self.popMenuBlacklist.addAction( self.actionBlacklistNew ) + self.popMenuBlacklist.addAction( self.actionBlacklistDelete ) + self.popMenuBlacklist.addSeparator() + self.popMenuBlacklist.addAction( self.actionBlacklistClipboard ) + self.popMenuBlacklist.addSeparator() + self.popMenuBlacklist.addAction( self.actionBlacklistEnable ) + self.popMenuBlacklist.addAction( self.actionBlacklistDisable ) + + #Initialize the user's list of addresses on the 'Your Identities' tab. + configSections = shared.config.sections() + for addressInKeysFile in configSections: + if addressInKeysFile <> 'bitmessagesettings': + isEnabled = shared.config.getboolean(addressInKeysFile, 'enabled') + newItem = QtGui.QTableWidgetItem(unicode(shared.config.get(addressInKeysFile, 'label'),'utf-8)')) + if not isEnabled: + newItem.setTextColor(QtGui.QColor(128,128,128)) + self.ui.tableWidgetYourIdentities.insertRow(0) + self.ui.tableWidgetYourIdentities.setItem(0, 0, newItem) + newItem = QtGui.QTableWidgetItem(addressInKeysFile) + newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled ) + if not isEnabled: + newItem.setTextColor(QtGui.QColor(128,128,128)) + if shared.safeConfigGetBoolean(addressInKeysFile,'mailinglist'): + newItem.setTextColor(QtGui.QColor(137,04,177))#magenta + self.ui.tableWidgetYourIdentities.setItem(0, 1, newItem) + newItem = QtGui.QTableWidgetItem(str(addressStream(addressInKeysFile))) + newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled ) + if not isEnabled: + newItem.setTextColor(QtGui.QColor(128,128,128)) + self.ui.tableWidgetYourIdentities.setItem(0, 2, newItem) + if isEnabled: + status,addressVersionNumber,streamNumber,hash = decodeAddress(addressInKeysFile) + + self.ui.tableWidgetSent.keyPressEvent = self.tableWidgetSentKeyPressEvent + font = QFont() + font.setBold(True) + #Load inbox from messages database file + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''SELECT msgid, toaddress, fromaddress, subject, received, message, read FROM inbox where folder='inbox' ORDER BY received''') + shared.sqlSubmitQueue.put('') + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + for row in queryreturn: + msgid, toAddress, fromAddress, subject, received, message, read = row + + try: + if toAddress == self.str_broadcast_subscribers: + toLabel = self.str_broadcast_subscribers + else: + toLabel = shared.config.get(toAddress, 'label') + except: + toLabel = '' + if toLabel == '': + toLabel = toAddress + + fromLabel = '' + t = (fromAddress,) + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''select label from addressbook where address=?''') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + + if queryreturn <> []: + for row in queryreturn: + fromLabel, = row + + if fromLabel == '': #If this address wasn't in our address book.. + t = (fromAddress,) + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''select label from subscriptions where address=?''') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + + if queryreturn <> []: + for row in queryreturn: + fromLabel, = row + + self.ui.tableWidgetInbox.insertRow(0) + newItem = QtGui.QTableWidgetItem(unicode(toLabel,'utf-8')) + newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled ) + if not read: + newItem.setFont(font) + newItem.setData(Qt.UserRole,str(toAddress)) + if shared.safeConfigGetBoolean(toAddress,'mailinglist'): + newItem.setTextColor(QtGui.QColor(137,04,177)) + self.ui.tableWidgetInbox.setItem(0,0,newItem) + if fromLabel == '': + newItem = QtGui.QTableWidgetItem(unicode(fromAddress,'utf-8')) + else: + newItem = QtGui.QTableWidgetItem(unicode(fromLabel,'utf-8')) + newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled ) + if not read: + newItem.setFont(font) + newItem.setData(Qt.UserRole,str(fromAddress)) + + self.ui.tableWidgetInbox.setItem(0,1,newItem) + newItem = QtGui.QTableWidgetItem(unicode(subject,'utf-8')) + newItem.setData(Qt.UserRole,unicode(message,'utf-8)')) + newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled ) + if not read: + newItem.setFont(font) + self.ui.tableWidgetInbox.setItem(0,2,newItem) + newItem = myTableWidgetItem(unicode(strftime(shared.config.get('bitmessagesettings', 'timeformat'),localtime(int(received))),'utf-8')) + newItem.setData(Qt.UserRole,QByteArray(msgid)) + newItem.setData(33,int(received)) + newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled ) + if not read: + newItem.setFont(font) + self.ui.tableWidgetInbox.setItem(0,3,newItem) + self.ui.tableWidgetInbox.sortItems(3,Qt.DescendingOrder) + + self.ui.tableWidgetInbox.keyPressEvent = self.tableWidgetInboxKeyPressEvent + #Load Sent items from database + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''SELECT toaddress, fromaddress, subject, message, status, ackdata, lastactiontime FROM sent where folder = 'sent' ORDER BY lastactiontime''') + shared.sqlSubmitQueue.put('') + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + for row in queryreturn: + toAddress, fromAddress, subject, message, status, ackdata, lastactiontime = row + try: + fromLabel = shared.config.get(fromAddress, 'label') + except: + fromLabel = '' + if fromLabel == '': + fromLabel = fromAddress + + toLabel = '' + t = (toAddress,) + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''select label from addressbook where address=?''') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + + if queryreturn <> []: + for row in queryreturn: + toLabel, = row + + self.ui.tableWidgetSent.insertRow(0) + if toLabel == '': + newItem = QtGui.QTableWidgetItem(unicode(toAddress,'utf-8')) + else: + newItem = QtGui.QTableWidgetItem(unicode(toLabel,'utf-8')) + newItem.setData(Qt.UserRole,str(toAddress)) + newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled ) + self.ui.tableWidgetSent.setItem(0,0,newItem) + if fromLabel == '': + newItem = QtGui.QTableWidgetItem(unicode(fromAddress,'utf-8')) + else: + newItem = QtGui.QTableWidgetItem(unicode(fromLabel,'utf-8')) + newItem.setData(Qt.UserRole,str(fromAddress)) + newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled ) + self.ui.tableWidgetSent.setItem(0,1,newItem) + newItem = QtGui.QTableWidgetItem(unicode(subject,'utf-8')) + newItem.setData(Qt.UserRole,unicode(message,'utf-8)')) + newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled ) + self.ui.tableWidgetSent.setItem(0,2,newItem) + if status == 'findingpubkey': + newItem = myTableWidgetItem('Waiting on their public key. Will request it again soon.') + elif status == 'sentmessage': + newItem = myTableWidgetItem('Message sent. Waiting on acknowledgement. Sent at ' + unicode(strftime(shared.config.get('bitmessagesettings', 'timeformat'),localtime(lastactiontime)),'utf-8')) + elif status == 'doingpow': + newItem = myTableWidgetItem('Need to do work to send message. Work is queued.') + elif status == 'ackreceived': + newItem = myTableWidgetItem('Acknowledgement of the message received ' + unicode(strftime(shared.config.get('bitmessagesettings', 'timeformat'),localtime(int(lastactiontime))),'utf-8')) + elif status == 'broadcastpending': + newItem = myTableWidgetItem('Doing the work necessary to send broadcast...') + elif status == 'broadcastsent': + newItem = myTableWidgetItem('Broadcast on ' + unicode(strftime(shared.config.get('bitmessagesettings', 'timeformat'),localtime(int(lastactiontime))),'utf-8')) + else: + newItem = myTableWidgetItem('Unknown status. ' + unicode(strftime(shared.config.get('bitmessagesettings', 'timeformat'),localtime(int(lastactiontime))),'utf-8')) + newItem.setData(Qt.UserRole,QByteArray(ackdata)) + newItem.setData(33,int(lastactiontime)) + newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled ) + self.ui.tableWidgetSent.setItem(0,3,newItem) + self.ui.tableWidgetSent.sortItems(3,Qt.DescendingOrder) + + #Initialize the address book + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('SELECT * FROM addressbook') + shared.sqlSubmitQueue.put('') + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + for row in queryreturn: + label, address = row + self.ui.tableWidgetAddressBook.insertRow(0) + newItem = QtGui.QTableWidgetItem(unicode(label,'utf-8')) + self.ui.tableWidgetAddressBook.setItem(0,0,newItem) + newItem = QtGui.QTableWidgetItem(address) + newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled ) + self.ui.tableWidgetAddressBook.setItem(0,1,newItem) + + #Initialize the Subscriptions self.rerenderSubscriptions() - # Initialize the inbox search - QtCore.QObject.connect(self.ui.inboxSearchLineEdit, QtCore.SIGNAL( - "returnPressed()"), self.inboxSearchLineEditReturnPressed) - QtCore.QObject.connect(self.ui.inboxSearchLineEditSubscriptions, QtCore.SIGNAL( - "returnPressed()"), self.inboxSearchLineEditReturnPressed) - QtCore.QObject.connect(self.ui.inboxSearchLineEditChans, QtCore.SIGNAL( - "returnPressed()"), self.inboxSearchLineEditReturnPressed) - QtCore.QObject.connect(self.ui.inboxSearchLineEdit, QtCore.SIGNAL( - "textChanged(QString)"), self.inboxSearchLineEditUpdated) - QtCore.QObject.connect(self.ui.inboxSearchLineEditSubscriptions, QtCore.SIGNAL( - "textChanged(QString)"), self.inboxSearchLineEditUpdated) - QtCore.QObject.connect(self.ui.inboxSearchLineEditChans, QtCore.SIGNAL( - "textChanged(QString)"), self.inboxSearchLineEditUpdated) + #Initialize the Blacklist or Whitelist + if shared.config.get('bitmessagesettings', 'blackwhitelist') == 'black': + self.loadBlackWhiteList() + else: + self.ui.tabWidget.setTabText(6,'Whitelist') + self.ui.radioButtonWhitelist.click() + self.loadBlackWhiteList() - # Initialize addressbook - QtCore.QObject.connect(self.ui.tableWidgetAddressBook, QtCore.SIGNAL( - "itemChanged(QTableWidgetItem *)"), self.tableWidgetAddressBookItemChanged) - # This is necessary for the completer to work if multiple recipients - QtCore.QObject.connect(self.ui.lineEditTo, QtCore.SIGNAL( - "cursorPositionChanged(int, int)"), self.ui.lineEditTo.completer().onCursorPositionChanged) - # show messages from message list - QtCore.QObject.connect(self.ui.tableWidgetInbox, QtCore.SIGNAL( - "itemSelectionChanged ()"), self.tableWidgetInboxItemClicked) - QtCore.QObject.connect(self.ui.tableWidgetInboxSubscriptions, QtCore.SIGNAL( - "itemSelectionChanged ()"), self.tableWidgetInboxItemClicked) - QtCore.QObject.connect(self.ui.tableWidgetInboxChans, QtCore.SIGNAL( - "itemSelectionChanged ()"), self.tableWidgetInboxItemClicked) + QtCore.QObject.connect(self.ui.tableWidgetYourIdentities, QtCore.SIGNAL("itemChanged(QTableWidgetItem *)"), self.tableWidgetYourIdentitiesItemChanged) + QtCore.QObject.connect(self.ui.tableWidgetAddressBook, QtCore.SIGNAL("itemChanged(QTableWidgetItem *)"), self.tableWidgetAddressBookItemChanged) + QtCore.QObject.connect(self.ui.tableWidgetSubscriptions, QtCore.SIGNAL("itemChanged(QTableWidgetItem *)"), self.tableWidgetSubscriptionsItemChanged) + QtCore.QObject.connect(self.ui.tableWidgetInbox, QtCore.SIGNAL("itemSelectionChanged ()"), self.tableWidgetInboxItemClicked) + QtCore.QObject.connect(self.ui.tableWidgetSent, QtCore.SIGNAL("itemSelectionChanged ()"), self.tableWidgetSentItemClicked) - # tree address lists - QtCore.QObject.connect(self.ui.treeWidgetYourIdentities, QtCore.SIGNAL( - "itemSelectionChanged ()"), self.treeWidgetItemClicked) - QtCore.QObject.connect(self.ui.treeWidgetYourIdentities, QtCore.SIGNAL( - "itemChanged (QTreeWidgetItem *, int)"), self.treeWidgetItemChanged) - QtCore.QObject.connect(self.ui.treeWidgetSubscriptions, QtCore.SIGNAL( - "itemSelectionChanged ()"), self.treeWidgetItemClicked) - QtCore.QObject.connect(self.ui.treeWidgetSubscriptions, QtCore.SIGNAL( - "itemChanged (QTreeWidgetItem *, int)"), self.treeWidgetItemChanged) - QtCore.QObject.connect(self.ui.treeWidgetChans, QtCore.SIGNAL( - "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()) + #Put the colored icon on the status bar + #self.ui.pushButtonStatusIcon.setIcon(QIcon(":/newPrefix/images/yellowicon.png")) self.statusbar = self.statusBar() + self.statusbar.insertPermanentWidget(0,self.ui.pushButtonStatusIcon) + self.ui.labelStartupTime.setText('Since startup on ' + unicode(strftime(shared.config.get('bitmessagesettings', 'timeformat'),localtime(int(time.time()))),'utf-8')) + self.numberOfMessagesProcessed = 0 + self.numberOfBroadcastsProcessed = 0 + self.numberOfPubkeysProcessed = 0 - self.pushButtonStatusIcon = QtGui.QPushButton(self) - self.pushButtonStatusIcon.setText('') - self.pushButtonStatusIcon.setIcon( - QtGui.QIcon(':/newPrefix/images/redicon.png')) - self.pushButtonStatusIcon.setFlat(True) - self.statusbar.insertPermanentWidget(0, self.pushButtonStatusIcon) - QtCore.QObject.connect(self.pushButtonStatusIcon, QtCore.SIGNAL( - "clicked()"), self.click_pushButtonStatusIcon) - - self.unreadCount = 0 - - # Set the icon sizes for the identicons - 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( - "updateStatusBar(PyQt_PyObject)"), self.updateStatusBar) - QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( - "updateSentItemStatusByToAddress(PyQt_PyObject,PyQt_PyObject)"), self.updateSentItemStatusByToAddress) - 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) - QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( - "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( - "changedInboxUnread(PyQt_PyObject)"), self.changedInboxUnread) - QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( - "rerenderMessagelistFromLabels()"), self.rerenderMessagelistFromLabels) - QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( - "rerenderMessgelistToLabels()"), self.rerenderMessagelistToLabels) - QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( - "rerenderAddressBook()"), self.rerenderAddressBook) - QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( - "rerenderSubscriptions()"), self.rerenderSubscriptions) - QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( - "removeInboxRowByMsgid(PyQt_PyObject)"), self.removeInboxRowByMsgid) - QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( - "newVersionAvailable(PyQt_PyObject)"), self.newVersionAvailable) - QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( - "displayAlert(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), self.displayAlert) + self.UISignalThread = UISignaler() + QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL("writeNewAddressToTable(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), self.writeNewAddressToTable) + QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL("updateStatusBar(PyQt_PyObject)"), self.updateStatusBar) + QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL("updateSentItemStatusByHash(PyQt_PyObject,PyQt_PyObject)"), self.updateSentItemStatusByHash) + 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) + QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL("displayNewSentMessage(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), self.displayNewSentMessage) + QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL("updateNetworkStatusTab()"), self.updateNetworkStatusTab) + QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL("incrementNumberOfMessagesProcessed()"), self.incrementNumberOfMessagesProcessed) + QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL("incrementNumberOfPubkeysProcessed()"), self.incrementNumberOfPubkeysProcessed) + QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL("incrementNumberOfBroadcastsProcessed()"), self.incrementNumberOfBroadcastsProcessed) + QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL("setStatusIcon(PyQt_PyObject)"), self.setStatusIcon) + QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL("rerenderInboxFromLabels()"), self.rerenderInboxFromLabels) + QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL("rerenderSubscriptions()"), self.rerenderSubscriptions) self.UISignalThread.start() - # Key press in tree view - self.ui.treeWidgetYourIdentities.keyPressEvent = self.treeWidgetKeyPressEvent - 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 - self.ui.tableWidgetInboxChans.keyPressEvent = self.tableWidgetKeyPressEvent - - # Key press in messageview - self.ui.textEditInboxMessage.keyPressEvent = self.textEditKeyPressEvent - self.ui.textEditInboxMessageSubscriptions.keyPressEvent = self.textEditKeyPressEvent - self.ui.textEditInboxMessageChans.keyPressEvent = self.textEditKeyPressEvent - - # Below this point, it would be good if all of the necessary global data - # structures were initialized. +#Below this point, it would be good if all of the necessary global data structures were initialized. self.rerenderComboBoxSendFrom() - self.rerenderComboBoxSendFromBroadcast() - # Put the TTL slider in the correct spot - TTL = 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""" - try: - return self._contact_selected - except AttributeError: - pass - finally: - self._contact_selected = None - - def updateStartOnLogon(self): - """ - Configure Bitmessage to start on startup (or remove the - configuration) based on the setting in the keys.dat file - """ - startonlogon = config.safeGetBoolean( - 'bitmessagesettings', 'startonlogon') - if sys.platform.startswith('win'): # Auto-startup for Windows - RUN_PATH = "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run" - settings = QtCore.QSettings( - RUN_PATH, QtCore.QSettings.NativeFormat) - # In case the user moves the program and the registry entry is - # no longer valid, this will delete the old registry entry. - if startonlogon: - settings.setValue("PyBitmessage", sys.argv[0]) - else: - settings.remove("PyBitmessage") - else: - try: # get desktop plugin if any - self.desktop = get_plugin('desktop')() - self.desktop.adjust_startonlogon(startonlogon) - except (NameError, TypeError): - self.desktop = False - - def updateTTL(self, sliderPosition): - TTL = int(sliderPosition ** 3.199 + 3600) - self.updateHumanFriendlyTTLDescription(TTL) - config.set('bitmessagesettings', 'ttl', str(TTL)) - config.save() - - 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) - 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) - - # Show or hide the application window after clicking an item within the - # tray icon or, on Windows, the try icon itself. + #Show or hide the application window after clicking an item within the tray icon or, on Windows, the try icon itself. def appIndicatorShowOrHideWindow(self): if not self.actionShow.isChecked(): self.hide() else: + if sys.platform[0:3] == 'win': + self.setWindowFlags(Qt.Window) + #else: + #self.showMaximized() self.show() - self.setWindowState( - self.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) - self.raise_() + self.setWindowState(self.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) 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: + if self.actionShow == None: return if not self.actionShow.isChecked(): self.actionShow.setChecked(True) - self.appIndicatorShowOrHideWindow() + self.appIndicatorShowOrHideWindow() # unchecks the show item on the application indicator def appIndicatorHide(self): - if self.actionShow is None: + if self.actionShow == None: return if self.actionShow.isChecked(): 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): @@ -964,3347 +492,1826 @@ 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: - self.ui.tableWidgetInbox.setCurrentCell(0, 0) + # 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(4) - # Show the program window and select channels tab - def appIndicatorChannel(self): + # Show the program window and select the address book tab + def appIndicatorAddressBook(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') - totalUnread = {} - normalUnread = {} - 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 treeWidget in widgets: - root = treeWidget.invisibleRootItem() - for i in range(root.childCount()): - addressItem = root.child(i) - 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 - if newCount != addressItem.unreadCount: - addressItem.setUnreadCount(newCount) - for j in range(addressItem.childCount()): - folderItem = addressItem.child(j) - 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 newCount != folderItem.unreadCount: - folderItem.setUnreadCount(newCount) - - def addMessageListItem(self, tableWidget, items): - sortingEnabled = tableWidget.isSortingEnabled() - if sortingEnabled: - tableWidget.setSortingEnabled(False) - tableWidget.insertRow(0) - for i, item in enumerate(items): - tableWidget.setItem(0, i, item) - 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) - 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) - - return acct - - # Load Sent items from database - def loadSent(self, tableWidget, account, where="", what=""): - if tableWidget == self.ui.tableWidgetInboxSubscriptions: - tableWidget.setColumnHidden(0, True) - tableWidget.setColumnHidden(1, False) - xAddress = 'toaddress' - elif tableWidget == self.ui.tableWidgetInboxChans: - tableWidget.setColumnHidden(0, False) - tableWidget.setColumnHidden(1, True) - xAddress = 'both' - else: - tableWidget.setColumnHidden(0, False) - tableWidget.setColumnHidden(1, bool(account)) - 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) - - 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: - tableWidget.setColumnHidden(0, True) - tableWidget.setColumnHidden(1, False) - else: - tableWidget.setColumnHidden(0, False) - tableWidget.setColumnHidden(1, False) - - 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) - - tableWidget.horizontalHeader().setSortIndicator( - 3, QtCore.Qt.DescendingOrder) - tableWidget.setSortingEnabled(True) - tableWidget.selectRow(0) - tableWidget.horizontalHeaderItem(3).setText( - _translate("MainWindow", "Received")) - tableWidget.setUpdatesEnabled(True) + self.ui.tabWidget.setCurrentIndex(5) # create application indicator - def appIndicatorInit(self, app): - self.initTrayIcon("can-icon-24px-red.png", app) - traySignal = "activated(QSystemTrayIcon::ActivationReason)" - QtCore.QObject.connect(self.tray, QtCore.SIGNAL( - traySignal), self.__icon_activated) + def appIndicatorInit(self,app): + self.tray = QSystemTrayIcon(QtGui.QIcon("images/can-icon-24px-red.png"), app) + if sys.platform[0:3] == 'win': + traySignal = "activated(QSystemTrayIcon::ActivationReason)" + 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) + self.actionStatus = QtGui.QAction('Not Connected',m,checkable=False) m.addAction(self.actionStatus) # separator - actionSeparator = QtGui.QAction('', m, checkable=False) + actionSeparator = QtGui.QAction('',m,checkable=False) actionSeparator.setSeparator(True) m.addAction(actionSeparator) # show bitmessage - self.actionShow = QtGui.QAction(_translate( - "MainWindow", "Show Bitmessage"), m, checkable=True) - self.actionShow.setChecked(not config.getboolean( - 'bitmessagesettings', 'startintray')) + self.actionShow = QtGui.QAction('Show Bitmessage',m,checkable=True) + 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) + m.addAction(self.actionShow) # Send - actionSend = QtGui.QAction(_translate( - "MainWindow", "Send"), m, checkable=False) + actionSend = QtGui.QAction('Send',m,checkable=False) actionSend.triggered.connect(self.appIndicatorSend) m.addAction(actionSend) # Subscribe - actionSubscribe = QtGui.QAction(_translate( - "MainWindow", "Subscribe"), m, checkable=False) + actionSubscribe = QtGui.QAction('Subscribe',m,checkable=False) actionSubscribe.triggered.connect(self.appIndicatorSubscribe) m.addAction(actionSubscribe) - # Channels - actionSubscribe = QtGui.QAction(_translate( - "MainWindow", "Channel"), m, checkable=False) - actionSubscribe.triggered.connect(self.appIndicatorChannel) - m.addAction(actionSubscribe) + # Address book + actionAddressBook = QtGui.QAction('Address Book',m,checkable=False) + actionAddressBook.triggered.connect(self.appIndicatorAddressBook) + m.addAction(actionAddressBook) # separator - actionSeparator = QtGui.QAction('', m, checkable=False) + actionSeparator = QtGui.QAction('',m,checkable=False) actionSeparator.setSeparator(True) m.addAction(actionSeparator) # Quit - m.addAction(_translate( - "MainWindow", "Quit"), self.quit) + m.addAction("Quit", self.quit) self.tray.setContextMenu(m) self.tray.show() - # returns the number of unread messages and subscriptions - def getUnread(self): - counters = [0, 0] + # Ubuntu Messaging menu object + mmapp = None - queryreturn = sqlQuery(''' - SELECT msgid, toaddress, read FROM inbox where folder='inbox' - ''') - for msgid, toAddress, read in queryreturn: + # is the operating system Ubuntu? + def isUbuntu(self): + for entry in platform.uname(): + if "Ubuntu" in entry: + return True + return False - if not read: - # increment the unread subscriptions if True (1) - # else messages (0) - counters[toAddress == str_broadcast_subscribers] += 1 + # When an unread inbox row is selected on then clear the messaging menu + def ubuntuMessagingMenuClear(self, inventoryHash): + global withMessagingMenu - return counters - - # 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 - - # if the address had a known label in the address book - if label: - # 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 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' - # the sound is for an unknown address - elif category is sound.SOUND_UNKNOWN: - soundFilename = state.appdata + 'sounds/unknown' - # initial connection sound - elif category is sound.SOUND_CONNECTED: - soundFilename = state.appdata + 'sounds/connected' - # disconnected sound - elif category is sound.SOUND_DISCONNECTED: - soundFilename = state.appdata + 'sounds/disconnected' - # sound when the connection status becomes green - elif category is sound.SOUND_CONNECTION_GREEN: - soundFilename = state.appdata + 'sounds/green' - - if soundFilename is None: - logger.warning("Probably wrong category number in playSound()") + # if this isn't ubuntu then don't do anything + if not self.isUbuntu(): return - if not sound.is_connection_sound(category): - # record the last time that a received message sound was played - self.lastSoundTime = datetime.now() + # has messageing menu been installed + if not withMessagingMenu: + return - 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 + # 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 - soundFilename += ext + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''SELECT toaddress, read FROM inbox WHERE msgid=?''') + shared.sqlSubmitQueue.put(inventoryHash) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + for row in queryreturn: + toAddress, read = row + if not read: + if toAddress == self.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") - self._player(soundFilename) + # returns the number of unread messages and subscriptions + def getUnread(self): + unreadMessages = 0 + unreadSubscriptions = 0 - # Adapters and converters for QT <-> sqlite - def sqlInit(self): - register_adapter(QtCore.QByteArray, str) + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''SELECT msgid, toaddress, read FROM inbox where folder='inbox' ''') + shared.sqlSubmitQueue.put('') + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + for row in queryreturn: + msgid, toAddress, read = row - def indicatorInit(self): - """ - Try init the distro specific appindicator, - for example the Ubuntu MessagingMenu - """ - def _noop_update(*args, **kwargs): - pass + try: + if toAddress == self.str_broadcast_subscribers: + toLabel = self.str_broadcast_subscribers + else: + toLabel = shared.config.get(toAddress, 'label') + except: + toLabel = '' + if toLabel == '': + toLabel = toAddress - try: - self.indicatorUpdate = get_plugin('indicator')(self) - except (NameError, TypeError): - logger.warning("No indicator plugin found") - self.indicatorUpdate = _noop_update + if not read: + if toLabel == self.str_broadcast_subscribers: + # increment the unread subscriptions + unreadSubscriptions = unreadSubscriptions + 1 + else: + # increment the unread messages + unreadMessages = unreadMessages + 1 + return unreadMessages, unreadSubscriptions + + # 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: + print '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 + print '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: + print 'WARNING: messaging menu disabled or libmessaging-menu-dev not installed' + return + + # remember this item to that the messaging menu can find it + if toLabel == self.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) # initialise the message notifier def notifierInit(self): - def _simple_notify( - title, subtitle, category, label=None, icon=None): + global withMessagingMenu + if withMessagingMenu: + Notify.init('pybitmessage') + + # shows a notification + def notifierShow(self, title, subtitle): + global withMessagingMenu + if withMessagingMenu: + n = Notify.Notification.new(title, subtitle,'notification-message-email') + n.show() + return + else: self.tray.showMessage(title, subtitle, 1, 2000) - self._notifier = _simple_notify - # does nothing if isAvailable returns false - self._player = QtGui.QSound.play - - if not get_plugins: - return - - _plugin = get_plugin('notification.message') - if _plugin: - self._notifier = _plugin - else: - logger.warning("No notification.message plugin found") - - self._theme_player = get_plugin('notification.sound', 'theme') - - if not QtGui.QSound.isAvailable(): - _plugin = get_plugin( - 'notification.sound', 'file', fallback='file.fallback') - if _plugin: - self._player = _plugin - else: - logger.warning("No notification.sound plugin found") - - def notifierShow( - self, title, subtitle, category, label=None, icon=None): - self.playSound(category, label) - self._notifier( - unicode(title), unicode(subtitle), category, label, icon) - - # tree - def treeWidgetKeyPressEvent(self, event): - return self.handleKeyPress(event, self.getCurrentTreeWidget()) - - # addressbook - def addressbookKeyPressEvent(self, event): - """Handle keypress event in addressbook widget""" + def tableWidgetInboxKeyPressEvent(self,event): if event.key() == QtCore.Qt.Key_Delete: - self.on_action_AddressBookDelete() - else: - return QtGui.QTableWidget.keyPressEvent( - self.ui.tableWidgetAddressBook, event) + self.on_action_InboxTrash() + return QtGui.QTableWidget.keyPressEvent(self.ui.tableWidgetInbox, event) - # inbox / sent - def tableWidgetKeyPressEvent(self, event): - return self.handleKeyPress(event, self.getCurrentMessagelist()) - - # messageview - 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""" - messagelist = self.getCurrentMessagelist() + def tableWidgetSentKeyPressEvent(self,event): if event.key() == QtCore.Qt.Key_Delete: - if isinstance(focus, (MessageView, QtGui.QTableWidget)): - folder = self.getCurrentFolder() - if folder == "sent": - self.on_action_SentTrash() - else: - self.on_action_InboxTrash() - event.ignore() - elif QtGui.QApplication.queryKeyboardModifiers() == QtCore.Qt.NoModifier: - if event.key() == QtCore.Qt.Key_N: - currentRow = messagelist.currentRow() - if currentRow < messagelist.rowCount() - 1: - messagelist.selectRow(currentRow + 1) - event.ignore() - elif event.key() == QtCore.Qt.Key_P: - currentRow = messagelist.currentRow() - if currentRow > 0: - messagelist.selectRow(currentRow - 1) - event.ignore() - elif event.key() == QtCore.Qt.Key_R: - if messagelist == self.ui.tableWidgetInboxChans: - self.on_action_InboxReplyChan() - else: - self.on_action_InboxReply() - event.ignore() - elif event.key() == QtCore.Qt.Key_C: - 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.lineEditTo.setFocus() - event.ignore() - elif event.key() == QtCore.Qt.Key_F: - try: - self.getCurrentSearchLine(retObj=True).setFocus() - except AttributeError: - pass - event.ignore() - if not event.isAccepted(): - return - if isinstance(focus, MessageView): - return MessageView.keyPressEvent(focus, event) - if isinstance(focus, QtGui.QTableWidget): - return QtGui.QTableWidget.keyPressEvent(focus, event) - if isinstance(focus, QtGui.QTreeWidget): - return QtGui.QTreeWidget.keyPressEvent(focus, event) + self.on_action_SentTrash() + return QtGui.QTableWidget.keyPressEvent(self.ui.tableWidgetSent, event) - # menu button 'manage keys' def click_actionManageKeys(self): if 'darwin' in sys.platform or 'linux' in sys.platform: - if state.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) - + 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) 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?','You may manage your keys by editing the keys.dat file stored in\n' + shared.appdata + '\nIt is important that you back up this file.', 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, '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.)', 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, 'Open keys.dat?','You may manage your keys by editing the keys.dat file stored in\n' + shared.appdata + '\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.)', QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) if reply == QtGui.QMessageBox.Yes: - openKeysFile() + self.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, 'Delete trash?',"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") - elif self.getCurrentFolder(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") + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''delete from inbox where folder='trash' ''') + shared.sqlSubmitQueue.put('') + shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('''delete from sent where folder='trash' ''') + shared.sqlSubmitQueue.put('') + shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') #Commit takes no parameters and returns nothing + shared.sqlSubmitQueue.put('vacuum') + shared.sqlSubmitQueue.put('') + shared.sqlReturnQueue.get() + shared.sqlLock.release() - # menu button '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." - )) - return - streamNumberForAddress = int(dialog.lineEditStreamNumber.text()) - try: - addressVersionNumber = int( - dialog.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." - )) - 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." - )) - 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) - ) - - # opens 'join chan' dialog - def click_actionJoinChan(self): - dialogs.NewChanDialog(self) - - 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.regenerateAddressesDialogInstance = regenerateAddressesDialog(self) + if self.regenerateAddressesDialogInstance.exec_(): + if self.regenerateAddressesDialogInstance.ui.lineEditPassphrase.text() == "": + QMessageBox.about(self, "bad passphrase", "You must type your passphrase. If you don\'t have one then this is not the form for you.") else: - self._firstrun = False + streamNumberForAddress = int(self.regenerateAddressesDialogInstance.ui.lineEditStreamNumber.text()) + addressVersionNumber = int(self.regenerateAddressesDialogInstance.ui.lineEditAddressVersionNumber.text()) + #self.addressGenerator = addressGenerator() + #self.addressGenerator.setup(addressVersionNumber,streamNumberForAddress,"unused address",self.regenerateAddressesDialogInstance.ui.spinBoxNumberOfAddressesToMake.value(),self.regenerateAddressesDialogInstance.ui.lineEditPassphrase.text().toUtf8(),self.regenerateAddressesDialogInstance.ui.checkBoxEighteenByteRipe.isChecked()) + #QtCore.QObject.connect(self.addressGenerator, SIGNAL("writeNewAddressToTable(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), self.writeNewAddressToTable) + #QtCore.QObject.connect(self.addressGenerator, QtCore.SIGNAL("updateStatusBar(PyQt_PyObject)"), self.updateStatusBar) + #self.addressGenerator.start() + 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) - def showMigrationWizard(self, level): - self.migrationWizardInstance = Ui_MigrationWizard(["a"]) - if self.migrationWizardInstance.exec_(): - pass + def openKeysFile(self): + if 'linux' in sys.platform: + subprocess.call(["xdg-open", shared.appdata + 'keys.dat']) else: - pass + os.startfile(shared.appdata + 'keys.dat') def changeEvent(self, event): - if event.type() == QtCore.QEvent.LanguageChange: - self.ui.retranslateUi(self) - self.init_inbox_popup_menu(False) - self.init_identities_popup_menu(False) - self.init_chan_popup_menu(False) - self.init_addressbook_popup_menu(False) - self.init_subscriptions_popup_menu(False) - self.init_sent_popup_menu(False) - 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) - elif event.oldState() & QtCore.Qt.WindowMinimized: - # The window state has just been changed to - # Normal/Maximised/FullScreen - pass + self.actionShow.setChecked(False) + if shared.config.getboolean('bitmessagesettings', 'minimizetotray') and not 'darwin' in sys.platform: + if event.type() == QtCore.QEvent.WindowStateChange: + if self.windowState() & QtCore.Qt.WindowMinimized: + self.appIndicatorHide() + if 'win32' in sys.platform or 'win64' in sys.platform: + self.setWindowFlags(Qt.ToolTip) + elif event.oldState() & QtCore.Qt.WindowMinimized: + #The window state has just been changed to Normal/Maximised/FullScreen + pass + #QtGui.QWidget.changeEvent(self, event) def __icon_activated(self, reason): if reason == QtGui.QSystemTrayIcon.Trigger: self.actionShow.setChecked(not self.actionShow.isChecked()) self.appIndicatorShowOrHideWindow() + """if 'linux' in sys.platform: + self.trayIcon.hide() + self.setWindowFlags(Qt.Window) + self.show() + elif 'win32' in sys.platform or 'win64' in sys.platform: + self.trayIcon.hide() + self.setWindowFlags(Qt.Window) + self.show() + self.setWindowState(self.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) + self.activateWindow() + elif 'darwin' in sys.platform: + #self.trayIcon.hide() #this line causes a segmentation fault + #self.setWindowFlags(Qt.Window) + #self.show() + self.setWindowState(self.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) + self.activateWindow()""" + + def incrementNumberOfMessagesProcessed(self): + self.numberOfMessagesProcessed += 1 + self.ui.labelMessageCount.setText('Processed ' + str(self.numberOfMessagesProcessed) + ' person-to-person messages.') + + def incrementNumberOfBroadcastsProcessed(self): + self.numberOfBroadcastsProcessed += 1 + self.ui.labelBroadcastCount.setText('Processed ' + str(self.numberOfBroadcastsProcessed) + ' broadcast messages.') + + def incrementNumberOfPubkeysProcessed(self): + self.numberOfPubkeysProcessed += 1 + self.ui.labelPubkeyCount.setText('Processed ' + str(self.numberOfPubkeysProcessed) + ' public keys.') + + def updateNetworkStatusTab(self): + #print 'updating network status tab' + totalNumberOfConnectionsFromAllStreams = 0 #One would think we could use len(sendDataQueues) for this but the number doesn't always match: just because we have a sendDataThread running doesn't mean that the connection has been fully established (with the exchange of version messages). + streamNumberTotals = {} + for host, streamNumber in shared.connectedHostsList.items(): + if not streamNumber in streamNumberTotals: + streamNumberTotals[streamNumber] = 1 + else: + streamNumberTotals[streamNumber] += 1 + + while self.ui.tableWidgetConnectionCount.rowCount() > 0: + self.ui.tableWidgetConnectionCount.removeRow(0) + for streamNumber, connectionCount in streamNumberTotals.items(): + self.ui.tableWidgetConnectionCount.insertRow(0) + if streamNumber == 0: + newItem = QtGui.QTableWidgetItem("?") + else: + newItem = QtGui.QTableWidgetItem(str(streamNumber)) + newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled ) + self.ui.tableWidgetConnectionCount.setItem(0,0,newItem) + newItem = QtGui.QTableWidgetItem(str(connectionCount)) + newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled ) + self.ui.tableWidgetConnectionCount.setItem(0,1,newItem) + """for currentRow in range(self.ui.tableWidgetConnectionCount.rowCount()): + rowStreamNumber = int(self.ui.tableWidgetConnectionCount.item(currentRow,0).text()) + if streamNumber == rowStreamNumber: + foundTheRowThatNeedsUpdating = True + self.ui.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.ui.tableWidgetConnectionCount.insertRow(0) + newItem = QtGui.QTableWidgetItem(str(streamNumber)) + newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled ) + self.ui.tableWidgetConnectionCount.setItem(0,0,newItem) + newItem = QtGui.QTableWidgetItem(str(connectionCount)) + newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled ) + self.ui.tableWidgetConnectionCount.setItem(0,1,newItem) + totalNumberOfConnectionsFromAllStreams += connectionCount""" + self.ui.labelTotalConnections.setText('Total Connections: ' + 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.setStatusIcon('yellow') + elif len(shared.connectedHostsList) == 0: + self.setStatusIcon('red') + # Indicates whether or not there is a connection to the Bitmessage network 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 + def setStatusIcon(self,color): + global withMessagingMenu + #print 'setting status icon color' if color == 'red': + self.ui.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','Connection lost') 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 self.actionStatus != None: + self.actionStatus.setText('Not Connected') + self.tray.setIcon(QtGui.QIcon(":/newPrefix/images/can-icon-24px-red.png")) + #self.trayIcon.show() + 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.ui.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','Connected') + self.connected = True - if self.statusbar.currentMessage() == ( - "Warning: You are currently not connected. Bitmessage will do" - " the work necessary to send the message but it won't send" - " until you connect." - ): - self.statusbar.clearMessage() - # if a new connection has been established then show a notification - if not self.connected and _notifications_enabled: - self.notifierShow( - 'Bitmessage', - _translate("MainWindow", "Connected"), - sound.SOUND_CONNECTED) - self.connected = True + if self.actionStatus != None: + self.actionStatus.setText('Connected') + self.tray.setIcon(QtGui.QIcon(":/newPrefix/images/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.ui.pushButtonStatusIcon.setIcon(QIcon(":/newPrefix/images/greenicon.png")) + shared.statusIconColor = 'green' + if not self.connected: + self.notifierShow('Bitmessage','Connected') + 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 != None: + self.actionStatus.setText('Connected') + self.tray.setIcon(QtGui.QIcon(":/newPrefix/images/can-icon-24px-green.png")) - def initTrayIcon(self, iconFileName, app): - self.currentTrayIconFileName = iconFileName - self.tray = QtGui.QSystemTrayIcon( - self.calcTrayIcon(iconFileName, self.findInboxUnreadCount()), app) + def updateSentItemStatusByHash(self,toRipe,textToDisplay): + for i in range(self.ui.tableWidgetSent.rowCount()): + toAddress = str(self.ui.tableWidgetSent.item(i,0).data(Qt.UserRole).toPyObject()) + status,addressVersionNumber,streamNumber,ripe = decodeAddress(toAddress) + if ripe == toRipe: + #self.ui.tableWidgetSent.item(i,3).setText(unicode(textToDisplay,'utf-8')) + self.ui.tableWidgetSent.item(i,3).setText(textToDisplay) - def setTrayIconFile(self, iconFileName): - self.currentTrayIconFileName = iconFileName - self.drawTrayIcon(iconFileName, self.findInboxUnreadCount()) + def updateSentItemStatusByAckdata(self,ackdata,textToDisplay): + for i in range(self.ui.tableWidgetSent.rowCount()): + toAddress = str(self.ui.tableWidgetSent.item(i,0).data(Qt.UserRole).toPyObject()) + tableAckdata = self.ui.tableWidgetSent.item(i,3).data(Qt.UserRole).toPyObject() + status,addressVersionNumber,streamNumber,ripe = decodeAddress(toAddress) + if ackdata == tableAckdata: + #self.ui.tableWidgetSent.item(i,3).setText(unicode(textToDisplay,'utf-8')) + self.ui.tableWidgetSent.item(i,3).setText(textToDisplay) - def calcTrayIcon(self, iconFileName, inboxUnreadCount): - pixmap = QtGui.QPixmap(":/newPrefix/images/" + iconFileName) - if inboxUnreadCount > 0: - # choose font and calculate font parameters - fontName = "Lucida" - fontSize = 10 - font = QtGui.QFont(fontName, fontSize, QtGui.QFont.Bold) - fontMetrics = QtGui.QFontMetrics(font) - # text - txt = str(inboxUnreadCount) - 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 - # if it renders too wide we need to change it to a plus symbol - if rect.width() > 20: - txt = "+" - fontSize = 15 - font = QtGui.QFont(fontName, fontSize, QtGui.QFont.Bold) - fontMetrics = QtGui.QFontMetrics(font) - rect = fontMetrics.boundingRect(txt) - # draw text - painter = QtGui.QPainter() - painter.begin(pixmap) - painter.setPen( - QtGui.QPen(QtGui.QColor(255, 0, 0), QtCore.Qt.SolidPattern)) - painter.setFont(font) - painter.drawText(24-rect.right()-marginX, -rect.top()+marginY, txt) - painter.end() - return QtGui.QIcon(pixmap) + def rerenderInboxFromLabels(self): + for i in range(self.ui.tableWidgetInbox.rowCount()): + addressToLookup = str(self.ui.tableWidgetInbox.item(i,1).data(Qt.UserRole).toPyObject()) + fromLabel = '' + t = (addressToLookup,) + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''select label from addressbook where address=?''') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() - def drawTrayIcon(self, iconFileName, inboxUnreadCount): - self.tray.setIcon(self.calcTrayIcon(iconFileName, inboxUnreadCount)) - - def changedInboxUnread(self, row=None): - self.drawTrayIcon( - self.currentTrayIconFileName, self.findInboxUnreadCount()) - self.rerenderTabTreeMessages() - self.rerenderTabTreeSubscriptions() - self.rerenderTabTreeChans() - - def findInboxUnreadCount(self, count=None): - if count is None: - queryreturn = sqlQuery('''SELECT count(*) from inbox WHERE folder='inbox' and read=0''') - cnt = 0 - for row in queryreturn: - cnt, = row - self.unreadCount = int(cnt) - else: - self.unreadCount = count - return self.unreadCount - - def updateSentItemStatusByToAddress(self, toAddress, textToDisplay): - 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: - continue - - for i in range(sent.rowCount()): - rowAddress = sent.item(i, 0).data(QtCore.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. - newlinePosition = 0 - if newlinePosition > 1: - sent.item(i, 3).setText( - textToDisplay[:newlinePosition]) - else: - 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 - ): - 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() - 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. - newlinePosition = 0 - if newlinePosition > 1: - sent.item(i, 3).setText( - textToDisplay[:newlinePosition]) - else: - sent.item(i, 3).setText(textToDisplay) - - 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 - for i in range(inbox.rowCount()): - if msgid == inbox.item(i, 3).data(): - break + if queryreturn <> []: + for row in queryreturn: + fromLabel, = row + self.ui.tableWidgetInbox.item(i,1).setText(unicode(fromLabel,'utf-8')) 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) + #It might be a broadcast message. We should check for that label. + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''select label from subscriptions where address=?''') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() - 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) - ) + if queryreturn <> []: + for row in queryreturn: + fromLabel, = row + self.ui.tableWidgetInbox.item(i,1).setText(unicode(fromLabel,'utf-8')) - def displayAlert(self, title, text, exitAfterUserClicksOk): - self.updateStatusBar(text) - QtGui.QMessageBox.critical(self, title, text, QtGui.QMessageBox.Ok) - if exitAfterUserClicksOk: - os._exit(0) - def rerenderMessagelistFromLabels(self): - 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 i in range(messagelist.rowCount()): - messagelist.item(i, 0).setLabel() - - def rerenderAddressBook(self): - def addRow (address, label, type): - self.ui.tableWidgetAddressBook.insertRow(0) - newItem = Ui_AddressBookWidgetItemLabel(address, unicode(label, 'utf-8'), type) - self.ui.tableWidgetAddressBook.setItem(0, 0, newItem) - newItem = Ui_AddressBookWidgetItemAddress(address, unicode(label, 'utf-8'), type) - self.ui.tableWidgetAddressBook.setItem(0, 1, newItem) - - oldRows = {} - for i in range(self.ui.tableWidgetAddressBook.rowCount()): - item = self.ui.tableWidgetAddressBook.item(i, 0) - oldRows[item.address] = [item.label, item.type, i] - - if self.ui.tableWidgetAddressBook.rowCount() == 0: - self.ui.tableWidgetAddressBook.horizontalHeader().setSortIndicator(0, QtCore.Qt.AscendingOrder) - if self.ui.tableWidgetAddressBook.isSortingEnabled(): - self.ui.tableWidgetAddressBook.setSortingEnabled(False) - - newRows = {} - # subscriptions - queryreturn = sqlQuery('SELECT label, address FROM subscriptions WHERE enabled = 1') - for row in queryreturn: - label, address = row - newRows[address] = [label, AccountMixin.SUBSCRIPTION] - # chans - for address in config.addresses(True): - account = accountClass(address) - if (account.type == AccountMixin.CHAN and config.safeGetBoolean(address, 'enabled')): - newRows[address] = [account.getLabel(), AccountMixin.CHAN] - # normal accounts - queryreturn = sqlQuery('SELECT * FROM addressbook') - for row in queryreturn: - label, address = row - newRows[address] = [label, AccountMixin.NORMAL] - - completerList = [] - for address in sorted( - oldRows, key=lambda x: oldRows[x][2], reverse=True - ): + def rerenderInboxToLabels(self): + for i in range(self.ui.tableWidgetInbox.rowCount()): + toAddress = str(self.ui.tableWidgetInbox.item(i,0).data(Qt.UserRole).toPyObject()) try: - completerList.append( - newRows.pop(address)[0] + " <" + address + ">") - except KeyError: - 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 + ">") + toLabel = shared.config.get(toAddress, 'label') + except: + toLabel = '' + if toLabel == '': + toLabel = toAddress + self.ui.tableWidgetInbox.item(i,0).setText(unicode(toLabel,'utf-8')) + #Set the color according to whether it is the address of a mailing list or not. + if shared.safeConfigGetBoolean(toAddress,'mailinglist'): + self.ui.tableWidgetInbox.item(i,0).setTextColor(QtGui.QColor(137,04,177)) + else: + self.ui.tableWidgetInbox.item(i,0).setTextColor(QtGui.QColor(0,0,0)) - # sort - self.ui.tableWidgetAddressBook.sortByColumn( - 0, QtCore.Qt.AscendingOrder) - self.ui.tableWidgetAddressBook.setSortingEnabled(True) - self.ui.lineEditTo.completer().model().setStringList(completerList) + def rerenderSentFromLabels(self): + for i in range(self.ui.tableWidgetSent.rowCount()): + fromAddress = str(self.ui.tableWidgetSent.item(i,1).data(Qt.UserRole).toPyObject()) + try: + fromLabel = shared.config.get(fromAddress, 'label') + except: + fromLabel = '' + if fromLabel == '': + fromLabel = fromAddress + self.ui.tableWidgetSent.item(i,1).setText(unicode(fromLabel,'utf-8')) + + def rerenderSentToLabels(self): + for i in range(self.ui.tableWidgetSent.rowCount()): + addressToLookup = str(self.ui.tableWidgetSent.item(i,0).data(Qt.UserRole).toPyObject()) + toLabel = '' + t = (addressToLookup,) + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''select label from addressbook where address=?''') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + + if queryreturn <> []: + for row in queryreturn: + toLabel, = row + self.ui.tableWidgetSent.item(i,0).setText(unicode(toLabel,'utf-8')) 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) + self.ui.tableWidgetSubscriptions.setRowCount(0) + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('SELECT label, address, enabled FROM subscriptions') + shared.sqlSubmitQueue.put('') + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + for row in queryreturn: + label, address, enabled = row + self.ui.tableWidgetSubscriptions.insertRow(0) + newItem = QtGui.QTableWidgetItem(unicode(label,'utf-8')) + if not enabled: + newItem.setTextColor(QtGui.QColor(128,128,128)) + self.ui.tableWidgetSubscriptions.setItem(0,0,newItem) + newItem = QtGui.QTableWidgetItem(address) + newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled ) + if not enabled: + newItem.setTextColor(QtGui.QColor(128,128,128)) + self.ui.tableWidgetSubscriptions.setItem(0,1,newItem) def click_pushButtonSend(self): - encoding = 3 if QtGui.QApplication.queryKeyboardModifiers() & QtCore.Qt.ShiftModifier else 2 - - self.statusbar.clearMessage() - - if self.ui.tabWidgetSend.currentIndex() == \ - self.ui.tabWidgetSend.indexOf(self.ui.sendDirect): - # message to specific people - sendMessageToPeople = True - fromAddress = str(self.ui.comboBoxSendFrom.itemData( - self.ui.comboBoxSendFrom.currentIndex(), - QtCore.Qt.UserRole).toString()) - toAddresses = str(self.ui.lineEditTo.text().toUtf8()) - subject = str(self.ui.lineEditSubject.text().toUtf8()) - message = str( - self.ui.textEditMessage.document().toPlainText().toUtf8()) - else: - # broadcast message - sendMessageToPeople = False - fromAddress = str(self.ui.comboBoxSendFromBroadcast.itemData( - self.ui.comboBoxSendFromBroadcast.currentIndex(), - QtCore.Qt.UserRole).toString()) - subject = str(self.ui.lineEditSubjectBroadcast.text().toUtf8()) - message = str( - self.ui.textEditMessageBroadcast.document().toPlainText().toUtf8()) - """ - The whole network message must fit in 2^18 bytes. - Let's assume 500 bytes of overhead. If someone wants to get that - too an exact number you are welcome to but I think that it would - be a better use of time to support message continuation so that - users can send messages of any length. - """ - if len(message) > (2 ** 18 - 500): - QtGui.QMessageBox.about( - self, _translate("MainWindow", "Message too long"), - _translate( - "MainWindow", - "The message that you are trying to send is too long" - " by %1 bytes. (The maximum is 261644 bytes). Please" - " cut it down before sending." - ).arg(len(message) - (2 ** 18 - 500))) - return - - acct = accountClass(fromAddress) - - # 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. + self.statusBar().showMessage('') + toAddresses = str(self.ui.lineEditTo.text()) + fromAddress = str(self.ui.labelFrom.text()) + subject = str(self.ui.lineEditSubject.text().toUtf8()) + message = str(self.ui.textEditMessage.document().toPlainText().toUtf8()) + if self.ui.radioButtonSpecific.isChecked(): #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: - if isinstance(acct, GatewayAccount): - acct.createMessage(toAddress, fromAddress, subject, message) - subject = acct.subject - toAddress = acct.toAddress - else: - if QtGui.QMessageBox.question( - self, - "Sending an email?", - _translate( - "MainWindow", - "You are trying to send an email instead of a bitmessage. " - "This requires registering with a gateway. Attempt to register?"), - QtGui.QMessageBox.Yes|QtGui.QMessageBox.No) != QtGui.QMessageBox.Yes: - continue - email = acct.getLabel() - if email[-14:] != "@mailchuck.com": # attempt register - # 12 character random email address - 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) - ) - return - status, addressVersionNumber, streamNumber = decodeAddress(toAddress)[:3] - if status != 'success': - try: - toAddress = unicode(toAddress, 'utf-8', 'ignore') - except: - pass - logger.error('Error: Could not decode recipient address ' + toAddress + ':' + status) - + if toAddress <> '': + status,addressVersionNumber,streamNumber,ripe = decodeAddress(toAddress) + if status <> 'success': + shared.printLock.acquire() + print 'Error: Could not decode', toAddress, ':', status + shared.printLock.release() if status == 'missingbm': - self.updateStatusBar(_translate( - "MainWindow", - "Error: Bitmessage addresses start with" - " BM- Please check the recipient address %1" - ).arg(toAddress)) + self.statusBar().showMessage('Error: Bitmessage addresses start with BM- Please check ' + 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('Error: The address ' + toAddress+' is not typed or copied correctly. Please check it.') elif status == 'invalidcharacters': - self.updateStatusBar(_translate( - "MainWindow", - "Error: The recipient address %1 contains" - " invalid characters. Please check it." - ).arg(toAddress)) + self.statusBar().showMessage('Error: The address '+ toAddress+ ' contains invalid characters. Please check it.') 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('Error: The address version in '+ toAddress+ ' is too high. Either you need to upgrade your Bitmessage software or your acquaintance is being clever.') 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('Error: Some data encoded in the address '+ toAddress+ ' is too short. There might be something wrong with the software of your acquaintance.') 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)) - 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('Error: Some data encoded in the address '+ toAddress+ ' is too long. There might be something wrong with the software of your acquaintance.') else: - self.updateStatusBar(_translate( - "MainWindow", - "Error: Something is wrong with the" - " recipient address %1." - ).arg(toAddress)) + self.statusBar().showMessage('Error: Something is wrong with the address '+ 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('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))) + try: + shared.config.get(toAddress, 'enabled') + #The toAddress is one owned by me. We cannot send messages to ourselves without significant changes to the codebase. + QMessageBox.about(self, "Sending to your address", "Error: One of the addresses to which you are sending a message, "+toAddress+", is yours. Unfortunately the Bitmessage client cannot process its own messages. Please try running a second client on a different computer or within a VM.") + continue + except: + pass + if addressVersionNumber > 3 or addressVersionNumber == 0: + QMessageBox.about(self, "Address version number", "Concerning the address "+toAddress+", Bitmessage cannot understand address version numbers of "+str(addressVersionNumber)+". Perhaps upgrade Bitmessage to the latest version.") 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, "Stream number", "Concerning the address "+toAddress+", Bitmessage cannot handle stream numbers of "+str(streamNumber)+". Perhaps upgrade Bitmessage to the latest version.") 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.") - ) - ackdata = helper_sent.insert( - toAddress=toAddress, fromAddress=fromAddress, - subject=subject, message=message, encoding=encoding) + self.statusBar().showMessage('') + if shared.statusIconColor == 'red': + self.statusBar().showMessage('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) + shared.sqlLock.acquire() + t = ('',toAddress,ripe,fromAddress,subject,message,ackdata,int(time.time()),'findingpubkey',1,1,'sent',2) + shared.sqlSubmitQueue.put('''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)''') + shared.sqlSubmitQueue.put(t) + shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() + toLabel = '' - queryreturn = sqlQuery('''select label from addressbook where address=?''', - toAddress) - if queryreturn != []: + t = (toAddress,) + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''select label from addressbook where address=?''') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + if queryreturn <> []: for row in queryreturn: toLabel, = row - self.displayNewSentMessage( - toAddress, toLabel, fromAddress, subject, message, ackdata) - queues.workerQueue.put(('sendmessage', toAddress)) + self.displayNewSentMessage(toAddress,toLabel,fromAddress, subject, message, ackdata) + shared.workerQueue.put(('sendmessage',toAddress)) - self.click_pushButtonClear() - if self.replyFromTab is not None: - self.ui.tabWidget.setCurrentIndex(self.replyFromTab) - self.replyFromTab = None - self.updateStatusBar(_translate( - "MainWindow", "Message queued.")) - # self.ui.tableWidgetInbox.setCurrentCell(0, 0) + self.ui.comboBoxSendFrom.setCurrentIndex(0) + self.ui.labelFrom.setText('') + self.ui.lineEditTo.setText('') + self.ui.lineEditSubject.setText('') + self.ui.textEditMessage.setText('') + self.ui.tabWidget.setCurrentIndex(2) + self.ui.tableWidgetSent.setCurrentCell(0,0) else: - self.updateStatusBar(_translate( - "MainWindow", "Your \'To\' field is empty.")) - else: # User selected 'Broadcast' + self.statusBar().showMessage('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('Error: You must specify a From address. If you don\'t have one, go to the \'Your Identities\' tab.') else: - self.statusbar.clearMessage() - # We don't actually need the ackdata for acknowledgement since - # this is a broadcast message, but we can use it to update the - # user interface when the POW is done generating. - toAddress = str_broadcast_subscribers + 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 = self.str_broadcast_subscribers + ripe = '' + shared.sqlLock.acquire() + t = ('',toAddress,ripe,fromAddress,subject,message,ackdata,int(time.time()),'broadcastpending',1,1,'sent',2) + shared.sqlSubmitQueue.put('''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)''') + shared.sqlSubmitQueue.put(t) + shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() - # 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) + shared.workerQueue.put(('sendbroadcast',(fromAddress,subject,message))) - toLabel = str_broadcast_subscribers + try: + fromLabel = shared.config.get(fromAddress, 'label') + except: + fromLabel = '' + if fromLabel == '': + fromLabel = fromAddress - self.displayNewSentMessage( - toAddress, toLabel, fromAddress, subject, message, ackdata) + toLabel = self.str_broadcast_subscribers - queues.workerQueue.put(('sendbroadcast', '')) + self.ui.tableWidgetSent.insertRow(0) + newItem = QtGui.QTableWidgetItem(unicode(toLabel,'utf-8')) + newItem.setData(Qt.UserRole,str(toAddress)) + self.ui.tableWidgetSent.setItem(0,0,newItem) + + if fromLabel == '': + newItem = QtGui.QTableWidgetItem(unicode(fromAddress,'utf-8')) + else: + newItem = QtGui.QTableWidgetItem(unicode(fromLabel,'utf-8')) + newItem.setData(Qt.UserRole,str(fromAddress)) + self.ui.tableWidgetSent.setItem(0,1,newItem) + newItem = QtGui.QTableWidgetItem(unicode(subject,'utf-8)')) + newItem.setData(Qt.UserRole,unicode(message,'utf-8)')) + self.ui.tableWidgetSent.setItem(0,2,newItem) + #newItem = QtGui.QTableWidgetItem('Doing work necessary to send broadcast...'+ unicode(strftime(config.get('bitmessagesettings', 'timeformat'),localtime(int(time.time()))),'utf-8')) + newItem = myTableWidgetItem('Work is queued.') + newItem.setData(Qt.UserRole,QByteArray(ackdata)) + newItem.setData(33,int(time.time())) + self.ui.tableWidgetSent.setItem(0,3,newItem) + + self.ui.textEditSentMessage.setPlainText(self.ui.tableWidgetSent.item(0,2).data(Qt.UserRole).toPyObject()) + + self.ui.comboBoxSendFrom.setCurrentIndex(0) + self.ui.labelFrom.setText('') + self.ui.lineEditTo.setText('') + self.ui.lineEditSubject.setText('') + self.ui.textEditMessage.setText('') + self.ui.tabWidget.setCurrentIndex(2) + self.ui.tableWidgetSent.setCurrentCell(0,0) - 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.tableWidgetInboxSubscriptions.setCurrentCell(0, 0) - self.updateStatusBar(_translate( - "MainWindow", "Broadcast queued.")) def click_pushButtonLoadFromAddressBook(self): self.ui.tabWidget.setCurrentIndex(5) for i in range(4): time.sleep(0.1) - self.statusbar.clearMessage() + self.statusBar().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('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()) - if err is not None: - self.updateStatusBar( - _translate("MainWindow", "Error: %1").arg(err)) - else: - identities[-1] = addr - self.ui.lineEditTo.setText("; ".join(identities)) - self.updateStatusBar(_translate( - "MainWindow", "Fetched address from namecoin identity.")) - - def setBroadcastEnablementDependingOnWhetherThisIsAMailingListAddress(self, address): - # If this is a chan then don't let people broadcast because no one - # should subscribe to chan addresses. - self.ui.tabWidgetSend.setCurrentIndex( - self.ui.tabWidgetSend.indexOf( - self.ui.sendBroadcast - if config.safeGetBoolean(str(address), 'mailinglist') - else self.ui.sendDirect - )) + def redrawLabelFrom(self,index): + self.ui.labelFrom.setText(self.ui.comboBoxSendFrom.itemData(index).toPyObject()) 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') - 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.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) - self.ui.comboBoxSendFrom.insertItem(0, '', '') + self.ui.labelFrom.setText('') + configSections = shared.config.sections() + for addressInKeysFile in configSections: + if addressInKeysFile <> 'bitmessagesettings': + 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. + if isEnabled: + self.ui.comboBoxSendFrom.insertItem(0,unicode(shared.config.get(addressInKeysFile, 'label'),'utf-8'),addressInKeysFile) + self.ui.comboBoxSendFrom.insertItem(0,'','') if(self.ui.comboBoxSendFrom.count() == 2): self.ui.comboBoxSendFrom.setCurrentIndex(1) + self.redrawLabelFrom(self.ui.comboBoxSendFrom.currentIndex()) else: self.ui.comboBoxSendFrom.setCurrentIndex(0) - def rerenderComboBoxSendFromBroadcast(self): - self.ui.comboBoxSendFromBroadcast.clear() - for addressInKeysFile in config.addresses(True): - isEnabled = config.getboolean( - addressInKeysFile, 'enabled') - isChan = config.safeGetBoolean(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) - 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) - self.ui.comboBoxSendFromBroadcast.insertItem(0, '', '') - if(self.ui.comboBoxSendFromBroadcast.count() == 2): - self.ui.comboBoxSendFromBroadcast.setCurrentIndex(1) + + #This function is called by the processmsg function when that function 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): + try: + fromLabel = shared.config.get(fromAddress, 'label') + except: + fromLabel = '' + if fromLabel == '': + fromLabel = fromAddress + + self.ui.tableWidgetSent.setSortingEnabled(False) + self.ui.tableWidgetSent.insertRow(0) + if toLabel == '': + newItem = QtGui.QTableWidgetItem(unicode(toAddress,'utf-8')) else: - self.ui.comboBoxSendFromBroadcast.setCurrentIndex(0) - - # This function is called by the processmsg function when that function - # 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): - acct = accountClass(fromAddress) - acct.parseMessage(toAddress, fromAddress, subject, message) - tab = -1 - 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): - continue - 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) - ): - continue - - self.addMessageListItemSent( - sent, toAddress, fromAddress, subject, - "msgqueued", ackdata, time.time()) - self.getAccountTextedit(acct).setPlainText(message) - sent.setCurrentCell(0, 0) - - def displayNewInboxMessage( - self, inventoryHash, toAddress, fromAddress, subject, message): - acct = accountClass( - fromAddress if toAddress == str_broadcast_subscribers - else toAddress - ) - inbox = self.getAccountMessagelist(acct) - ret = treeWidget = None - tab = -1 - 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 ret is None: - acct.parseMessage(toAddress, fromAddress, subject, "") + newItem = QtGui.QTableWidgetItem(unicode(toLabel,'utf-8')) + newItem.setData(Qt.UserRole,str(toAddress)) + self.ui.tableWidgetSent.setItem(0,0,newItem) + if fromLabel == '': + newItem = QtGui.QTableWidgetItem(unicode(fromAddress,'utf-8')) 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) + newItem = QtGui.QTableWidgetItem(unicode(fromLabel,'utf-8')) + newItem.setData(Qt.UserRole,str(fromAddress)) + self.ui.tableWidgetSent.setItem(0,1,newItem) + newItem = QtGui.QTableWidgetItem(unicode(subject,'utf-8)')) + newItem.setData(Qt.UserRole,unicode(message,'utf-8)')) + self.ui.tableWidgetSent.setItem(0,2,newItem) + #newItem = QtGui.QTableWidgetItem('Doing work necessary to send broadcast...'+ unicode(strftime(shared.config.get('bitmessagesettings', 'timeformat'),localtime(int(time.time()))),'utf-8')) + newItem = myTableWidgetItem('Work is queued. '+ unicode(strftime(shared.config.get('bitmessagesettings', 'timeformat'),localtime(int(time.time()))),'utf-8')) + newItem.setData(Qt.UserRole,QByteArray(ackdata)) + newItem.setData(33,int(time.time())) + self.ui.tableWidgetSent.setItem(0,3,newItem) + self.ui.textEditSentMessage.setPlainText(self.ui.tableWidgetSent.item(0,2).data(Qt.UserRole).toPyObject()) + self.ui.tableWidgetSent.setSortingEnabled(True) + + def displayNewInboxMessage(self,inventoryHash,toAddress,fromAddress,subject,message): + fromLabel = '' + shared.sqlLock.acquire() + t = (fromAddress,) + shared.sqlSubmitQueue.put('''select label from addressbook where address=?''') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + if queryreturn <> []: + for row in queryreturn: + fromLabel, = row + else: + #There might be a label in the subscriptions table + shared.sqlLock.acquire() + t = (fromAddress,) + shared.sqlSubmitQueue.put('''select label from subscriptions where address=?''') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + if queryreturn <> []: + for row in queryreturn: + fromLabel, = row 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 + if toAddress == self.str_broadcast_subscribers: + toLabel = self.str_broadcast_subscribers + else: + toLabel = shared.config.get(toAddress, 'label') + except: + toLabel = '' + if toLabel == '': + toLabel = toAddress - def click_pushButtonAddAddressBook(self, dialog=None): - if not dialog: - dialog = dialogs.AddAddressDialog(self) - dialog.exec_() - try: - address, label = dialog.data - except AttributeError: - return + font = QFont() + font.setBold(True) + self.ui.tableWidgetInbox.setSortingEnabled(False) + newItem = QtGui.QTableWidgetItem(unicode(toLabel,'utf-8')) + newItem.setFont(font) + newItem.setData(Qt.UserRole,str(toAddress)) + if shared.safeConfigGetBoolean(str(toAddress),'mailinglist'): + newItem.setTextColor(QtGui.QColor(137,04,177)) + self.ui.tableWidgetInbox.insertRow(0) + self.ui.tableWidgetInbox.setItem(0,0,newItem) - # 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): - self.rerenderMessagelistFromLabels() - self.rerenderMessagelistToLabels() - self.rerenderAddressBook() + if fromLabel == '': + newItem = QtGui.QTableWidgetItem(unicode(fromAddress,'utf-8')) + if shared.config.getboolean('bitmessagesettings', 'showtraynotifications'): + self.notifierShow('New Message', 'From '+ fromAddress) else: - self.updateStatusBar(_translate( - "MainWindow", - "Error: You cannot add your own address in the address book." - )) + newItem = QtGui.QTableWidgetItem(unicode(fromLabel,'utf-8')) + if shared.config.getboolean('bitmessagesettings', 'showtraynotifications'): + self.notifierShow('New Message', 'From ' + fromLabel) + newItem.setData(Qt.UserRole,str(fromAddress)) + newItem.setFont(font) + self.ui.tableWidgetInbox.setItem(0,1,newItem) + newItem = QtGui.QTableWidgetItem(unicode(subject,'utf-8)')) + newItem.setData(Qt.UserRole,unicode(message,'utf-8)')) + newItem.setFont(font) + self.ui.tableWidgetInbox.setItem(0,2,newItem) + newItem = myTableWidgetItem(unicode(strftime(shared.config.get('bitmessagesettings', 'timeformat'),localtime(int(time.time()))),'utf-8')) + newItem.setData(Qt.UserRole,QByteArray(inventoryHash)) + newItem.setData(33,int(time.time())) + newItem.setFont(font) + self.ui.tableWidgetInbox.setItem(0,3,newItem) + self.ui.tableWidgetInbox.setSortingEnabled(True) + self.ubuntuMessagingMenuUpdate(True, newItem, toLabel) - 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. - 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 - ) - self.rerenderMessagelistFromLabels() - shared.reloadBroadcastSendersForWhichImWatching() - self.rerenderAddressBook() - self.rerenderTabTreeSubscriptions() + def click_pushButtonAddAddressBook(self): + self.NewSubscriptionDialogInstance = NewSubscriptionDialog(self) + if self.NewSubscriptionDialogInstance.exec_(): + if self.NewSubscriptionDialogInstance.ui.labelSubscriptionAddressCheck.text() == '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. + shared.sqlLock.acquire() + t = (addBMIfNotPresent(str(self.NewSubscriptionDialogInstance.ui.lineEditSubscriptionAddress.text())),) + shared.sqlSubmitQueue.put('''select * from addressbook where address=?''') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + if queryreturn == []: + self.ui.tableWidgetAddressBook.setSortingEnabled(False) + self.ui.tableWidgetAddressBook.insertRow(0) + newItem = QtGui.QTableWidgetItem(unicode(self.NewSubscriptionDialogInstance.ui.newsubscriptionlabel.text().toUtf8(),'utf-8')) + self.ui.tableWidgetAddressBook.setItem(0,0,newItem) + newItem = QtGui.QTableWidgetItem(addBMIfNotPresent(self.NewSubscriptionDialogInstance.ui.lineEditSubscriptionAddress.text())) + newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled ) + self.ui.tableWidgetAddressBook.setItem(0,1,newItem) + self.ui.tableWidgetAddressBook.setSortingEnabled(True) + t = (str(self.NewSubscriptionDialogInstance.ui.newsubscriptionlabel.text().toUtf8()),addBMIfNotPresent(str(self.NewSubscriptionDialogInstance.ui.lineEditSubscriptionAddress.text()))) + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''INSERT INTO addressbook VALUES (?,?)''') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() + self.rerenderInboxFromLabels() + self.rerenderSentToLabels() + else: + self.statusBar().showMessage('Error: You cannot add the same address to your address book twice. Try renaming the existing one if you want.') + else: + self.statusBar().showMessage('The address you entered was invalid. Ignoring it.') def click_pushButtonAddSubscription(self): - dialog = dialogs.NewSubscriptionDialog(self) - dialog.exec_() - try: - address, label = dialog.data - except AttributeError: - return + self.NewSubscriptionDialogInstance = NewSubscriptionDialog(self) - # 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 + if self.NewSubscriptionDialogInstance.exec_(): + if self.NewSubscriptionDialogInstance.ui.labelSubscriptionAddressCheck.text() == 'Address is valid.': + #First 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. + shared.sqlLock.acquire() + t = (addBMIfNotPresent(str(self.NewSubscriptionDialogInstance.ui.lineEditSubscriptionAddress.text())),) + shared.sqlSubmitQueue.put('''select * from subscriptions where address=?''') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + if queryreturn == []: + self.ui.tableWidgetSubscriptions.setSortingEnabled(False) + self.ui.tableWidgetSubscriptions.insertRow(0) + newItem = QtGui.QTableWidgetItem(unicode(self.NewSubscriptionDialogInstance.ui.newsubscriptionlabel.text().toUtf8(),'utf-8')) + self.ui.tableWidgetSubscriptions.setItem(0,0,newItem) + newItem = QtGui.QTableWidgetItem(addBMIfNotPresent(self.NewSubscriptionDialogInstance.ui.lineEditSubscriptionAddress.text())) + newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled ) + self.ui.tableWidgetSubscriptions.setItem(0,1,newItem) + self.ui.tableWidgetSubscriptions.setSortingEnabled(True) + t = (str(self.NewSubscriptionDialogInstance.ui.newsubscriptionlabel.text().toUtf8()),addBMIfNotPresent(str(self.NewSubscriptionDialogInstance.ui.lineEditSubscriptionAddress.text())),True) + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''INSERT INTO subscriptions VALUES (?,?,?)''') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() + self.rerenderInboxFromLabels() + shared.reloadBroadcastSendersForWhichImWatching() + else: + self.statusBar().showMessage('Error: You cannot add the same address to your subsciptions twice. Perhaps rename the existing one if you want.') + else: + self.statusBar().showMessage('The address you entered was invalid. Ignoring it.') - self.addSubscription(address, label) - # Now, if the user wants to display old broadcasts, let's get - # them out of the inventory and put them - # to the objectProcessorQueue to be processed - if dialog.checkBoxDisplayMessagesAlreadyInInventory.isChecked(): - for value in dialog.recent: - queues.objectProcessorQueue.put(( - value.type, value.payload - )) + def loadBlackWhiteList(self): + #Initialize the Blacklist or Whitelist table + listType = shared.config.get('bitmessagesettings', 'blackwhitelist') + shared.sqlLock.acquire() + if listType == 'black': + shared.sqlSubmitQueue.put('''SELECT label, address, enabled FROM blacklist''') + else: + shared.sqlSubmitQueue.put('''SELECT label, address, enabled FROM whitelist''') + shared.sqlSubmitQueue.put('') + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + for row in queryreturn: + label, address, enabled = row + self.ui.tableWidgetBlacklist.insertRow(0) + newItem = QtGui.QTableWidgetItem(unicode(label,'utf-8')) + if not enabled: + newItem.setTextColor(QtGui.QColor(128,128,128)) + self.ui.tableWidgetBlacklist.setItem(0,0,newItem) + newItem = QtGui.QTableWidgetItem(address) + newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled ) + if not enabled: + newItem.setTextColor(QtGui.QColor(128,128,128)) + self.ui.tableWidgetBlacklist.setItem(0,1,newItem) def click_pushButtonStatusIcon(self): - dialogs.IconGlossaryDialog(self, config=config).exec_() + print 'click_pushButtonStatusIcon' + self.iconGlossaryInstance = iconGlossaryDialog(self) + if self.iconGlossaryInstance.exec_(): + pass def click_actionHelp(self): - dialogs.HelpDialog(self).exec_() - - def click_actionSupport(self): - support.createSupportMessage(self) + self.helpDialogInstance = helpDialog(self) + self.helpDialogInstance.exec_() 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', 'showtraynotifications', str(self.settingsDialogInstance.ui.checkBoxShowTrayNotifications.isChecked())) + shared.config.set('bitmessagesettings', 'startintray', str(self.settingsDialogInstance.ui.checkBoxStartInTray.isChecked())) + if int(shared.config.get('bitmessagesettings','port')) != int(self.settingsDialogInstance.ui.lineEditTCPPort.text()): + QMessageBox.about(self, "Restart", "You must restart Bitmessage for the port number change to take effect.") + shared.config.set('bitmessagesettings', 'port', str(self.settingsDialogInstance.ui.lineEditTCPPort.text())) + if shared.config.get('bitmessagesettings', 'socksproxytype') == 'none' and str(self.settingsDialogInstance.ui.comboBoxProxyType.currentText())[0:5] == 'SOCKS': + if shared.statusIconColor != 'red': + QMessageBox.about(self, "Restart", "Bitmessage will use your proxy from now on now but you may want to manually restart Bitmessage now to close existing connections.") + if shared.config.get('bitmessagesettings', 'socksproxytype')[0:5] == 'SOCKS' and str(self.settingsDialogInstance.ui.comboBoxProxyType.currentText()) == 'none': + self.statusBar().showMessage('') + shared.config.set('bitmessagesettings', 'socksproxytype', str(self.settingsDialogInstance.ui.comboBoxProxyType.currentText())) + 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())) + 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))) - 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) - ) + with open(shared.appdata + 'keys.dat', 'wb') as configfile: + shared.config.write(configfile) + + 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 != '' 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 + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('movemessagstoprog') + shared.sqlLock.release() + with open('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('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') + shared.appdata = '' + + if shared.appdata == '' 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) + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('movemessagstoappdata') + shared.sqlLock.release() + #Write the keys.dat file to disk in the new location + with open(shared.appdata + '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.appdata + 'knownnodes.dat', 'wb') + pickle.dump(shared.knownNodes, output) + output.close() + shared.knownNodesLock.release() + os.remove('keys.dat') + os.remove('knownnodes.dat') + + + def click_radioButtonBlacklist(self): + if shared.config.get('bitmessagesettings', 'blackwhitelist') == 'white': + shared.config.set('bitmessagesettings','blackwhitelist','black') + with open(shared.appdata + 'keys.dat', 'wb') as configfile: + shared.config.write(configfile) + #self.ui.tableWidgetBlacklist.clearContents() + self.ui.tableWidgetBlacklist.setRowCount(0) + self.loadBlackWhiteList() + self.ui.tabWidget.setTabText(6,'Blacklist') + + + def click_radioButtonWhitelist(self): + if shared.config.get('bitmessagesettings', 'blackwhitelist') == 'black': + shared.config.set('bitmessagesettings','blackwhitelist','white') + with open(shared.appdata + 'keys.dat', 'wb') as configfile: + shared.config.write(configfile) + #self.ui.tableWidgetBlacklist.clearContents() + self.ui.tableWidgetBlacklist.setRowCount(0) + self.loadBlackWhiteList() + self.ui.tabWidget.setTabText(6,'Whitelist') + + def click_pushButtonAddBlacklist(self): + self.NewBlacklistDialogInstance = NewSubscriptionDialog(self) + if self.NewBlacklistDialogInstance.exec_(): + if self.NewBlacklistDialogInstance.ui.labelSubscriptionAddressCheck.text() == '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. + shared.sqlLock.acquire() + t = (addBMIfNotPresent(str(self.NewBlacklistDialogInstance.ui.lineEditSubscriptionAddress.text())),) + if shared.config.get('bitmessagesettings', 'blackwhitelist') == 'black': + shared.sqlSubmitQueue.put('''select * from blacklist where address=?''') + else: + shared.sqlSubmitQueue.put('''select * from whitelist where address=?''') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() + if queryreturn == []: + self.ui.tableWidgetBlacklist.setSortingEnabled(False) + self.ui.tableWidgetBlacklist.insertRow(0) + newItem = QtGui.QTableWidgetItem(unicode(self.NewBlacklistDialogInstance.ui.newsubscriptionlabel.text().toUtf8(),'utf-8')) + self.ui.tableWidgetBlacklist.setItem(0,0,newItem) + newItem = QtGui.QTableWidgetItem(addBMIfNotPresent(self.NewBlacklistDialogInstance.ui.lineEditSubscriptionAddress.text())) + newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled ) + self.ui.tableWidgetBlacklist.setItem(0,1,newItem) + self.ui.tableWidgetBlacklist.setSortingEnabled(True) + t = (str(self.NewBlacklistDialogInstance.ui.newsubscriptionlabel.text().toUtf8()),addBMIfNotPresent(str(self.NewBlacklistDialogInstance.ui.lineEditSubscriptionAddress.text())),True) + shared.sqlLock.acquire() + if shared.config.get('bitmessagesettings', 'blackwhitelist') == 'black': + shared.sqlSubmitQueue.put('''INSERT INTO blacklist VALUES (?,?,?)''') + else: + shared.sqlSubmitQueue.put('''INSERT INTO whitelist VALUES (?,?,?)''') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() + else: + self.statusBar().showMessage('Error: You cannot add the same address to your list twice. Perhaps rename the existing one if you want.') + else: + self.statusBar().showMessage('The address you entered was invalid. Ignoring it.') def on_action_SpecialAddressBehaviorDialog(self): - """Show SpecialAddressBehaviorDialog""" - dialogs.SpecialAddressBehaviorDialog(self, config) - - def on_action_EmailGatewayDialog(self): - dialog = dialogs.EmailGatewayDialog(self, config=config) + self.dialog = SpecialAddressBehaviorDialog(self) # For Modal dialogs - dialog.exec_() - try: - acct = dialog.data - except AttributeError: - return + if self.dialog.exec_(): + currentRow = self.ui.tableWidgetYourIdentities.currentRow() + addressAtCurrentRow = str(self.ui.tableWidgetYourIdentities.item(currentRow,1).text()) + 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.ui.tableWidgetYourIdentities.item(currentRow,1).setTextColor(QtGui.QColor(0,0,0)) + else: + self.ui.tableWidgetYourIdentities.item(currentRow,1).setTextColor(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.ui.tableWidgetYourIdentities.item(currentRow,1).setTextColor(QtGui.QColor(137,04,177)) + with open(shared.appdata + 'keys.dat', 'wb') as configfile: + shared.config.write(configfile) + self.rerenderInboxToLabels() - # 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() def click_NewAddressDialog(self): - dialogs.NewAddressDialog(self) + 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 = addressStream(self.dialog.ui.comboBoxExisting.currentText()) - 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.addressGenerator = addressGenerator() + #self.addressGenerator.setup(3,streamNumberForAddress,str(self.dialog.ui.newaddresslabel.text().toUtf8()),1,"",self.dialog.ui.checkBoxEighteenByteRipe.isChecked()) + #QtCore.QObject.connect(self.addressGenerator, SIGNAL("writeNewAddressToTable(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), self.writeNewAddressToTable) + #QtCore.QObject.connect(self.addressGenerator, QtCore.SIGNAL("updateStatusBar(PyQt_PyObject)"), self.updateStatusBar) + #self.addressGenerator.start() + shared.addressGeneratorQueue.put(('createRandomAddress',3,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, "Passphrase mismatch", "The passphrase you entered twice doesn\'t match. Try again.") + elif self.dialog.ui.lineEditPassphrase.text() == "": + QMessageBox.about(self, "Choose a passphrase", "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. + #self.addressGenerator = addressGenerator() + #self.addressGenerator.setup(3,streamNumberForAddress,"unused address",self.dialog.ui.spinBoxNumberOfAddressesToMake.value(),self.dialog.ui.lineEditPassphrase.text().toUtf8(),self.dialog.ui.checkBoxEighteenByteRipe.isChecked()) + #QtCore.QObject.connect(self.addressGenerator, SIGNAL("writeNewAddressToTable(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), self.writeNewAddressToTable) + #QtCore.QObject.connect(self.addressGenerator, QtCore.SIGNAL("updateStatusBar(PyQt_PyObject)"), self.updateStatusBar) + #self.addressGenerator.start() + shared.addressGeneratorQueue.put(('createDeterministicAddresses',3,streamNumberForAddress,"unused deterministic address",self.dialog.ui.spinBoxNumberOfAddressesToMake.value(),self.dialog.ui.lineEditPassphrase.text().toUtf8(),self.dialog.ui.checkBoxEighteenByteRipe.isChecked())) + else: + print 'new address dialog box rejected' - self.ui.pushButtonFetchNamecoinID.setHidden( - dontconnect_option or self.namecoin.test()[0] == 'failed' - ) # Quit selected from menu or application indicator def quit(self): - """Quit the bitmessageqt application""" - if self.quitAccepted and not self.wait: + '''quit_msg = "Are you sure you want to exit Bitmessage?" + reply = QtGui.QMessageBox.question(self, 'Message', + quit_msg, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) + + if reply is QtGui.QMessageBox.No: return - - 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': - time.sleep(0.5) - QtCore.QCoreApplication.processEvents( - QtCore.QEventLoop.AllEvents, 1000 - ) - - # this probably will not work correctly, because there is a delay - # between the status icon turning red and inventory exchange, - # but it's better than nothing. - if waitForSync: - self.updateStatusBar(_translate( - "MainWindow", "Waiting for finishing synchronisation...")) - while pendingDownload() > 0: - time.sleep(0.5) - 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 - ) - - # 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.saveSettings() - for attr, obj in self.ui.__dict__.iteritems(): - if hasattr(obj, "__class__") \ - and isinstance(obj, settingsmixin.SettingsMixin): - saveMethod = getattr(obj, "saveSettings", None) - 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)) + ''' + shared.doCleanShutdown() self.tray.hide() + # unregister the messaging system + if self.mmapp is not None: + self.mmapp.unregister() + #self.trayIcon.hide() + self.statusBar().showMessage('All done. Closing user interface...') + os._exit(0) - self.updateStatusBar(_translate( - "MainWindow", "Shutdown imminent... %1%").arg(100)) - - logger.info("Shutdown complete") - self.close() - # FIXME: rewrite loops with timer instead - if self.wait: - self.destroy() - app.quit() - + # window close event def closeEvent(self, event): - """window close event""" - event.ignore() - trayonclose = config.safeGetBoolean( - 'bitmessagesettings', 'trayonclose') - if trayonclose: - self.appIndicatorHide() + self.appIndicatorHide() + minimizeonclose = False + + try: + minimizeonclose = shared.config.getboolean('bitmessagesettings', 'minimizeonclose') + except Exception: + pass + + if minimizeonclose: + # minimize the application + event.ignore() else: - # custom quit method + # quit the application + event.accept() self.quit() def on_action_InboxMessageForceHtml(self): - msgid = self.getCurrentMessageId() - textEdit = self.getCurrentMessageTextedit() - if not msgid: - return - queryreturn = sqlQuery( - '''select message from inbox where msgid=?''', msgid) - if queryreturn != []: - for row in queryreturn: - messageText, = row - - lines = messageText.split('\n') - totalLines = len(lines) - for i in xrange(totalLines): - if 'Message ostensibly from ' in lines[i]: - lines[i] = '

%s

' % ( - lines[i]) + currentInboxRow = self.ui.tableWidgetInbox.currentRow() + lines = self.ui.tableWidgetInbox.item(currentInboxRow,2).data(Qt.UserRole).toPyObject().split('\n') + for i in xrange(len(lines)): + if lines[i].contains('Message ostensibly from '): + lines[i] = '

%s

' % (lines[i]) elif lines[i] == '------------------------------------------------------': lines[i] = '
' - elif lines[i] == '' and (i+1) < totalLines and \ - lines[i+1] != '------------------------------------------------------': - lines[i] = '

' - content = ' '.join(lines) # To keep the whitespace between lines - content = shared.fixPotentiallyInvalidUTF8Data(content) - content = unicode(content, 'utf-8)') - textEdit.setHtml(QtCore.QString(content)) + content = '' + for i in xrange(len(lines)): + content += lines[i] + content = content.replace('\n\n', '

') + self.ui.textEditInboxMessage.setHtml(QtCore.QString(content)) - def on_action_InboxMarkUnread(self): - tableWidget = self.getCurrentMessagelist() - if not tableWidget: - return + def on_action_InboxReply(self): + currentInboxRow = self.ui.tableWidgetInbox.currentRow() + toAddressAtCurrentInboxRow = str(self.ui.tableWidgetInbox.item(currentInboxRow,0).data(Qt.UserRole).toPyObject()) + fromAddressAtCurrentInboxRow = str(self.ui.tableWidgetInbox.item(currentInboxRow,1).data(Qt.UserRole).toPyObject()) - msgids = set() - # 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. - - # 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) - - def quote_line(line): - # Do quote empty lines. - if line == '' or line.isspace(): - return '> ' - # Quote already quoted lines, but do not wrap them. - elif line[0:2] == '> ': - return '> ' + line - # Wrap and quote lines/paragraphs new to this message. - else: - return quoteWrapper.fill(line) - return '\n'.join([quote_line(l) for l in message.splitlines()]) + '\n\n' - - def setSendFromComboBox(self, address=None): - if address is None: - messagelist = self.getCurrentMessagelist() - if not messagelist: + if toAddressAtCurrentInboxRow == self.str_broadcast_subscribers: + self.ui.labelFrom.setText('') + else: + if not shared.config.get(toAddressAtCurrentInboxRow,'enabled'): + self.statusBar().showMessage('Error: The address from which you are trying to send is disabled. Enable it from the \'Your Identities\' tab first.') 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 - 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 - tableWidget = self.getCurrentMessagelist() - if not tableWidget: - return - - if reply_type is None: - reply_type = 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 - acct = accountClass(toAddressAtCurrentInboxRow) - fromAddressAtCurrentInboxRow = tableWidget.item( - currentInboxRow, column_from).address - msgid = tableWidget.item(currentInboxRow, 3).data() - queryreturn = sqlQuery( - "SELECT message FROM inbox WHERE msgid=?", msgid - ) or sqlQuery("SELECT message FROM sent WHERE ackdata=?", msgid) - if queryreturn != []: - for row in queryreturn: - messageAtCurrentInboxRow, = row - acct.parseMessage( - toAddressAtCurrentInboxRow, fromAddressAtCurrentInboxRow, - tableWidget.item(currentInboxRow, 2).subject, - messageAtCurrentInboxRow) - widget = { - 'subject': self.ui.lineEditSubject, - 'from': self.ui.comboBoxSendFrom, - 'message': self.ui.textEditMessage - } - - if toAddressAtCurrentInboxRow == str_broadcast_subscribers: - self.ui.tabWidgetSend.setCurrentIndex( - self.ui.tabWidgetSend.indexOf(self.ui.sendDirect) - ) -# toAddressAtCurrentInboxRow = fromAddressAtCurrentInboxRow - elif not config.has_section(toAddressAtCurrentInboxRow): - QtGui.QMessageBox.information( - self, _translate("MainWindow", "Address is gone"), - _translate( - "MainWindow", - "Bitmessage cannot find your address %1. Perhaps you" - " removed it?" - ).arg(toAddressAtCurrentInboxRow), QtGui.QMessageBox.Ok) - elif not config.getboolean( - toAddressAtCurrentInboxRow, 'enabled'): - QtGui.QMessageBox.information( - self, _translate("MainWindow", "Address disabled"), - _translate( - "MainWindow", - "Error: The address from which you are trying to send" - " is disabled. You\'ll have to enable it on the" - " \'Your Identities\' tab before using it." - ), QtGui.QMessageBox.Ok) + self.ui.labelFrom.setText(toAddressAtCurrentInboxRow) + self.ui.lineEditTo.setText(str(fromAddressAtCurrentInboxRow)) + self.ui.comboBoxSendFrom.setCurrentIndex(0) + #self.ui.comboBoxSendFrom.setEditText(str(self.ui.tableWidgetInbox.item(currentInboxRow,0).text)) + self.ui.textEditMessage.setText('\n\n------------------------------------------------------\n'+self.ui.tableWidgetInbox.item(currentInboxRow,2).data(Qt.UserRole).toPyObject()) + if self.ui.tableWidgetInbox.item(currentInboxRow,2).text()[0:3] == 'Re:': + self.ui.lineEditSubject.setText(self.ui.tableWidgetInbox.item(currentInboxRow,2).text()) else: - self.setBroadcastEnablementDependingOnWhetherThisIsAMailingListAddress(toAddressAtCurrentInboxRow) - broadcast_tab_index = self.ui.tabWidgetSend.indexOf( - self.ui.sendBroadcast - ) - if self.ui.tabWidgetSend.currentIndex() == broadcast_tab_index: - widget = { - 'subject': self.ui.lineEditSubjectBroadcast, - 'from': self.ui.comboBoxSendFromBroadcast, - 'message': self.ui.textEditMessageBroadcast - } - self.ui.tabWidgetSend.setCurrentIndex(broadcast_tab_index) - toAddressAtCurrentInboxRow = fromAddressAtCurrentInboxRow - if fromAddressAtCurrentInboxRow == \ - tableWidget.item(currentInboxRow, column_from).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(str(toAddressAtCurrentInboxRow)) - else: - self.ui.lineEditTo.setText( - tableWidget.item(currentInboxRow, column_to).accountString() - ) - - self.setSendFromComboBox(toAddressAtCurrentInboxRow) - - 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) - else: - widget['subject'].setText( - 'Re: ' + tableWidget.item(currentInboxRow, 2).label) - self.ui.tabWidget.setCurrentIndex( - self.ui.tabWidget.indexOf(self.ui.send) - ) - widget['message'].setFocus() + self.ui.lineEditSubject.setText('Re: '+self.ui.tableWidgetInbox.item(currentInboxRow,2).text()) + self.ui.radioButtonSpecific.setChecked(True) + self.ui.tabWidget.setCurrentIndex(1) def on_action_InboxAddSenderToAddressBook(self): - tableWidget = self.getCurrentMessagelist() - if not tableWidget: - return - currentInboxRow = tableWidget.currentRow() - 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)) - - def on_action_InboxAddSenderToBlackList(self): - tableWidget = self.getCurrentMessagelist() - if not tableWidget: - return - currentInboxRow = tableWidget.currentRow() - addressAtCurrentInboxRow = tableWidget.item( - currentInboxRow, 1).data(QtCore.Qt.UserRole) - recipientAddress = tableWidget.item( - currentInboxRow, 0).data(QtCore.Qt.UserRole) - # Let's make sure that it isn't already in the address book - queryreturn = sqlQuery('''select * from blacklist where address=?''', - addressAtCurrentInboxRow) + currentInboxRow = self.ui.tableWidgetInbox.currentRow() + #self.ui.tableWidgetInbox.item(currentRow,1).data(Qt.UserRole).toPyObject() + addressAtCurrentInboxRow = str(self.ui.tableWidgetInbox.item(currentInboxRow,1).data(Qt.UserRole).toPyObject()) + #Let's make sure that it isn't already in the address book + shared.sqlLock.acquire() + t = (addressAtCurrentInboxRow,) + shared.sqlSubmitQueue.put('''select * from addressbook where address=?''') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() if queryreturn == []: - label = "\"" + tableWidget.item(currentInboxRow, 2).subject + "\" in " + 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.ui.tableWidgetAddressBook.insertRow(0) + newItem = QtGui.QTableWidgetItem('--New entry. Change label in Address Book.--') + self.ui.tableWidgetAddressBook.setItem(0,0,newItem) + newItem = QtGui.QTableWidgetItem(addressAtCurrentInboxRow) + newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled ) + self.ui.tableWidgetAddressBook.setItem(0,1,newItem) + t = ('--New entry. Change label in Address Book.--',addressAtCurrentInboxRow) + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''INSERT INTO addressbook VALUES (?,?)''') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() + self.ui.tabWidget.setCurrentIndex(5) + self.ui.tableWidgetAddressBook.setCurrentCell(0,0) + self.statusBar().showMessage('Entry added to the Address Book. 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('Error: You cannot add the same address to your address book twice. Try renaming the existing one if you want.') - def deleteRowFromMessagelist( - self, row=None, inventoryHash=None, ackData=None, messageLists=None - ): - if messageLists is None: - messageLists = ( - self.ui.tableWidgetInbox, - self.ui.tableWidgetInboxChans, - self.ui.tableWidgetInboxSubscriptions - ) - elif type(messageLists) not in (list, tuple): - messageLists = (messageLists,) - for messageList in messageLists: - if row is not None: - inventoryHash = messageList.item(row, 3).data() - messageList.removeRow(row) - elif inventoryHash is not None: - for i in range(messageList.rowCount() - 1, -1, -1): - if messageList.item(i, 3).data() == 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: - messageList.removeRow(i) - - # Send item on the Inbox tab to trash + #Send item on the Inbox tab to trash def on_action_InboxTrash(self): - tableWidget = self.getCurrentMessagelist() - if not tableWidget: - return - 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.")) + while self.ui.tableWidgetInbox.selectedIndexes() != []: + currentRow = self.ui.tableWidgetInbox.selectedIndexes()[0].row() + inventoryHashToTrash = str(self.ui.tableWidgetInbox.item(currentRow,3).data(Qt.UserRole).toPyObject()) + t = (inventoryHashToTrash,) + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''UPDATE inbox SET folder='trash' WHERE msgid=?''') + shared.sqlSubmitQueue.put(t) + shared.sqlReturnQueue.get() + shared.sqlLock.release() + self.ui.textEditInboxMessage.setText("") + self.ui.tableWidgetInbox.removeRow(currentRow) + self.statusBar().showMessage('Moved items to trash. There is no user interface to view your trash, but it is still on disk if you are desperate to get it back.') + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() + if currentRow == 0: + self.ui.tableWidgetInbox.selectRow(currentRow) + else: + self.ui.tableWidgetInbox.selectRow(currentRow-1) - def on_action_TrashUndelete(self): - tableWidget = self.getCurrentMessagelist() - if not tableWidget: - return - 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() - 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.")) - - def on_action_InboxSaveMessageAs(self): - tableWidget = self.getCurrentMessagelist() - if not tableWidget: - return - currentInboxRow = tableWidget.currentRow() - try: - subjectAtCurrentInboxRow = str(tableWidget.item( - currentInboxRow, 2).data(QtCore.Qt.UserRole)) - except: - subjectAtCurrentInboxRow = '' - - # Retrieve the message data out of the SQL database - msgid = tableWidget.item(currentInboxRow, 3).data() - queryreturn = sqlQuery( - '''select message from inbox where msgid=?''', msgid) - if queryreturn != []: - for row in queryreturn: - 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 (*.*)") - if filename == '': - return - try: - f = open(filename, 'w') - f.write(message) - f.close() - except Exception: - logger.exception('Message not saved', exc_info=True) - self.updateStatusBar(_translate("MainWindow", "Write error.")) - - # Send item on the Sent tab to trash + #Send item on the Sent tab to trash def on_action_SentTrash(self): - tableWidget = self.getCurrentMessagelist() - if not tableWidget: - return - folder = self.getCurrentFolder() - shifted = QtGui.QApplication.queryKeyboardModifiers() & QtCore.Qt.ShiftModifier - 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 - ) - self.getCurrentMessageTextedit().setPlainText("") - tableWidget.removeRow(currentRow) - self.updateStatusBar(_translate( - "MainWindow", "Moved items to trash.")) - - self.ui.tableWidgetInbox.selectRow( - currentRow if currentRow == 0 else currentRow - 1) - - def on_action_ForceSend(self): - currentRow = self.ui.tableWidgetInbox.currentRow() - addressAtCurrentRow = self.ui.tableWidgetInbox.item( - currentRow, 0).data(QtCore.Qt.UserRole) - toRipe = decodeAddress(addressAtCurrentRow)[3] - sqlExecute( - '''UPDATE sent SET status='forcepow' WHERE toripe=? AND status='toodifficult' and folder='sent' ''', - toRipe) - queryreturn = sqlQuery('''select ackdata FROM sent WHERE status='forcepow' ''') - for row in queryreturn: - ackdata, = row - queues.UISignalQueue.put(('updateSentItemStatusByAckdata', ( - ackdata, 'Overriding maximum-difficulty setting. Work queued.'))) - queues.workerQueue.put(('sendmessage', '')) + while self.ui.tableWidgetSent.selectedIndexes() != []: + currentRow = self.ui.tableWidgetSent.selectedIndexes()[0].row() + ackdataToTrash = str(self.ui.tableWidgetSent.item(currentRow,3).data(Qt.UserRole).toPyObject()) + t = (ackdataToTrash,) + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''UPDATE sent SET folder='trash' WHERE ackdata=?''') + shared.sqlSubmitQueue.put(t) + shared.sqlReturnQueue.get() + shared.sqlLock.release() + self.ui.textEditSentMessage.setPlainText("") + self.ui.tableWidgetSent.removeRow(currentRow) + self.statusBar().showMessage('Moved items to trash. There is no user interface to view your trash, but it is still on disk if you are desperate to get it back.') + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() + if currentRow == 0: + self.ui.tableWidgetSent.selectRow(currentRow) + else: + self.ui.tableWidgetSent.selectRow(currentRow-1) def on_action_SentClipboard(self): - currentRow = self.ui.tableWidgetInbox.currentRow() - addressAtCurrentRow = self.ui.tableWidgetInbox.item( - currentRow, 0).data(QtCore.Qt.UserRole) + currentRow = self.ui.tableWidgetSent.currentRow() + addressAtCurrentRow = str(self.ui.tableWidgetSent.item(currentRow,0).data(Qt.UserRole).toPyObject()) clipboard = QtGui.QApplication.clipboard() clipboard.setText(str(addressAtCurrentRow)) - # Group of functions for the Address Book dialog box + #Group of functions for the Address Book dialog box def on_action_AddressBookNew(self): self.click_pushButtonAddAddressBook() - def on_action_AddressBookDelete(self): 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) + currentRow = self.ui.tableWidgetAddressBook.selectedIndexes()[0].row() + labelAtCurrentRow = self.ui.tableWidgetAddressBook.item(currentRow,0).text().toUtf8() + addressAtCurrentRow = self.ui.tableWidgetAddressBook.item(currentRow,1).text() + t = (str(labelAtCurrentRow),str(addressAtCurrentRow)) + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''DELETE FROM addressbook WHERE label=? AND address=?''') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() self.ui.tableWidgetAddressBook.removeRow(currentRow) - self.rerenderMessagelistFromLabels() - self.rerenderMessagelistToLabels() - + self.rerenderInboxFromLabels() + self.rerenderSentToLabels() 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,1).text() + if self.ui.lineEditTo.text() == '': + self.ui.lineEditTo.setText(str(addressAtCurrentRow)) 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) - ) - - 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.")) - continue - self.addSubscription(item.address, item.label) - self.ui.tabWidget.setCurrentIndex( - self.ui.tabWidget.indexOf(self.ui.subscriptions) - ) - + self.ui.lineEditTo.setText(str(self.ui.lineEditTo.text()) + '; '+ str(addressAtCurrentRow)) + if listOfSelectedRows == {}: + self.statusBar().showMessage('No addresses selected.') + else: + self.statusBar().showMessage('') + self.ui.tabWidget.setCurrentIndex(1) def on_context_menuAddressBook(self, point): - self.popMenuAddressBook = QtGui.QMenu(self) - self.popMenuAddressBook.addAction(self.actionAddressBookSend) - 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: - 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)) + self.popMenuAddressBook.exec_( self.ui.tableWidgetAddressBook.mapToGlobal(point) ) - # Group of functions for the Subscriptions dialog box + + #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: - return - address = self.getCurrentAccount() - sqlExecute('''DELETE FROM subscriptions WHERE address=?''', - address) - self.rerenderTabTreeSubscriptions() - self.rerenderMessagelistFromLabels() - self.rerenderAddressBook() + print 'clicked Delete' + currentRow = self.ui.tableWidgetSubscriptions.currentRow() + labelAtCurrentRow = self.ui.tableWidgetSubscriptions.item(currentRow,0).text().toUtf8() + addressAtCurrentRow = self.ui.tableWidgetSubscriptions.item(currentRow,1).text() + t = (str(labelAtCurrentRow),str(addressAtCurrentRow)) + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''DELETE FROM subscriptions WHERE label=? AND address=?''') + shared.sqlSubmitQueue.put(t) + shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() + self.ui.tableWidgetSubscriptions.removeRow(currentRow) + self.rerenderInboxFromLabels() shared.reloadBroadcastSendersForWhichImWatching() - def on_action_SubscriptionsClipboard(self): - address = self.getCurrentAccount() + currentRow = self.ui.tableWidgetSubscriptions.currentRow() + addressAtCurrentRow = self.ui.tableWidgetSubscriptions.item(currentRow,1).text() clipboard = QtGui.QApplication.clipboard() - clipboard.setText(str(address)) - + clipboard.setText(str(addressAtCurrentRow)) def on_action_SubscriptionsEnable(self): - address = self.getCurrentAccount() - sqlExecute( - '''update subscriptions set enabled=1 WHERE address=?''', - address) - account = self.getCurrentItem() - account.setEnabled(True) - self.rerenderAddressBook() + currentRow = self.ui.tableWidgetSubscriptions.currentRow() + labelAtCurrentRow = self.ui.tableWidgetSubscriptions.item(currentRow,0).text().toUtf8() + addressAtCurrentRow = self.ui.tableWidgetSubscriptions.item(currentRow,1).text() + t = (str(labelAtCurrentRow),str(addressAtCurrentRow)) + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''update subscriptions set enabled=1 WHERE label=? AND address=?''') + shared.sqlSubmitQueue.put(t) + shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() + self.ui.tableWidgetSubscriptions.item(currentRow,0).setTextColor(QtGui.QColor(0,0,0)) + self.ui.tableWidgetSubscriptions.item(currentRow,1).setTextColor(QtGui.QColor(0,0,0)) shared.reloadBroadcastSendersForWhichImWatching() - def on_action_SubscriptionsDisable(self): - address = self.getCurrentAccount() - sqlExecute( - '''update subscriptions set enabled=0 WHERE address=?''', - address) - account = self.getCurrentItem() - account.setEnabled(False) - self.rerenderAddressBook() + currentRow = self.ui.tableWidgetSubscriptions.currentRow() + labelAtCurrentRow = self.ui.tableWidgetSubscriptions.item(currentRow,0).text().toUtf8() + addressAtCurrentRow = self.ui.tableWidgetSubscriptions.item(currentRow,1).text() + t = (str(labelAtCurrentRow),str(addressAtCurrentRow)) + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''update subscriptions set enabled=0 WHERE label=? AND address=?''') + shared.sqlSubmitQueue.put(t) + shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() + self.ui.tableWidgetSubscriptions.item(currentRow,0).setTextColor(QtGui.QColor(128,128,128)) + self.ui.tableWidgetSubscriptions.item(currentRow,1).setTextColor(QtGui.QColor(128,128,128)) shared.reloadBroadcastSendersForWhichImWatching() - 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.popMenuSubscriptions.exec_( self.ui.tableWidgetSubscriptions.mapToGlobal(point) ) - self._contact_selected = currentItem - # preloaded gui.menu plugins with prefix 'address' - for plugin in self.menu_plugins['address']: - self.popMenuSubscriptions.addAction(plugin) - self.popMenuSubscriptions.addSeparator() - if self.getCurrentFolder() != 'sent': - self.popMenuSubscriptions.addAction(self.actionMarkAllRead) - if self.popMenuSubscriptions.isEmpty(): - return - self.popMenuSubscriptions.exec_( - self.ui.treeWidgetSubscriptions.mapToGlobal(point)) - - def widgetConvert(self, widget): - if widget == self.ui.tableWidgetInbox: - return self.ui.treeWidgetYourIdentities - elif widget == self.ui.tableWidgetInboxSubscriptions: - return self.ui.treeWidgetSubscriptions - elif widget == self.ui.tableWidgetInboxChans: - return self.ui.treeWidgetChans - elif widget == self.ui.treeWidgetYourIdentities: - return self.ui.tableWidgetInbox - elif widget == self.ui.treeWidgetSubscriptions: - return self.ui.tableWidgetInboxSubscriptions - elif widget == self.ui.treeWidgetChans: - return self.ui.tableWidgetInboxChans + #Group of functions for the Blacklist dialog box + def on_action_BlacklistNew(self): + self.click_pushButtonAddBlacklist() + def on_action_BlacklistDelete(self): + currentRow = self.ui.tableWidgetBlacklist.currentRow() + labelAtCurrentRow = self.ui.tableWidgetBlacklist.item(currentRow,0).text().toUtf8() + addressAtCurrentRow = self.ui.tableWidgetBlacklist.item(currentRow,1).text() + t = (str(labelAtCurrentRow),str(addressAtCurrentRow)) + shared.sqlLock.acquire() + if shared.config.get('bitmessagesettings', 'blackwhitelist') == 'black': + shared.sqlSubmitQueue.put('''DELETE FROM blacklist WHERE label=? AND address=?''') + shared.sqlSubmitQueue.put(t) + shared.sqlReturnQueue.get() else: - return None - - def getCurrentTreeWidget(self): - 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] + shared.sqlSubmitQueue.put('''DELETE FROM whitelist WHERE label=? AND address=?''') + shared.sqlSubmitQueue.put(t) + shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() + self.ui.tableWidgetBlacklist.removeRow(currentRow) + def on_action_BlacklistClipboard(self): + currentRow = self.ui.tableWidgetBlacklist.currentRow() + addressAtCurrentRow = self.ui.tableWidgetBlacklist.item(currentRow,1).text() + clipboard = QtGui.QApplication.clipboard() + clipboard.setText(str(addressAtCurrentRow)) + def on_context_menuBlacklist(self, point): + self.popMenuBlacklist.exec_( self.ui.tableWidgetBlacklist.mapToGlobal(point) ) + def on_action_BlacklistEnable(self): + currentRow = self.ui.tableWidgetBlacklist.currentRow() + addressAtCurrentRow = self.ui.tableWidgetBlacklist.item(currentRow,1).text() + self.ui.tableWidgetBlacklist.item(currentRow,0).setTextColor(QtGui.QColor(0,0,0)) + self.ui.tableWidgetBlacklist.item(currentRow,1).setTextColor(QtGui.QColor(0,0,0)) + t = (str(addressAtCurrentRow),) + shared.sqlLock.acquire() + if shared.config.get('bitmessagesettings', 'blackwhitelist') == 'black': + shared.sqlSubmitQueue.put('''UPDATE blacklist SET enabled=1 WHERE address=?''') + shared.sqlSubmitQueue.put(t) + shared.sqlReturnQueue.get() else: - return False - - def getAccountTreeWidget(self, account): - try: - if account.type == AccountMixin.CHAN: - return self.ui.treeWidgetChans - elif account.type == AccountMixin.SUBSCRIPTION: - return self.ui.treeWidgetSubscriptions - else: - return self.ui.treeWidgetYourIdentities - except: - return self.ui.treeWidgetYourIdentities - - def getCurrentMessagelist(self): - 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] - - def getAccountMessagelist(self, account): - try: - if account.type == AccountMixin.CHAN: - return self.ui.tableWidgetInboxChans - elif account.type == AccountMixin.SUBSCRIPTION: - return self.ui.tableWidgetInboxSubscriptions - else: - return self.ui.tableWidgetInbox - except: - return self.ui.tableWidgetInbox - - def getCurrentMessageId(self): - messagelist = self.getCurrentMessagelist() - if messagelist: - currentRow = messagelist.currentRow() - if currentRow >= 0: - return messagelist.item(currentRow, 3).data() - - def getCurrentMessageTextedit(self): - 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] - - def getAccountTextedit(self, account): - try: - if account.type == AccountMixin.CHAN: - return self.ui.textEditInboxMessageChans - elif account.type == AccountMixin.SUBSCRIPTION: - return self.ui.textEditInboxSubscriptions - else: - return self.ui.textEditInboxMessage - except: - return self.ui.textEditInboxMessage - - def getCurrentSearchLine(self, currentIndex=None, retObj=False): - if currentIndex is None: - 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()) - - def getCurrentSearchOption(self, currentIndex=None): - if currentIndex is None: - 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() - - # Group of functions for the Your Identities dialog box - def getCurrentItem(self, treeWidget=None): - if treeWidget is None: - treeWidget = self.getCurrentTreeWidget() - if treeWidget: - return treeWidget.currentItem() - - def getCurrentAccount(self, treeWidget=None): - currentItem = self.getCurrentItem(treeWidget) - if currentItem: - return currentItem.address - - def getCurrentFolder(self, treeWidget=None): - currentItem = self.getCurrentItem(treeWidget) - try: - return currentItem.folderName - except AttributeError: - pass - - def setCurrentItemColor(self, color): - currentItem = self.getCurrentItem() - if currentItem: - brush = QtGui.QBrush() - brush.setStyle(QtCore.Qt.NoBrush) - brush.setColor(color) - 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 - ] + shared.sqlSubmitQueue.put('''UPDATE whitelist SET enabled=1 WHERE address=?''') + shared.sqlSubmitQueue.put(t) + shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() + def on_action_BlacklistDisable(self): + currentRow = self.ui.tableWidgetBlacklist.currentRow() + addressAtCurrentRow = self.ui.tableWidgetBlacklist.item(currentRow,1).text() + self.ui.tableWidgetBlacklist.item(currentRow,0).setTextColor(QtGui.QColor(128,128,128)) + self.ui.tableWidgetBlacklist.item(currentRow,1).setTextColor(QtGui.QColor(128,128,128)) + t = (str(addressAtCurrentRow),) + shared.sqlLock.acquire() + if shared.config.get('bitmessagesettings', 'blackwhitelist') == 'black': + shared.sqlSubmitQueue.put('''UPDATE blacklist SET enabled=0 WHERE address=?''') + shared.sqlSubmitQueue.put(t) + shared.sqlReturnQueue.get() + else: + shared.sqlSubmitQueue.put('''UPDATE whitelist SET enabled=0 WHERE address=?''') + shared.sqlSubmitQueue.put(t) + shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() + #Group of functions for the Your Identities dialog box 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 - 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)) - else: - return - else: - return - config.save() + def on_action_YourIdentitiesEnable(self): + currentRow = self.ui.tableWidgetYourIdentities.currentRow() + addressAtCurrentRow = str(self.ui.tableWidgetYourIdentities.item(currentRow,1).text()) + shared.config.set(addressAtCurrentRow,'enabled','true') + with open(shared.appdata + 'keys.dat', 'wb') as configfile: + shared.config.write(configfile) + self.ui.tableWidgetYourIdentities.item(currentRow,0).setTextColor(QtGui.QColor(0,0,0)) + self.ui.tableWidgetYourIdentities.item(currentRow,1).setTextColor(QtGui.QColor(0,0,0)) + self.ui.tableWidgetYourIdentities.item(currentRow,2).setTextColor(QtGui.QColor(0,0,0)) + if shared.safeConfigGetBoolean(addressAtCurrentRow,'mailinglist'): + self.ui.tableWidgetYourIdentities.item(currentRow,1).setTextColor(QtGui.QColor(137,04,177)) shared.reloadMyAddressHashes() - self.rerenderAddressBook() - self.rerenderComboBoxSendFrom() - if account.type == AccountMixin.NORMAL: - self.rerenderTabTreeMessages() - elif account.type == AccountMixin.CHAN: - self.rerenderTabTreeChans() - - def on_action_Enable(self): - addressAtCurrentRow = self.getCurrentAccount() - self.enableIdentity(addressAtCurrentRow) - account = self.getCurrentItem() - account.setEnabled(True) - - def enableIdentity(self, address): - config.set(address, 'enabled', 'true') - config.save() + def on_action_YourIdentitiesDisable(self): + currentRow = self.ui.tableWidgetYourIdentities.currentRow() + addressAtCurrentRow = str(self.ui.tableWidgetYourIdentities.item(currentRow,1).text()) + shared.config.set(str(addressAtCurrentRow),'enabled','false') + self.ui.tableWidgetYourIdentities.item(currentRow,0).setTextColor(QtGui.QColor(128,128,128)) + self.ui.tableWidgetYourIdentities.item(currentRow,1).setTextColor(QtGui.QColor(128,128,128)) + self.ui.tableWidgetYourIdentities.item(currentRow,2).setTextColor(QtGui.QColor(128,128,128)) + if shared.safeConfigGetBoolean(addressAtCurrentRow,'mailinglist'): + self.ui.tableWidgetYourIdentities.item(currentRow,1).setTextColor(QtGui.QColor(137,04,177)) + with open(shared.appdata + 'keys.dat', 'wb') as configfile: + shared.config.write(configfile) shared.reloadMyAddressHashes() - self.rerenderAddressBook() - - def on_action_Disable(self): - address = self.getCurrentAccount() - self.disableIdentity(address) - account = self.getCurrentItem() - account.setEnabled(False) - - def disableIdentity(self, address): - config.set(str(address), 'enabled', 'false') - config.save() - shared.reloadMyAddressHashes() - self.rerenderAddressBook() - - def on_action_Clipboard(self): - address = self.getCurrentAccount() + def on_action_YourIdentitiesClipboard(self): + currentRow = self.ui.tableWidgetYourIdentities.currentRow() + addressAtCurrentRow = self.ui.tableWidgetYourIdentities.item(currentRow,1).text() 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) - else: - myAddress = tableWidget.item(currentRow, 0).data(QtCore.Qt.UserRole) - otherAddress = tableWidget.item(currentRow, 1).data(QtCore.Qt.UserRole) - account = accountClass(myAddress) - if isinstance(account, GatewayAccount) and otherAddress == account.relayAddress and ( - (currentColumn in [0, 2] and self.getCurrentFolder() == "sent") or - (currentColumn in [1, 2] and self.getCurrentFolder() != "sent")): - text = str(tableWidget.item(currentRow, currentColumn).label) - else: - text = tableWidget.item(currentRow, currentColumn).data(QtCore.Qt.UserRole) - - clipboard = QtGui.QApplication.clipboard() - clipboard.setText(text) - - # 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( - currentRow, 1).text() - setToIdenticon = not self.setAvatar(addressAtCurrentRow) - if setToIdenticon: - thisTableWidget.item( - currentRow, 0).setIcon(avatarize(addressAtCurrentRow)) - - # TODO: reuse utils - def setAvatar(self, addressAtCurrentRow): - if not os.path.exists(state.appdata + 'avatars/'): - os.makedirs(state.appdata + 'avatars/') - hash = hashlib.md5(addBMIfNotPresent(addressAtCurrentRow)).hexdigest() - extensions = [ - 'PNG', 'GIF', 'JPG', 'JPEG', 'SVG', 'BMP', 'MNG', 'PBM', - 'PGM', 'PPM', 'TIFF', 'XBM', 'XPM', 'TGA'] - - names = { - 'BMP': 'Windows Bitmap', - 'GIF': 'Graphic Interchange Format', - 'JPG': 'Joint Photographic Experts Group', - 'JPEG': 'Joint Photographic Experts Group', - 'MNG': 'Multiple-image Network Graphics', - 'PNG': 'Portable Network Graphics', - 'PBM': 'Portable Bitmap', - 'PGM': 'Portable Graymap', - 'PPM': 'Portable Pixmap', - 'TIFF': 'Tagged Image File Format', - 'XBM': 'X11 Bitmap', - 'XPM': 'X11 Pixmap', - 'SVG': 'Scalable Vector Graphics', - 'TGA': 'Targa Image Format'} - 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() - 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) - ) - # determine the correct filename (note that avatars don't use the suffix) - destination = state.appdata + 'avatars/' + hash + '.' + sourcefile.split('.')[-1] - exists = QtCore.QFile.exists(destination) - if sourcefile == '': - # ask for removal of avatar - if exists | (len(current_files) > 0): - displayMsg = _translate( - "MainWindow", "Do you really want to remove this avatar?") - overwrite = QtGui.QMessageBox.question( - 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?") - overwrite = QtGui.QMessageBox.question( - 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: - for file in current_files: - QtCore.QFile.remove(file) - QtCore.QFile.remove(destination) - # copy it - if sourcefile != '': - copied = QtCore.QFile.copy(sourcefile, destination) - if not copied: - logger.error('couldn\'t copy :(') - # set the icon - self.rerenderTabTreeMessages() - self.rerenderTabTreeSubscriptions() - self.rerenderTabTreeChans() - self.rerenderComboBoxSendFrom() - self.rerenderComboBoxSendFromBroadcast() - self.rerenderMessagelistFromLabels() - self.rerenderMessagelistToLabels() - self.ui.blackwhitelist.rerenderBlackWhiteList() - # generate identicon - 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) - + clipboard.setText(str(addressAtCurrentRow)) 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(): - return - 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(): - return - self.popMenu.exec_( - self.ui.treeWidgetChans.mapToGlobal(point)) - + self.popMenu.exec_( self.ui.tableWidgetYourIdentities.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)) - + self.popMenuInbox.exec_( self.ui.tableWidgetInbox.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. - if currentRow >= 0: - ackData = self.ui.tableWidgetInbox.item(currentRow, 3).data() - queryreturn = sqlQuery('''SELECT status FROM sent where ackdata=?''', ackData) - for row in queryreturn: - status, = row - if status == 'toodifficult': - self.popMenuSent.addAction(self.actionForceSend) - - self.popMenuSent.exec_(self.ui.tableWidgetInbox.mapToGlobal(point)) - - def inboxSearchLineEditUpdated(self, text): - # dynamic search for too short text is slow - text = text.toUtf8() - if 0 < len(text) < 3: - return - messagelist = self.getCurrentMessagelist() - if messagelist: - searchOption = self.getCurrentSearchOption() - account = self.getCurrentAccount() - folder = self.getCurrentFolder() - self.loadMessagelist( - messagelist, account, folder, searchOption, text) - - def inboxSearchLineEditReturnPressed(self): - logger.debug("Search return pressed") - searchLine = self.getCurrentSearchLine() - messagelist = self.getCurrentMessagelist() - if messagelist and len(str(searchLine)) < 3: - searchOption = self.getCurrentSearchOption() - account = self.getCurrentAccount() - folder = self.getCurrentFolder() - self.loadMessagelist( - messagelist, account, folder, searchOption, searchLine) - messagelist.setFocus() - - def treeWidgetItemClicked(self): - messagelist = self.getCurrentMessagelist() - if not messagelist: - return - 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()) - - 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: - return - # not visible - if (not self.getCurrentItem()) or (not isinstance(self.getCurrentItem(), Ui_AddressWidget)): - return - # only currently selected item - if item.address != self.getCurrentAccount(): - return - # "All accounts" can't be renamed - if item.type == AccountMixin.ALL: - return - - newLabel = unicode(item.text(0), 'utf-8', 'ignore') - oldLabel = item.defaultLabel() - - # unchanged, do not do anything either - if newLabel == oldLabel: - return - - # recursion prevention - if self.recurDepth > 0: - return - - self.recurDepth += 1 - if item.type == AccountMixin.NORMAL or item.type == AccountMixin.MAILINGLIST: - self.rerenderComboBoxSendFromBroadcast() - if item.type == AccountMixin.NORMAL or item.type == AccountMixin.CHAN: - self.rerenderComboBoxSendFrom() - self.rerenderMessagelistFromLabels() - if item.type != AccountMixin.SUBSCRIPTION: - self.rerenderMessagelistToLabels() - if item.type in (AccountMixin.NORMAL, AccountMixin.CHAN, AccountMixin.SUBSCRIPTION): - self.rerenderAddressBook() - self.recurDepth -= 1 + self.popMenuSent.exec_( self.ui.tableWidgetSent.mapToGlobal(point) ) def tableWidgetInboxItemClicked(self): - messageTextedit = self.getCurrentMessageTextedit() - if not messageTextedit: - return + currentRow = self.ui.tableWidgetInbox.currentRow() + if currentRow >= 0: + fromAddress = str(self.ui.tableWidgetInbox.item(currentRow,1).data(Qt.UserRole).toPyObject()) + #If we have received this message from either a broadcast address or from someone in our address book, display as HTML + if decodeAddress(fromAddress)[3] in shared.broadcastSendersForWhichImWatching or shared.isAddressInMyAddressBook(fromAddress): + if len(self.ui.tableWidgetInbox.item(currentRow,2).data(Qt.UserRole).toPyObject()) < 30000: + self.ui.textEditInboxMessage.setText(self.ui.tableWidgetInbox.item(currentRow,2).data(Qt.UserRole).toPyObject())#Only show the first 30K characters + else: + self.ui.textEditInboxMessage.setText(self.ui.tableWidgetInbox.item(currentRow,2).data(Qt.UserRole).toPyObject()[:30000]+'\n\nDisplay of the remainder of the message truncated because it is too long.')#Only show the first 30K characters + else: + if len(self.ui.tableWidgetInbox.item(currentRow,2).data(Qt.UserRole).toPyObject()) < 30000: + self.ui.textEditInboxMessage.setPlainText(self.ui.tableWidgetInbox.item(currentRow,2).data(Qt.UserRole).toPyObject())#Only show the first 30K characters + else: + self.ui.textEditInboxMessage.setPlainText(self.ui.tableWidgetInbox.item(currentRow,2).data(Qt.UserRole).toPyObject()[:30000]+'\n\nDisplay of the remainder of the message truncated because it is too long.')#Only show the first 30K characters - 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 - ) + font = QFont() + font.setBold(False) + self.ui.tableWidgetInbox.item(currentRow,0).setFont(font) + self.ui.tableWidgetInbox.item(currentRow,1).setFont(font) + self.ui.tableWidgetInbox.item(currentRow,2).setFont(font) + self.ui.tableWidgetInbox.item(currentRow,3).setFont(font) - try: - message = queryreturn[-1][0] - except NameError: - message = "" - except IndexError: - message = _translate( - "MainWindow", - "Error occurred: could not load message from disk." - ) - else: - 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() + inventoryHash = str(self.ui.tableWidgetInbox.item(currentRow,3).data(Qt.UserRole).toPyObject()) + t = (inventoryHash,) + self.ubuntuMessagingMenuClear(t) + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put('''update inbox set read=1 WHERE msgid=?''') + shared.sqlSubmitQueue.put(t) + shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() - messageTextedit.setCurrentFont(QtGui.QFont()) - messageTextedit.setTextColor(QtGui.QColor()) - messageTextedit.setContent(message) + def tableWidgetSentItemClicked(self): + currentRow = self.ui.tableWidgetSent.currentRow() + if currentRow >= 0: + self.ui.textEditSentMessage.setPlainText(self.ui.tableWidgetSent.item(currentRow,2).data(Qt.UserRole).toPyObject()) - def tableWidgetAddressBookItemChanged(self, item): - if item.type == AccountMixin.CHAN: + def tableWidgetYourIdentitiesItemChanged(self): + currentRow = self.ui.tableWidgetYourIdentities.currentRow() + if currentRow >= 0: + addressAtCurrentRow = self.ui.tableWidgetYourIdentities.item(currentRow,1).text() + shared.config.set(str(addressAtCurrentRow),'label',str(self.ui.tableWidgetYourIdentities.item(currentRow,0).text().toUtf8())) + with open(shared.appdata + 'keys.dat', 'wb') as configfile: + shared.config.write(configfile) 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) + #self.rerenderInboxFromLabels() + self.rerenderInboxToLabels() + self.rerenderSentFromLabels() + #self.rerenderSentToLabels() - def tabWidgetCurrentChanged(self, n): - if n == self.ui.tabWidget.indexOf(self.ui.networkstatus): - self.ui.networkstatus.startUpdate() - else: - self.ui.networkstatus.stopUpdate() + def tableWidgetAddressBookItemChanged(self): + currentRow = self.ui.tableWidgetAddressBook.currentRow() + shared.sqlLock.acquire() + if currentRow >= 0: + addressAtCurrentRow = self.ui.tableWidgetAddressBook.item(currentRow,1).text() + t = (str(self.ui.tableWidgetAddressBook.item(currentRow,0).text().toUtf8()),str(addressAtCurrentRow)) + shared.sqlSubmitQueue.put('''UPDATE addressbook set label=? WHERE address=?''') + shared.sqlSubmitQueue.put(t) + shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() + self.rerenderInboxFromLabels() + self.rerenderSentToLabels() - def writeNewAddressToTable(self, label, address, streamNumber): - self.rerenderTabTreeMessages() - self.rerenderTabTreeSubscriptions() - self.rerenderTabTreeChans() + def tableWidgetSubscriptionsItemChanged(self): + currentRow = self.ui.tableWidgetSubscriptions.currentRow() + shared.sqlLock.acquire() + if currentRow >= 0: + addressAtCurrentRow = self.ui.tableWidgetSubscriptions.item(currentRow,1).text() + t = (str(self.ui.tableWidgetSubscriptions.item(currentRow,0).text().toUtf8()),str(addressAtCurrentRow)) + shared.sqlSubmitQueue.put('''UPDATE subscriptions set label=? WHERE address=?''') + shared.sqlSubmitQueue.put(t) + shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() + self.rerenderInboxFromLabels() + self.rerenderSentToLabels() + + def writeNewAddressToTable(self,label,address,streamNumber): + self.ui.tableWidgetYourIdentities.setSortingEnabled(False) + self.ui.tableWidgetYourIdentities.insertRow(0) + self.ui.tableWidgetYourIdentities.setItem(0, 0, QtGui.QTableWidgetItem(unicode(label,'utf-8'))) + newItem = QtGui.QTableWidgetItem(address) + newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled ) + self.ui.tableWidgetYourIdentities.setItem(0, 1, newItem) + newItem = QtGui.QTableWidgetItem(streamNumber) + newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled ) + self.ui.tableWidgetYourIdentities.setItem(0, 2, newItem) + #self.ui.tableWidgetYourIdentities.setSortingEnabled(True) self.rerenderComboBoxSendFrom() - self.rerenderComboBoxSendFromBroadcast() - self.rerenderAddressBook() - def updateStatusBar(self, data): + def updateStatusBar(self,data): + if data != "": + shared.printLock.acquire() + print 'Status bar:', data + shared.printLock.release() + self.statusBar().showMessage(data) + + + + + +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 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.checkBoxShowTrayNotifications.setChecked(shared.config.getboolean('bitmessagesettings', 'showtraynotifications')) + self.ui.checkBoxStartInTray.setChecked(shared.config.getboolean('bitmessagesettings', 'startintray')) + if shared.appdata == '': + self.ui.checkBoxPortableMode.setChecked(True) + if 'darwin' in sys.platform: + self.ui.checkBoxStartOnLogon.setDisabled(True) + self.ui.checkBoxMinimizeToTray.setDisabled(True) + self.ui.checkBoxShowTrayNotifications.setDisabled(True) + self.ui.checkBoxStartInTray.setDisabled(True) + self.ui.labelSettingsNote.setText('Options have been disabled because they either aren\'t applicable or because they haven\'t yet been implimented for your operating system.') + elif 'linux' in sys.platform: + self.ui.checkBoxStartOnLogon.setDisabled(True) + self.ui.checkBoxMinimizeToTray.setDisabled(True) + self.ui.checkBoxStartInTray.setDisabled(True) + self.ui.labelSettingsNote.setText('Options have been disabled because they either aren\'t applicable or because they haven\'t yet been implimented for your operating system.') + #On the Network settings tab: + self.ui.lineEditTCPPort.setText(str(shared.config.get('bitmessagesettings', 'port'))) + self.ui.checkBoxAuthentication.setChecked(shared.config.getboolean('bitmessagesettings', 'socksauthentication')) + 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) + 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.lineEditTotalDifficulty.setText(str((float(shared.config.getint('bitmessagesettings', 'defaultnoncetrialsperbyte'))/shared.networkDefaultProofOfWorkNonceTrialsPerByte))) + self.ui.lineEditSmallMessageDifficulty.setText(str((float(shared.config.getint('bitmessagesettings', 'defaultpayloadlengthextrabytes'))/shared.networkDefaultPayloadLengthExtraBytes))) + 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.lineEditTCPPort.setEnabled(True) + elif comboBoxIndex == 1 or comboBoxIndex == 2: + self.ui.lineEditSocksHostname.setEnabled(True) + self.ui.lineEditSocksPort.setEnabled(True) + self.ui.checkBoxAuthentication.setEnabled(True) + if self.ui.checkBoxAuthentication.isChecked(): + self.ui.lineEditSocksUsername.setEnabled(True) + self.ui.lineEditSocksPassword.setEnabled(True) + self.ui.lineEditTCPPort.setEnabled(False) + +class SpecialAddressBehaviorDialog(QtGui.QDialog): + def __init__(self,parent): + QtGui.QWidget.__init__(self, parent) + self.ui = Ui_SpecialAddressBehaviorDialog() + self.ui.setupUi(self) + self.parent = parent + currentRow = parent.ui.tableWidgetYourIdentities.currentRow() + addressAtCurrentRow = str(parent.ui.tableWidgetYourIdentities.item(currentRow,1).text()) + if shared.safeConfigGetBoolean(addressAtCurrentRow,'mailinglist'): + self.ui.radioButtonBehaviorMailingList.click() + else: + self.ui.radioButtonBehaveNormalAddress.click() try: - message, option = data - except ValueError: - option = 0 - message = data - except TypeError: - logger.debug( - 'Invalid argument for updateStatusBar!', exc_info=True) + mailingListName = shared.config.get(addressAtCurrentRow, 'mailinglistname') + except: + mailingListName = '' + self.ui.lineEditMailingListName.setText(unicode(mailingListName,'utf-8')) + QtGui.QWidget.resize(self,QtGui.QWidget.sizeHint(self)) - if message != "": - logger.info('Status bar: ' + message) +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.subscriptionAddressChanged) - if option == 1: - self.statusbar.addImportant(message) - else: - self.statusbar.showMessage(message, 10000) + def subscriptionAddressChanged(self,QString): + status,a,b,c = decodeAddress(str(QString)) + if status == 'missingbm': + self.ui.labelSubscriptionAddressCheck.setText('The address should start with ''BM-''') + elif status == 'checksumfailed': + self.ui.labelSubscriptionAddressCheck.setText('The address is not typed or copied correctly (the checksum failed).') + elif status == 'versiontoohigh': + self.ui.labelSubscriptionAddressCheck.setText('The version number of this address is higher than this software can support. Please upgrade Bitmessage.') + elif status == 'invalidcharacters': + self.ui.labelSubscriptionAddressCheck.setText('The address contains invalid characters.') + elif status == 'ripetooshort': + self.ui.labelSubscriptionAddressCheck.setText('Some data encoded in the address is too short.') + elif status == 'ripetoolong': + self.ui.labelSubscriptionAddressCheck.setText('Some data encoded in the address is too long.') + elif status == 'success': + self.ui.labelSubscriptionAddressCheck.setText('Address is valid.') - def resetNamecoinConnection(self): - namecoin.ensureNamecoinOptions() - self.namecoin = namecoin.namecoinConnection() +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. + while self.parent.ui.tableWidgetYourIdentities.item(row-1,1): + self.ui.radioButtonExisting.click() + #print self.parent.ui.tableWidgetYourIdentities.item(row-1,1).text() + self.ui.comboBoxExisting.addItem(self.parent.ui.tableWidgetYourIdentities.item(row-1,1).text()) + row += 1 + self.ui.groupBoxDeterministic.setHidden(True) + QtGui.QWidget.resize(self,QtGui.QWidget.sizeHint(self)) - # Check to see whether we can connect to namecoin. - # Hide the 'Fetch Namecoin ID' button if we can't. - if config.safeGetBoolean( - 'bitmessagesettings', 'dontconnect' - ) or self.namecoin.test()[0] == 'failed': - logger.warning( - 'There was a problem testing for a Namecoin daemon.' - ' Hiding the Fetch Namecoin ID button') - self.ui.pushButtonFetchNamecoinID.hide() - else: - self.ui.pushButtonFetchNamecoinID.show() - - def initSettings(self): - self.loadSettings() - for attr, obj in self.ui.__dict__.iteritems(): - if hasattr(obj, "__class__") and \ - isinstance(obj, settingsmixin.SettingsMixin): - loadMethod = getattr(obj, "loadSettings", None) - if callable(loadMethod): - obj.loadSettings() +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('You are using TCP port ' + str(shared.config.getint('bitmessagesettings', 'port')) + '. (This can be changed in the settings).') + QtGui.QWidget.resize(self,QtGui.QWidget.sizeHint(self)) -app = None -myapp = None +#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()) -class BitmessageQtApplication(QtGui.QApplication): - """ - Listener to allow our Qt form to get focus when another instance of the - application is open. - - Based off this nice reimplmentation of MySingleApplication: - http://stackoverflow.com/a/12712362/2679626 - """ - - # Unique identifier for this application - uuid = '6ec0149b-96e1-4be1-93ab-1465fb3ebf7c' - - def __init__(self, *argv): - super(BitmessageQtApplication, self).__init__(*argv) - id = BitmessageQtApplication.uuid - - QtCore.QCoreApplication.setOrganizationName("PyBitmessage") - QtCore.QCoreApplication.setOrganizationDomain("bitmessage.org") - QtCore.QCoreApplication.setApplicationName("pybitmessageqt") - - self.server = None - self.is_running = False - - socket = QLocalSocket() - socket.connectToServer(id) - self.is_running = socket.waitForConnected() - - # Cleanup past crashed servers - if not self.is_running: - if socket.error() == QLocalSocket.ConnectionRefusedError: - socket.disconnectFromServer() - QLocalServer.removeServer(id) - - socket.abort() - - # 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. - pass - else: - # Nope, create a local server with this id and assign on_new_connection - # for whenever a second instance tries to run focus the application. - self.server = QLocalServer() - 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): - if myapp: - myapp.appIndicatorShow() - - -def init(): - global app - if not app: - app = BitmessageQtApplication(sys.argv) - return app +class UISignaler(QThread): + def __init__(self, parent = None): + QThread.__init__(self, parent) + def run(self): + while True: + 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)) + elif command == 'updateStatusBar': + self.emit(SIGNAL("updateStatusBar(PyQt_PyObject)"),data) + elif command == 'updateSentItemStatusByHash': + hash, message = data + self.emit(SIGNAL("updateSentItemStatusByHash(PyQt_PyObject,PyQt_PyObject)"),hash,message) + elif command == 'updateSentItemStatusByAckdata': + ackData, message = data + self.emit(SIGNAL("updateSentItemStatusByAckdata(PyQt_PyObject,PyQt_PyObject)"),ackData,message) + elif command == 'displayNewInboxMessage': + inventoryHash,toAddress,fromAddress,subject,body = data + self.emit(SIGNAL("displayNewInboxMessage(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"),inventoryHash,toAddress,fromAddress,subject,body) + elif command == 'displayNewSentMessage': + toAddress,fromLabel,fromAddress,subject,message,ackdata = data + self.emit(SIGNAL("displayNewSentMessage(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"),toAddress,fromLabel,fromAddress,subject,message,ackdata) + elif command == 'updateNetworkStatusTab': + self.emit(SIGNAL("updateNetworkStatusTab()")) + elif command == 'incrementNumberOfMessagesProcessed': + self.emit(SIGNAL("incrementNumberOfMessagesProcessed()")) + elif command == 'incrementNumberOfPubkeysProcessed': + self.emit(SIGNAL("incrementNumberOfPubkeysProcessed()")) + elif command == 'incrementNumberOfBroadcastsProcessed': + self.emit(SIGNAL("incrementNumberOfBroadcastsProcessed()")) + elif command == 'setStatusIcon': + self.emit(SIGNAL("setStatusIcon(PyQt_PyObject)"),data) + elif command == 'rerenderInboxFromLabels': + self.emit(SIGNAL("rerenderInboxFromLabels()")) + elif command == 'rerenderSubscriptions': + self.emit(SIGNAL("rerenderSubscriptions()")) + else: + sys.stderr.write('Command sent to UISignaler not recognized: %s\n' % command) def run(): - global myapp - app = init() + app = QtGui.QApplication(sys.argv) + 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 - -# try: -# if config.get('bitmessagesettings', 'mailchuck') < 1: -# myapp.showMigrationWizard(config.get('bitmessagesettings', 'mailchuck')) -# except: -# myapp.showMigrationWizard(0) - - # only show after wizards and connect dialogs have completed - if not config.getboolean('bitmessagesettings', 'startintray'): + if shared.config.getboolean('bitmessagesettings', 'startintray'): + if not myapp.isUbuntu(): + myapp.trayIcon.show() + if 'win32' in sys.platform or 'win64' in sys.platform: + myapp.setWindowFlags(Qt.ToolTip) + else: myapp.show() - QtCore.QTimer.singleShot( - 30000, lambda: myapp.setStatusIcon(state.statusIconColor)) - app.exec_() + myapp.appIndicatorInit(app) + myapp.ubuntuMessagingMenuInit() + myapp.notifierInit() + + sys.exit(app.exec_()) diff --git a/src/bitmessageqt/about.ui b/src/bitmessageqt/about.ui deleted file mode 100644 index 49bd4eca..00000000 --- a/src/bitmessageqt/about.ui +++ /dev/null @@ -1,128 +0,0 @@ - - - aboutDialog - - - - 0 - 0 - 430 - 340 - - - - 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 - - - - - - - - - - - buttonBox - accepted() - aboutDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - aboutDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff --git a/src/bitmessageqt/account.py b/src/bitmessageqt/account.py deleted file mode 100644 index 8c82c6f6..00000000 --- a/src/bitmessageqt/account.py +++ /dev/null @@ -1,346 +0,0 @@ -# pylint: disable=too-many-instance-attributes,attribute-defined-outside-init -""" -account.py -========== - -Account related functions. - -""" - -from __future__ import absolute_import - -import inspect -import re -import sys -import time - -from PyQt4 import QtGui - -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 {} - """ - queryreturn = sqlQuery('SELECT label, address, enabled FROM subscriptions ORDER BY label COLLATE NOCASE ASC') - ret = {} - for row in queryreturn: - label, address, enabled = row - ret[address] = {} - ret[address]["inbox"] = {} - ret[address]["inbox"]['label'] = label - ret[address]["inbox"]['enabled'] = enabled - ret[address]["inbox"]['count'] = 0 - if count: - queryreturn = sqlQuery('''SELECT fromaddress, folder, count(msgid) as cnt - FROM inbox, subscriptions ON subscriptions.address = inbox.fromaddress - WHERE read = 0 AND toaddress = ? - GROUP BY inbox.fromaddress, folder''', str_broadcast_subscribers) - for row in queryreturn: - address, folder, cnt = row - if folder not in ret[address]: - ret[address][folder] = { - 'label': ret[address]['inbox']['label'], - 'enabled': ret[address]['inbox']['enabled'] - } - 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 address == str_broadcast_subscribers: - subscription = BroadcastAccount(address) - if subscription.type != AccountMixin.BROADCAST: - return None - else: - subscription = SubscriptionAccount(address) - if subscription.type != AccountMixin.SUBSCRIPTION: - # e.g. deleted chan - return NoAccount(address) - return subscription - try: - gateway = config.get(address, "gateway") - for _, cls in inspect.getmembers(sys.modules[__name__], inspect.isclass): - if issubclass(cls, GatewayAccount) and cls.gatewayName == gateway: - return cls(address) - # general gateway - return GatewayAccount(address) - except: - 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): - self.isEnabled = True - self.address = address - if address_type is None: - if address is None: - self.type = AccountMixin.ALL - elif config.safeGetBoolean(self.address, 'mailinglist'): - self.type = AccountMixin.MAILINGLIST - elif config.safeGetBoolean(self.address, 'chan'): - self.type = AccountMixin.CHAN - elif sqlQuery( - '''select label from subscriptions where address=?''', self.address): - self.type = AccountMixin.SUBSCRIPTION - else: - self.type = AccountMixin.NORMAL - else: - self.type = address_type - - -class BMAccount(object): - """Encapsulate a Bitmessage account""" - - def __init__(self, address=None): - self.address = address - self.type = AccountMixin.NORMAL - if config.has_section(address): - if config.safeGetBoolean(self.address, 'chan'): - self.type = AccountMixin.CHAN - elif config.safeGetBoolean(self.address, 'mailinglist'): - self.type = AccountMixin.MAILINGLIST - elif self.address == str_broadcast_subscribers: - self.type = AccountMixin.BROADCAST - else: - queryreturn = sqlQuery( - '''select label from subscriptions where address=?''', self.address) - if queryreturn: - self.type = AccountMixin.SUBSCRIPTION - - def getLabel(self, address=None): - """Get a label for this bitmessage account""" - if address is None: - address = self.address - label = config.safeGet(address, 'label', address) - queryreturn = sqlQuery( - '''select label from addressbook where address=?''', address) - if queryreturn != []: - for row in queryreturn: - label, = row - else: - queryreturn = sqlQuery( - '''select label from subscriptions where address=?''', address) - if queryreturn != []: - 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.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) - - 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) - sqlExecute( - '''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''', - '', - self.toAddress, - ripe, - self.fromAddress, - 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. - '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', self.toAddress)) - - -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+) (.*)") - - def __init__(self, address): - super(MailchuckAccount, self).__init__(address) - self.feedback = self.ALL_OK - - def createMessage(self, toAddress, fromAddress, subject, message): - """createMessage specific to a MailchuckAccount""" - self.subject = toAddress + " " + subject - self.toAddress = self.relayAddress - self.fromAddress = fromAddress - self.message = message - - def register(self, email): - """register specific to a MailchuckAccount""" - self.toAddress = self.registrationAddress - self.subject = email - self.message = "" - self.fromAddress = self.address - self.send() - - def unregister(self): - """unregister specific to a MailchuckAccount""" - self.toAddress = self.unregistrationAddress - self.subject = "" - self.message = "" - self.fromAddress = self.address - self.send() - - def status(self): - """status specific to a MailchuckAccount""" - self.toAddress = self.registrationAddress - self.subject = "status" - self.message = "" - self.fromAddress = self.address - 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 -# 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. -""") - self.fromAddress = self.address - - def parseMessage(self, toAddress, fromAddress, subject, message): - """parseMessage specific to a MailchuckAccount""" - - super(MailchuckAccount, self).parseMessage(toAddress, fromAddress, subject, message) - if fromAddress == self.relayAddress: - matches = self.regExpIncoming.search(subject) - if matches is not None: - self.subject = "" - if not matches.group(1) is None: - self.subject += matches.group(1) - if not matches.group(3) is None: - self.subject += matches.group(3) - if not matches.group(2) is None: - self.fromLabel = matches.group(2) - self.fromAddress = matches.group(2) - if toAddress == self.relayAddress: - matches = self.regExpOutgoing.search(subject) - if matches is not None: - if not matches.group(2) is None: - self.subject = matches.group(2) - if not matches.group(1) is None: - self.toLabel = matches.group(1) - self.toAddress = matches.group(1) - self.feedback = self.ALL_OK - if fromAddress == self.registrationAddress and self.subject == "Registration Request Denied": - self.feedback = self.REGISTRATION_DENIED - return self.feedback diff --git a/src/bitmessageqt/addpeer.ui b/src/bitmessageqt/addpeer.ui deleted file mode 100644 index 522759a8..00000000 --- a/src/bitmessageqt/addpeer.ui +++ /dev/null @@ -1,118 +0,0 @@ - - - Dialog - - - - 0 - 0 - 362 - 136 - - - - Dialog - - - - - - When you opened this dialog box, you had X peers in your list of peers. - - - - - - - You can manually add a peer here: - - - - - - - IP - - - - - - - Port - - - - - - - - - - - - - Add - - - - - - - Qt::Horizontal - - - - 47 - 20 - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Close - - - - - - - - - buttonBox - accepted() - Dialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - Dialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff --git a/src/bitmessageqt/address_dialogs.py b/src/bitmessageqt/address_dialogs.py deleted file mode 100644 index 3d3e5e37..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 -from account import AccountMixin, GatewayAccount, MailchuckAccount, accountClass -from addresses import addBMIfNotPresent, decodeAddress, encodeVarint -from bmconfigparser import config as global_config -from inventory import Inventory -from tr import _translate - - -class AddressCheckMixin(object): - """Base address validation class for QT UI""" - - def __init__(self): - self.valid = False - QtCore.QObject.connect( # pylint: disable=no-member - self.lineEditAddress, - QtCore.SIGNAL("textChanged(QString)"), - self.addressChanged) - - 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: - Inventory().flush() - doubleHashOfAddressData = hashlib.sha512(hashlib.sha512( - encodeVarint(addressVersion) - + encodeVarint(streamNumber) + ripe - ).digest()).digest() - tag = doubleHashOfAddressData[32:] - self.recent = Inventory().by_type_and_tag(3, tag) - count = len(self.recent) - if count == 0: - self.checkBoxDisplayMessagesAlreadyInInventory.setText( - _translate( - "MainWindow", - "There are no recent broadcasts from this address" - " to display." - )) - else: - self.checkBoxDisplayMessagesAlreadyInInventory.setEnabled(True) - self.checkBoxDisplayMessagesAlreadyInInventory.setText( - _translate( - "MainWindow", - "Display the %n recent broadcast(s) from this address.", - None, - QtCore.QCoreApplication.CodecForTr, - count - )) - - -class RegenerateAddressesDialog(QtGui.QDialog): - """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/bitmessage_icons.qrc b/src/bitmessageqt/bitmessage_icons.qrc deleted file mode 100644 index 68574630..00000000 --- a/src/bitmessageqt/bitmessage_icons.qrc +++ /dev/null @@ -1,25 +0,0 @@ - - - ../images/no_identicons.png - ../images/qidenticon_x.png - ../images/qidenticon.png - ../images/qidenticon_two.png - ../images/qidenticon_two_x.png - ../images/can-icon-24px-yellow.png - ../images/can-icon-24px-red.png - ../images/can-icon-24px-green.png - ../images/can-icon-24px.png - ../images/can-icon-16px.png - ../images/greenicon.png - ../images/redicon.png - ../images/yellowicon.png - ../images/addressbook.png - ../images/blacklist.png - ../images/identities.png - ../images/networkstatus.png - ../images/sent.png - ../images/subscriptions.png - ../images/send.png - ../images/inbox.png - - diff --git a/src/bitmessageqt/bitmessage_icons_rc.py b/src/bitmessageqt/bitmessage_icons_rc.py deleted file mode 100644 index bb0a02c0..00000000 --- a/src/bitmessageqt/bitmessage_icons_rc.py +++ /dev/null @@ -1,1675 +0,0 @@ -# -*- coding: utf-8 -*- - -# Resource object code -# -# Created: Sa 21. Sep 13:45:58 2013 -# by: The Resource Compiler for PyQt (Qt v4.8.4) -# -# WARNING! All changes made in this file will be lost! - -from PyQt4 import QtCore - -qt_resource_data = "\ -\x00\x00\x03\x66\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x03\x08\x49\x44\x41\x54\x78\xda\x84\ -\x53\x6d\x48\x53\x51\x18\x7e\xce\xfd\xd8\x75\x9b\x8e\xdc\x2c\xdd\ -\x4c\x5d\x4e\xa7\xc9\xe6\xb7\xf6\x61\x61\x11\x14\x52\x16\xf5\xc7\ -\x0a\x0b\xa2\x3f\x41\x51\x41\x61\x7f\x0a\x84\xa2\x9f\xfd\xeb\x67\ -\x7f\xfa\x51\x44\x50\x91\x14\x15\x5a\x14\x41\x25\x6d\x44\x59\x68\ -\x69\xd9\x74\xa6\x6d\xd7\x7d\x38\xb6\xdd\x6d\x77\xa7\x73\x2d\x4d\ -\x84\xe8\x81\x87\xf7\x7d\xef\x7b\xde\xe7\xbe\xe7\x9c\xf7\x10\x30\ -\x48\x84\x20\x4f\xb3\xf8\x8b\xb3\x1b\xe9\xbc\xe5\x38\x14\xb3\x74\ -\x2f\x73\xab\x18\x47\x28\x45\x6f\x36\x0b\xff\xc2\x3a\xde\xc6\xb2\ -\x06\xcd\x61\x24\x4b\x04\xbe\x87\x09\x48\x82\x89\x8a\xb8\x62\xaf\ -\x76\x75\x5a\x4a\xcb\x9d\x31\x85\xae\x9d\x0d\xce\x15\x7c\xf1\xa3\ -\xef\x67\x18\xd0\xc8\xe1\x1f\xf0\xcf\x01\x43\x53\xc4\xf1\x33\x04\ -\x57\x20\x12\x29\xcc\x31\x5b\x84\x4d\x7b\xf6\x18\xb5\x78\xcc\x0f\ -\x07\x23\x34\x0a\xcb\xea\x0a\x19\x4f\x32\xda\x19\xc7\x53\x04\x91\ -\x99\x10\xc4\xde\xd3\xa7\x61\x30\x1a\xa1\xb2\xde\xb5\x98\xe7\xb0\ -\x85\xe5\xc7\xb4\x02\x81\x2e\xa9\x66\xfe\xb9\x86\xd6\xd6\xfd\xee\ -\xba\x3a\xcb\x3b\x8f\x47\x9e\x78\xe7\x8d\xc5\x13\x88\x4a\x3a\x1d\ -\x94\x78\x1c\x82\x28\x22\xae\x6d\x8b\x47\x23\x5b\x7e\x6d\x5e\xa0\ -\xdd\xf9\x77\xe7\xcf\x3e\xd3\x0d\xbd\xa7\x3a\xac\x2e\xa7\x15\x43\ -\x9f\x6d\xd6\xae\x43\xde\xb0\x51\x44\x74\x6c\x78\x18\xf6\x8a\x0a\ -\x68\x96\xc5\x1a\x4a\x16\x6a\x84\xad\xce\xc5\xfa\xae\xc1\x69\x53\ -\x65\xbd\xdb\x8e\x74\x32\x09\xcd\xea\xf2\x4c\xb9\x0e\x5b\x94\x0c\ -\xdc\xba\xe9\x6d\xda\xbe\xa3\xd1\xf3\xe4\xb1\x37\xf7\xb7\x40\xc1\ -\xa2\x40\x26\xbb\x28\xc0\x75\xd5\x29\x23\xc9\xb9\xb9\x8d\x99\x74\ -\x1a\x2a\xe3\xae\xfa\xf4\xc7\xf1\x92\xa2\x60\xce\xc4\x0f\x4b\x85\ -\xb3\x0a\xcf\xfb\x6e\xd2\x57\xdd\x35\x1f\x73\x43\xc9\x47\x33\x25\ -\x26\x4c\x15\xe7\x82\x27\xb5\x07\x41\x09\x87\x7c\x75\x66\xc8\x28\ -\x66\xaa\x4b\x2a\xdd\x4d\xec\x42\x85\xf0\x6c\x20\xf5\x32\x3c\xfa\ -\x4d\x3a\xd1\xe3\xd4\xd7\xb4\x54\xa5\x14\x17\xa6\xdb\xaa\x6d\x85\ -\x5b\xda\x0b\x9e\xe6\x04\x12\xe1\x3c\xc1\x8e\x2c\xfd\xc2\x7f\x6d\ -\xba\x8c\x41\x7d\x07\x1e\x99\x8e\x40\xa5\x24\xc0\x7d\xb8\xb1\x3e\ -\x96\x26\xb6\x57\xaf\x07\xfc\x74\x77\x77\x45\xc1\x6a\x87\x79\x2a\ -\x91\xc0\xd9\x8e\xa3\xb8\x3d\xe5\x41\xe9\xaa\x62\x93\xcb\x5c\x5e\ -\x6b\xa0\xba\x35\xdf\x02\x93\xe2\x92\x39\xa0\xcd\xfd\xa6\xc3\x3b\ -\x83\xf2\x2c\x69\x6c\x6e\x41\x24\x1a\x13\xef\x8f\xb4\xbe\x1f\xf7\ -\x49\x93\x49\x76\x26\xb2\x2c\x43\xb3\x1a\xd4\x54\x46\xaa\x36\x97\ -\xb9\x69\x54\x69\x23\x7c\x77\xdf\x0a\x70\xe2\x7e\x83\x24\xd4\x1c\ -\xeb\x74\xef\x5b\x19\x19\x2a\xb6\x4b\x32\xc6\x15\x0b\x82\xf9\x95\ -\xa1\xab\x0f\xfb\x3d\x49\xce\x17\x6b\x19\xf6\x0e\x0c\x6e\xf0\x6f\ -\xa3\x69\x55\x0f\x45\x35\xd0\x74\x36\x07\xa3\xd1\x27\x84\x3f\x70\ -\xe7\x4c\xe7\xfa\xf2\xee\xa6\x2a\xeb\x5a\x4b\x7e\x9e\xe4\xf3\x4d\ -\xe3\xd2\xde\x52\x9c\xbf\xeb\x43\x59\x99\x15\x72\x28\x9a\x7a\xfb\ -\xe9\xfb\x68\x5f\xff\xeb\x7b\xea\x83\x93\xd7\x97\x0d\x9e\xcc\x41\ -\x89\x36\xd7\xda\xcd\xf5\xd9\x4c\x76\xfe\x2d\x2d\x6f\x97\xaa\xd0\ -\xd5\x39\xac\x35\x90\x4c\xe5\xfc\xe6\x9e\x11\xed\x41\x2d\x61\x90\ -\xf0\xf5\x87\x2e\xc0\xda\xd0\x4e\x79\x29\x41\x05\x7d\x0c\x82\x3e\ -\xde\x36\x7d\xf5\xcd\xcb\xa2\xe3\xeb\x48\x26\x69\x20\x99\x84\x91\ -\xa8\x8a\x1e\x3f\xbc\x2f\xe8\xec\xe8\x45\x1a\x99\x04\x8d\x4c\x2c\ -\xb6\x40\xfe\x0c\x85\x05\xff\x87\xac\xfd\x71\xf9\xc7\x5f\x02\x0c\ -\x00\x00\x31\x44\x70\x94\xe4\x6d\xa8\x00\x00\x00\x00\x49\x45\x4e\ -\x44\xae\x42\x60\x82\ -\x00\x00\x02\xaf\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x02\x51\x49\x44\x41\x54\x78\xda\x9c\ -\x53\xcf\x6b\x13\x51\x10\xfe\x36\xfb\x62\x8d\x69\x48\x62\x9b\x18\ -\x8d\xab\x5d\xd3\x84\xa2\x10\xbd\x48\x0f\x62\xa9\xd8\x93\xe0\x49\ -\x0f\xa5\x20\x52\xb0\xe0\x7f\xe0\x45\x3c\xf5\xa6\x77\x7b\xe8\x55\ -\x28\x41\x3d\x78\x28\x7a\xf0\x47\x51\xa4\xbd\x58\x0d\xa8\x60\x5a\ -\x13\x51\xd0\x43\x89\x69\xf3\x63\xb3\xc9\xee\x3e\x67\x9e\xd9\xa2\ -\x08\x4a\x1d\x18\xde\xdb\x99\xf9\xbe\xf7\xcd\x7b\xb3\xda\x8d\x85\ -\x05\xb0\x69\x9a\x76\x9e\x96\xfd\xf8\xbb\x3d\x71\x1c\x67\xad\xdb\ -\xe9\xe0\xdc\xf3\x19\x48\x0a\x08\xd7\x75\xfd\xe4\x81\xeb\x93\x93\ -\x73\x0e\x7d\x73\xc2\x95\x12\x5d\xda\x77\x3d\x4f\xed\x2b\x95\x0a\ -\x1e\x15\x8b\x57\xa5\x94\x1a\xa5\x4b\x3e\x28\x30\xf1\xf8\x32\xcc\ -\xb5\x7b\x20\x56\x4d\x72\xb1\xe3\xc0\xe9\x76\xe1\xf6\xbc\xd3\x6e\ -\xc3\x6a\x36\xd1\x68\x34\x30\x3b\x35\x35\x47\xb9\xb3\x44\x92\xf5\ -\x09\x04\xfb\xf0\xa7\x07\x57\x5a\x32\x78\x41\xd3\x2e\xe1\xc5\xea\ -\x2a\x3c\x22\x8a\xc5\x62\x68\xb5\x5a\x38\x3e\x32\xa2\x0a\xab\xd5\ -\x2a\xee\x2c\x2e\x22\x9f\x4c\xde\x5e\x29\xcc\x3e\x85\x8e\x02\x85\ -\xe7\x05\xa9\x1b\x44\x40\xcf\x65\x8f\x9e\x9c\x60\x6d\x99\x4c\x06\ -\x74\x82\x22\x89\xc7\xe3\x08\xea\xba\x22\x38\x35\x3a\x8a\x0e\xa9\ -\x0b\x85\xc3\x18\x68\x5d\x3c\x23\x1f\xbe\x7a\x2d\x3d\x77\x50\xb8\ -\x12\xc6\x5e\xe3\xd8\xf0\x26\x5d\x4c\x40\xd3\x54\xaf\xd1\x68\x54\ -\x9d\xc8\x24\x1f\x89\x8c\x09\x39\xc6\x8a\x4e\xe4\xf3\xb0\x6d\x1b\ -\x49\xc2\x54\x2b\x45\x43\xb8\x1e\x0e\xed\x8e\x26\xf7\x59\x56\x1b\ -\xbf\x2a\xe0\xd3\x7d\x25\xb2\x47\xe2\x2b\xe2\x5a\xc6\x30\x96\x14\ -\xc8\xa1\x60\x38\x16\x6a\x12\x3b\x3d\x25\xca\xe5\xf2\x36\xc0\x57\ -\xc2\x2b\x7f\xb3\x82\xc3\xa9\x14\xb8\x96\x31\x8c\x15\x8e\x87\x5c\ -\x24\x65\x26\xac\xf7\x75\x94\x0b\xd7\x30\x40\xb7\xde\x97\x1b\x47\ -\x5f\x76\xec\x37\x25\xf6\x87\x25\x04\x4b\x4b\xf8\xba\xbe\x07\x56\ -\xdb\x46\xc4\x34\x13\x8c\xe5\x16\x44\x24\x91\x4e\x4d\x27\x7e\x3e\ -\x0b\x4f\xd2\xca\xf2\x7d\x38\xc2\x50\x40\x7e\x0d\x6e\x63\x73\xf9\ -\x2e\x4e\x8f\x8d\xab\x9a\x69\x53\x2d\x29\xc6\xb2\x02\xb1\xb5\xb1\ -\x41\x7d\x59\x2a\xda\x4f\x00\x23\x9d\xc6\x97\x67\x37\x15\x41\x93\ -\x62\x3c\x58\xe6\x90\x89\x66\xbd\x8e\x46\xad\xa6\xea\x42\xa1\x10\ -\x1c\x45\xe0\x4a\xe1\xf0\xf0\x90\xb3\xd5\x88\xcc\xc8\x66\x71\xd0\ -\x3c\xf2\xc7\x1c\x7f\x2e\x6d\x0f\xa0\xaa\x67\xac\xe8\x7a\x08\x76\ -\x3a\x34\x71\xe4\xbe\xad\xbf\x7d\x87\x7f\x99\xae\x0b\x30\x56\x34\ -\x6c\xf4\x4b\xc9\x5a\x74\xec\xc4\x18\xc3\x58\xf1\xe6\x9b\xac\x6c\ -\xcd\xdf\x7a\x89\xff\xb0\xf2\x77\x54\x78\x76\x76\x91\xc7\x7a\xff\ -\xc5\x4e\x8c\x2f\xad\xf6\x43\x80\x01\x00\xc1\x52\x4e\xcc\x97\x5f\ -\x6c\x5a\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x02\xcc\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x01\x68\xf4\xcf\xf7\ -\x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\ -\xa7\x93\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\ -\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x02\x6c\x49\x44\x41\x54\x38\ -\xcb\x4d\xd2\xbd\x6b\xdd\x65\x14\x07\xf0\xcf\xaf\xb9\x49\x9a\x10\ -\x69\x08\x06\x6a\x2c\x82\xf4\x25\x2e\xed\x50\x87\x4e\x85\x2e\x42\ -\xb2\x34\xe0\xe4\x58\x10\xea\x58\x69\x57\x47\x57\x83\xd2\x49\xc1\ -\xad\xfe\x01\xba\x28\xb8\xa8\x93\x83\xa0\x4b\x41\x89\x10\x1b\x43\ -\x20\x37\x37\xf7\xe6\xbe\xfe\xee\xef\x2d\xc7\xe1\xb9\x6a\x1f\x38\ -\x1c\x9e\xf3\x7c\xbf\xe7\x7c\x9f\x73\x4e\x16\xb7\x6e\x71\xf5\x6a\ -\x2d\xae\x5f\x2f\x82\x10\x0f\x1e\x6c\xc6\x8d\x1b\xc4\xc5\x8b\xf1\ -\x17\x21\xee\xdf\xbf\x19\x77\xee\x44\xac\xad\x45\xcc\xcf\x47\x36\ -\xc4\x11\x91\xe3\x67\x64\xb1\xb3\xc3\xa5\x4b\xbf\xd8\xdf\xff\xd1\ -\xf3\xe7\x4f\xc4\xce\x4e\xc4\x95\x2b\x11\xab\xab\x31\xa0\x16\x1b\ -\x1b\x11\x44\x45\xfc\x40\x64\x07\xc4\x18\x2f\xb0\xc7\x6e\x16\xdb\ -\xdb\xac\xaf\x7f\xab\x69\xb6\x74\x3a\x9c\x9d\x31\x1e\x27\xdf\xed\ -\x2e\xb6\x8c\xc7\x7b\x8e\x8f\xaf\x39\x3c\xe4\xf4\x94\xf3\x73\xd0\ -\x8f\xd0\xa6\x10\xcb\xcb\x4f\x83\x28\x67\x56\x10\x6d\xe2\x27\xe2\ -\x19\x91\x75\x92\x6e\x6d\x86\x7d\x56\x06\xe8\xa2\xe6\x83\xd7\xf9\ -\x22\x8b\x7b\xf7\xd8\xd8\x60\x71\xf1\x13\x79\xfe\xc8\xd9\xd9\x6f\ -\xda\xed\xf7\xb4\xdb\x7f\xea\xf5\xb4\x5c\xbe\xbc\x60\x7e\xbe\xd0\ -\xef\xd3\xe9\x30\x1a\xbd\x6d\x30\xd8\x33\x99\x7c\xa7\x28\xb6\x5b\ -\xca\x72\xa2\xdb\xe5\xe0\x20\x89\xac\x6b\xea\x5a\x33\x1c\x6e\x9d\ -\xb1\xd9\x72\x7c\x3c\xa7\xdd\xe6\xf0\x90\xe9\x14\x54\x11\x4e\xd0\ -\xe1\xab\x96\xa3\x23\xfa\xfd\xf4\x18\x21\x90\xe3\x24\x89\x7f\x23\ -\x8b\x56\x2b\x9a\xba\x56\x63\x0e\x25\xfe\xc6\xef\x18\xf0\x59\xd6\ -\xe6\xd3\x21\x8f\x4a\x34\x29\xe8\x45\xfa\xb6\x55\xb2\xd6\x84\x0f\ -\x8f\xd9\xef\x26\xa0\x5e\x02\x8d\x96\x79\xe5\x35\x64\x71\xf7\x2e\ -\x6b\x6b\xac\xac\xb0\xb0\xf0\x58\x96\x7d\xac\xae\x97\x14\x45\xd2\ -\x35\x9d\x52\x14\xe4\x39\x93\x49\x6e\x32\xf9\xc8\x64\xb2\x2b\xcf\ -\x29\xcb\xd9\x42\x2c\x2d\x7d\xee\xc2\x85\x87\xaa\x2a\x01\x87\x43\ -\x46\xa3\x44\x2e\x4b\x9a\x26\x59\x59\xa6\x58\x9e\x8b\xe9\x74\xb7\ -\xe2\x49\x4b\x51\x3c\x55\x96\x0f\x4d\xa7\x89\xd8\xeb\xa5\x4d\xc8\ -\x73\xaa\x8a\x08\x20\xcb\xa8\x6b\x65\x84\x1c\x13\x1e\x17\xcc\x65\ -\x71\xfb\x76\xa1\xae\x17\xe4\x79\x5a\xa3\xe1\x30\x91\x9b\xe6\x7f\ -\x32\xff\xb5\x77\x34\x6b\xd4\x20\x25\x39\x69\x39\x3a\x3a\x50\x55\ -\xd7\x54\x55\xaa\x58\x96\xa2\x69\xbc\x7c\xce\x67\xed\x1f\xa6\xe1\ -\xe9\xe2\x0c\x05\x07\x59\xc3\xcd\x29\xbf\x56\xcc\xd5\xb3\x4a\x90\ -\xcd\x7c\x83\xe9\x4b\xe4\x53\xf4\x53\xc2\x66\x81\xb7\xb2\x6e\x92\ -\xb5\x30\xe6\xeb\x9c\xad\x7f\xe7\xd9\xa0\x4a\x55\xe4\x33\xc9\xa3\ -\xd9\x1d\xdf\x2c\xf3\xee\xab\x34\x59\x0f\xe3\x19\xa0\x9f\xfc\x9b\ -\x23\xde\x1f\xf1\x4e\xce\x66\x91\x12\xfd\xd1\xf0\xfd\x1c\x5f\x2e\ -\xb1\x7f\x09\xeb\x33\xfb\x07\x6a\x4f\x76\xe7\x35\x05\x41\x4b\x00\ -\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x02\x24\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x01\xc6\x49\x44\x41\x54\x78\xda\x8c\ -\x53\x3d\x4b\x03\x41\x10\x7d\xd9\x3b\xf5\x44\x45\x8b\x44\x34\xa2\ -\xc4\x42\x49\x2b\xe8\xcf\xb0\xf3\x07\x28\xd6\x82\x9d\x20\x58\x88\ -\x0a\x36\x82\x8d\x95\xa0\x58\x0b\x16\xda\x8b\xd8\x08\x09\x44\x0b\ -\x8b\x54\x22\x7e\x44\x10\x41\x42\xce\xe4\x2e\xb7\x1f\xce\x9c\xe6\ -\xcb\x5c\xc0\xe5\x06\x6e\xdf\xbe\xf7\x66\x76\x76\x37\x96\xdd\x05\ -\x62\xc0\x12\x80\x14\xfe\x3f\x1e\x0d\x70\x3c\xbb\x66\x60\x1b\xfa\ -\xa3\x6f\x72\x6e\xf5\x62\x03\x5a\x03\x4a\x75\x96\x59\x16\x20\x04\ -\xb2\xfb\xf3\x5b\x35\x48\x84\x06\x06\xe2\x25\x93\x01\x1b\x18\x29\ -\x61\xaa\x7e\x7b\x10\xce\xeb\xcc\x63\x3e\xeb\x42\x03\xc5\x49\x35\ -\x44\x6f\x3c\x8e\xfb\xcb\x4b\xca\x22\x60\x44\x7b\x30\xce\xeb\xcc\ -\x63\x3e\xeb\x78\xd8\xfa\xc7\xc9\x1a\x1a\x4e\xa0\xe2\x96\x70\x73\ -\x7e\x51\xaf\xd8\xf3\x3c\x38\x8e\x53\x9f\x4f\x4c\x4f\x81\x79\xa4\ -\xb1\x6a\x98\xfd\xeb\x24\x0c\xed\x7d\x38\x39\x1a\x46\x08\x74\x75\ -\xe3\x29\x9f\xc7\x44\x3a\x0d\x1d\x54\xeb\x26\xcc\xe3\x0a\xfe\x1a\ -\x58\x5a\x05\x50\x32\x68\x34\x4c\xc4\x30\xd0\xd7\x87\x28\x9c\x34\ -\x56\xbb\x81\x54\xd0\xdc\xa8\xdf\x11\x13\x16\x1d\x08\x63\x11\x78\ -\x94\x81\x51\x92\xb2\x35\x88\x42\x59\x90\x94\x39\x0a\xef\x50\x41\ -\x00\xdd\x54\xaa\x1f\x28\x2c\xf6\x6c\xa2\xfa\xa6\xa8\x99\x92\x22\ -\x80\xef\x2b\x64\xa6\x8f\x5a\x0d\xa4\xaa\x19\x48\xda\x6b\x23\x53\ -\xd9\xf5\x70\x32\x53\x6e\xba\x45\x22\x0c\xf7\xae\x04\xd2\x44\x54\ -\x10\x96\xda\xa8\xc0\xfd\x2c\xc2\xae\x54\x90\xcb\xe5\x90\x48\x24\ -\xc2\x7e\xa4\x52\x29\xe8\x62\xa9\x53\x0f\xa8\x59\x4d\xd7\xd8\x25\ -\x62\x77\xb9\x8c\x34\x1d\x63\xbd\x2a\x9a\xeb\xd2\x57\xab\xc1\xdd\ -\x23\x90\x4e\xc2\x79\x79\x7a\xa5\x9b\xaa\x9a\x7a\xe0\xe3\xe3\x74\ -\xa5\xed\x39\x0c\xc6\x87\xe0\x55\xe1\xe4\x0b\xc0\x02\x1b\xec\x9c\ -\x61\xf0\x60\x19\xfd\xe3\xe3\xc9\xd6\xf3\x1e\x1b\x89\x7e\x4f\x76\ -\x17\x6e\xaf\xd1\xcf\xba\x6d\xa0\x68\xb3\xe9\xfd\x33\x0a\x87\x7b\ -\xeb\x57\xff\x7d\xcb\x0f\xef\x28\xb0\x8e\xa2\xf8\x2d\xc0\x00\x14\ -\x2c\x1a\x71\xde\xeb\xd2\x54\x00\x00\x00\x00\x49\x45\x4e\x44\xae\ -\x42\x60\x82\ -\x00\x00\x06\xe3\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x90\x00\x00\x00\x90\x08\x06\x00\x00\x00\xe7\x46\xe2\xb8\ -\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ -\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0e\xc4\x00\x00\x0e\xc4\ -\x01\x95\x2b\x0e\x1b\x00\x00\x06\x85\x49\x44\x41\x54\x78\x9c\xed\ -\x9d\x4b\x72\xe3\x38\x10\x44\xc1\x8e\x5e\xf8\x3c\x3a\x8e\x0f\xa8\ -\xe3\xf8\x3c\xda\x79\x16\x0e\x4c\xd3\x10\x89\x6f\x7d\xb2\x0a\xf5\ -\x96\x0e\x59\x2c\x24\x12\x29\x90\x20\xc1\xe3\xf3\xe3\xeb\x3b\xa5\ -\x94\x9e\xaf\xc7\x91\x0c\x92\xeb\xb7\x8c\x65\xed\x8f\xb2\x03\x2c\ -\x37\x46\xbb\x86\x59\xac\x69\x7e\xd6\xfa\x28\xff\x90\xb1\xd6\xa8\ -\x8c\x45\x23\x59\xd1\xfa\x4a\xdb\x5b\x03\x65\xac\x34\xae\xc4\x92\ -\x91\xd0\x35\xbe\xd3\xf2\xf9\x7a\x1c\x47\xeb\x43\xe7\x0f\x53\x17\ -\x26\x81\x05\x23\xa1\x6a\xdb\xe3\x89\x6e\x03\x9d\xff\x69\xb5\x30\ -\x0d\x90\x8d\x84\xa6\x69\x8f\x56\xb9\xe6\x5f\x85\x8f\x88\x8c\xd6\ -\xe8\x5e\x10\x8d\x84\xa2\xe5\x4c\xff\x4f\x1b\xa8\xfc\x22\x6b\x20\ -\x19\x49\x5b\xc3\x51\x2d\xce\xf5\xbe\x15\x3e\x2b\xac\xb6\x08\xb3\ -\x20\x18\x49\x4b\x3b\x8a\xbe\x26\x33\xd0\xd5\x97\x5b\x42\xd3\x48\ -\xd2\x9a\xad\xb4\xb5\xac\xf5\xb2\x70\x0a\x31\xc3\x48\xfd\x48\x69\ -\xc5\xd1\xaf\x6c\x06\xba\x3b\xa0\x15\x24\x8d\xc4\xad\x11\x55\x5b\ -\xae\xea\xbc\x2d\x9c\x5a\x40\x8b\x46\x92\x32\x11\x97\x36\x12\x7d\ -\xf8\x97\xf2\x00\x35\x2c\x2d\xda\x5e\xad\x0f\x22\x4c\xb6\x7b\xe1\ -\xa8\xf5\xae\xdf\xaa\x9d\xc9\x29\x1a\xa2\x91\x6a\x97\xec\x5b\x9f\ -\x59\x81\x4a\x0b\x8d\xfe\x12\x4b\xa0\x12\xa4\x44\x9a\xb9\x80\x86\ -\x94\x48\xdc\xb5\xd4\xfa\xa8\xd9\x79\xd6\xe7\x01\x35\x28\x96\x6f\ -\x34\xcf\x58\x11\xfa\x46\x2d\x81\x4a\x24\x13\x89\xe3\x2c\x53\x32\ -\x91\x90\xce\x10\xbb\x3a\xcb\xcb\xb5\x11\x89\xab\xec\x9c\xcb\x41\ -\x88\xfd\x00\x93\x40\x25\x94\x89\xa4\x31\x62\x29\x8f\xa9\x35\xdf\ -\xea\xd1\x9e\x75\x64\x51\x32\x63\x24\xce\x0b\x68\x94\x35\xdc\x7d\ -\xbf\x05\xcd\x61\x13\xa8\x64\x24\x91\xb4\x85\x3f\x33\x93\x48\x08\ -\xf5\xf7\x0e\x9a\xa1\x91\x85\xd0\xb0\xcc\x55\x03\xb9\xea\xa3\x9c\ -\x8f\xd5\xee\x3f\x47\xd7\xf7\x0a\xb3\x06\xca\x48\x5c\x25\x46\x9a\ -\xd0\x4b\x30\xd2\xde\x3f\x5c\x5f\x2c\x05\x72\x47\xd4\x40\xd4\x72\ -\x86\x21\x03\xa1\x62\xad\x33\x3e\x3f\xbe\xbe\x51\x8d\x3f\xaa\xe5\ -\xb0\x81\x50\x3b\xeb\xf9\x7a\x1c\xa8\xb5\x65\x90\x8d\x33\x8b\x99\ -\xb3\xb0\x5e\x10\x27\xa4\x48\xb5\xd4\x98\x19\x80\x53\x3f\x61\xe8\ -\x23\x3d\x25\x8c\x44\xf2\x98\x38\x25\xee\x12\xa8\xc4\xfb\x5a\x15\ -\x15\xb3\x83\x6d\x7a\x12\xad\x3d\xba\x47\x91\x48\xa4\x1d\x12\xa7\ -\xc4\x7d\x02\x95\x78\x5a\xab\xa2\x62\x65\x60\x2d\x9d\xc6\x5b\x4b\ -\xa1\x33\x14\x89\xb4\x63\xe2\x94\x6c\x97\x40\x25\x56\xd7\xaa\xa8\ -\x58\x1d\x44\xcb\x17\x12\x2d\xa7\xd0\x99\x9e\x44\x8a\xc4\x79\x07\ -\xfe\x66\xee\x1e\x76\x5b\xab\xa2\x82\x42\x37\x92\xa5\x0c\x2f\x29\ -\x74\xc6\x63\x9b\x38\xd8\x7e\x0e\x74\x45\xa4\x4f\x3f\x64\x8b\xa9\ -\x1e\x46\x6c\xcc\x71\xc6\x89\x04\x4a\x7b\x24\xce\x19\xca\xc1\x4e\ -\x7a\x3b\x87\xb5\x14\x8a\xc4\x59\x67\xcb\x04\xda\xd9\x34\xd4\x83\ -\x9c\xfc\x86\x32\xe4\x14\x8a\xc4\xa1\x67\x8b\x04\x0a\xd3\xfc\xc0\ -\x31\xb8\x59\x6e\x69\x45\x49\xa1\x48\x1c\x7e\x5c\x26\x50\x98\xe6\ -\x1d\xae\x41\xcd\x76\x53\xbd\xd6\x6e\x1b\x61\x1e\x59\x4c\xec\xcd\ -\x17\xac\xc1\x39\x98\xff\x46\x27\x07\x2b\xb8\x9c\x03\x59\xc3\xca\ -\x26\x9b\x57\xdf\xcf\xfe\x60\x21\xca\x19\x19\x32\x12\x1d\xcd\xf5\ -\xdd\xac\x06\x0a\xf3\xe8\x21\x65\x4a\x91\x47\x9b\xc3\x48\x6d\xac\ -\xa6\x90\xab\xd3\xf8\xe0\x07\x49\x33\x8a\x6d\xae\x10\x86\x6a\x63\ -\x31\x85\x5c\x2f\x65\xec\x88\xb4\x09\x45\xb7\x77\x09\x63\xb5\xb1\ -\x96\x42\x5b\xdd\xce\xe1\x1d\x0d\xf3\x89\x6f\x30\x15\x06\x6b\x63\ -\x29\x85\xb6\xbe\xa5\xd5\x13\x5a\xa6\x53\xd9\xe2\x2e\x8c\xd6\xc6\ -\x4a\x0a\xc5\x63\x3d\x0e\xd0\x34\x9b\xda\x26\x9b\x61\xb8\x36\x16\ -\x52\x28\x1e\x6d\x36\x8e\xb6\xc9\x54\xb7\xf9\x0d\xe3\xb5\xd1\x36\ -\x48\x8b\xd8\xde\xc5\x30\x08\xe6\x52\xdf\x68\x3c\x0c\xd8\x06\xc1\ -\x28\x77\x6c\xbb\xc5\x9d\x75\x50\x4c\xa5\x9e\x40\x29\x85\x11\x7b\ -\x40\x31\x4c\xc9\x36\xdb\xfc\x7a\x02\xc9\x4c\x10\x09\x94\x52\x18\ -\xb2\x07\x24\xe3\x64\xa6\xde\xb5\x65\xf5\x29\x82\x80\x1e\xf6\x97\ -\xb5\x05\xbe\x89\xe7\xc2\x0c\x82\xf4\x0b\x00\xf7\xbe\xb0\x98\x0b\ -\xb5\x41\xfa\xd5\x80\x7a\xe5\x65\x98\x47\x0f\xd1\xd3\xf8\x30\x92\ -\x3e\x28\x29\xd4\x6d\xa0\x30\x8d\x5f\x54\x96\x32\xc2\x50\xfa\x20\ -\xa4\x50\x97\x81\xc2\x2c\x7e\x51\xbd\x9d\x23\x8c\xa5\x8f\x76\x0a\ -\x35\x0d\x14\x26\xf1\x0b\xc4\x2d\xad\x61\x30\x7d\x34\x53\xa8\x6a\ -\xa0\x30\x87\x5f\xa0\x1e\xeb\x09\xa3\xe9\xa3\x95\x42\xb7\x06\x0a\ -\x53\xf8\x05\xf2\xd1\xe6\x30\x9c\x3e\x1a\x29\x74\x69\xa0\x30\x83\ -\x5f\xa0\xb7\x77\x09\xe3\xe9\x23\x9d\x42\x6f\x06\x0a\x13\xf8\xc5\ -\xc4\x16\x77\x61\x40\x7d\x24\x53\xe8\x97\x81\xa2\xf3\xfd\x62\x6a\ -\x9b\xdf\x30\xa2\x3e\x52\x29\xf4\xbf\x81\xa2\xd3\xfd\x62\xf2\x55\ -\x07\x61\x48\x7d\x24\x52\xe8\x4f\x4a\xd1\xd9\x9e\xe1\x36\xd1\xf1\ -\xf9\xf1\xf5\xcd\xd9\xc1\xda\xf7\xab\x04\xbc\xc4\x1b\x0b\x15\x79\ -\xbe\x1e\x22\x0f\x76\x72\x06\x04\xcc\xb3\xf1\x3b\xf1\x7c\x3d\x0e\ -\xc9\x9f\x75\x4e\x93\xb2\x3d\x99\x1a\xe9\xf3\x8e\xc7\xb9\x60\x24\ -\x90\x00\xd2\x89\x73\x05\xd7\x80\x66\x49\xa0\x48\x9f\x1f\xb4\x4d\ -\x23\x41\x24\x10\x03\x08\x89\x73\x05\xc7\xc0\x26\x4f\xa0\x9d\xd3\ -\x07\xd1\x34\xdc\x44\x02\x11\x80\x9a\x38\x57\x50\x0f\x70\xd2\x04\ -\xda\x2d\x7d\xac\x98\x86\x93\x48\xa0\x09\x2c\x25\xce\x15\x94\x03\ -\x9d\x2c\x81\x76\x48\x1f\xcb\xa6\xe1\x22\x12\xa8\x13\x6f\xe6\x81\ -\x7a\xb0\xd0\x6b\xfa\x9c\x4d\xf3\xf9\xf1\xf5\xed\xb5\x9d\x2b\x44\ -\x02\x5d\x50\x9b\xe3\x78\x32\x12\x45\x3b\x96\xe7\x40\x5e\xc4\x4c\ -\x69\xec\x67\x2a\xb7\xdb\xdb\x4f\xdb\x28\x91\x40\x69\xed\xac\xca\ -\x7a\x22\xad\xd6\xbe\x94\x40\x96\x85\x4b\x89\x36\x3d\x76\x4d\xa4\ -\x2d\x13\x88\xf3\x3a\x8e\xc5\x44\x5a\xa9\x77\x3a\x81\xac\x89\x94\ -\x92\x6c\x3a\xec\x92\x48\x5b\x24\x90\xe6\x95\x63\x2b\x89\x34\x5b\ -\xe3\x54\x02\x59\x10\x24\x25\xac\xd1\xef\x35\x91\x5c\x26\x10\xf2\ -\x5a\x15\x72\x22\xcd\xd4\x35\x9c\x40\xa8\x8d\x4f\xc9\xd6\xe8\x46\ -\xd6\x71\x04\x37\x09\x64\xc9\x3c\xc8\x8c\x1a\x7b\xc8\x40\x68\xa3\ -\xc6\xfa\x5a\x55\xfe\xa9\xb5\x6c\xfe\xa1\xc2\x51\x3a\xa8\x34\x4e\ -\xeb\x33\x2b\x70\xb4\xb9\x56\x1b\xa2\xc6\x35\xba\xe7\x40\x08\x0d\ -\xb3\xbe\x56\xd5\x53\x4b\xfe\x0c\x82\xde\x3d\x0c\x77\x88\x06\x14\ -\x23\x76\x65\xad\x6b\xe6\xff\x28\x8e\x4d\x75\xfc\x59\x7a\xea\xee\ -\x4a\x20\xad\x46\x58\x5f\xab\xa2\x38\x16\x7a\x22\x75\x35\x50\xba\ -\xf8\x99\x9f\x2a\xae\x63\x20\xbd\x16\x3d\x25\xbc\xbe\x68\x26\x90\ -\x64\xc1\xd6\xd7\xaa\x24\xea\x47\x4b\xa4\x66\x83\xd1\xb7\x1f\xa1\ -\xaa\xaf\x76\x07\xe2\xec\xff\x4a\xa0\xdd\x3f\xd5\x04\xe2\x2e\x0e\ -\xe9\x0c\x69\x26\x91\x10\xea\xd7\x4e\xa4\xaa\x00\x5c\x45\x71\x4c\ -\x8e\xa9\xa9\x75\x0c\x82\x71\xee\x90\xee\x33\xd1\x0b\x5a\x1c\xc2\ -\x7b\x9d\xa3\xad\x42\xad\x8b\xaa\x81\x3c\x9c\x95\x58\x32\xcf\x19\ -\xee\x7e\x9c\x9e\x38\xce\x1e\x90\x1a\xb4\xd3\x5a\x54\xb8\x2e\x88\ -\xb2\x18\xc8\xcb\xfe\x7f\x35\x76\x35\x52\xd9\xee\x37\x11\x56\x0e\ -\xa0\x21\xaa\xf6\xf5\x90\xdd\x8c\xc4\x62\x20\xef\xd7\x41\x7a\xd8\ -\xc9\x48\xe7\xb6\xfe\x6a\xf4\xe8\x97\x21\x88\x86\x62\xa0\x0c\x82\ -\x26\x33\x8c\xe8\xb8\x6c\x20\x24\x91\xd0\x0c\x94\x41\xd2\x68\x84\ -\x51\x0f\x34\x6f\xcc\xba\xfa\x27\x24\x50\x0d\x94\x41\xd4\xac\x87\ -\x96\xae\x43\x06\x42\x16\x01\xdd\x40\x19\x64\x0d\x6b\xb4\x7c\x51\ -\x5d\x47\xb1\xd0\x68\x2b\x06\xca\x58\xd0\xf4\x8a\xbb\x25\x9d\x4b\ -\x03\x59\x6a\xa4\x35\x03\x65\x2c\x69\x7c\xa6\xd4\xfb\xd7\xdb\x62\ -\x2c\x36\xca\xaa\x81\x32\x16\x35\x4f\xe9\x9f\xee\xff\x01\x8b\x65\ -\xc9\x17\x1c\x9e\xef\x70\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\ -\x60\x82\ -\x00\x00\x02\xf0\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x02\x92\x49\x44\x41\x54\x78\xda\x84\ -\x53\x5f\x48\x53\x61\x14\x3f\xf7\xee\x6e\x22\x0b\x4c\x6b\xac\x60\ -\x6a\xb5\x39\xdd\x06\x36\xd9\x70\x6b\xb8\x31\xd4\xb2\x35\x82\x3d\ -\x4c\x50\x50\x08\x1f\x24\x84\x24\x56\xea\xc3\xdc\x2a\x63\x36\x1d\ -\x4d\xa1\xb0\xf0\xc1\x17\x1f\x7c\xb0\x3f\x0f\x51\x96\x12\xe8\xa6\ -\x84\xa6\xeb\x45\xa9\x84\x22\x25\x08\xb7\x97\xec\x45\xe6\xee\xdd\ -\xed\x7c\x97\xb5\x36\x1a\x74\xe0\xc7\xe1\xfb\x9d\xf3\xfb\x9d\xf3\ -\xdd\x8f\x4b\x41\x4e\xd0\xc5\xc5\x20\xa9\xa8\x80\xc3\xdd\x5d\x48\ -\x1f\x1c\x40\x75\x43\x03\x68\x6d\x36\xa0\x45\x22\xa0\x69\x5a\x85\ -\x2d\x8d\x90\x1f\x3f\x18\x28\x10\x4a\xa3\x11\xaa\x4d\x26\x41\x98\ -\x89\xaa\x74\x3a\xdd\x38\x3b\x34\xf4\xf8\x0f\x41\x21\xdc\x7e\xff\ -\xd5\x3c\x83\x53\x7a\xbd\x20\x16\x31\x79\x74\x55\x9a\xe3\x9a\x66\ -\x03\x81\x47\xd1\xf5\x75\xf8\xb0\xb5\x05\x75\x3a\x1d\x58\xb1\x0f\ -\x79\x4a\xe8\x2c\xaf\xab\x83\xd3\x48\x30\xcc\x3f\x0b\x55\x71\x1c\ -\xd7\xfc\x34\x18\x9c\xc0\x0c\x89\xfd\x7d\x28\x2d\x2b\xa3\x30\xf3\ -\xa4\xc8\x11\x03\x53\x47\x07\x88\xc4\xe2\x42\x37\x51\xe3\x84\xf3\ -\xcf\x42\xa1\x87\xc9\x64\x12\x44\x78\x1d\x8d\x52\x09\xdf\xe3\x71\ -\xbe\x5c\x2e\x17\x1a\xb0\x4e\xd3\x50\x38\xd4\x2c\xc7\x5d\x78\x82\ -\xe2\x58\x2c\x06\x57\x70\xc8\xd6\xe6\x26\x9c\x51\x28\xc0\x6e\x30\ -\x80\xba\xb2\x12\x2e\x79\x3c\xd7\x70\x83\x85\x42\x06\xd5\x1c\xcb\ -\xb6\x3c\x0f\x87\x1f\xbc\x5f\x5b\x83\xbb\x7e\x3f\x1c\xe0\x8b\xdc\ -\x1a\x1c\x24\x2b\x0b\x1f\xd6\xd1\xdb\xdb\x8b\xd3\x17\xf0\x1e\xdb\ -\x4c\x01\xf1\xc5\x17\x13\x13\xe3\xef\x56\x56\xe0\x8e\xd7\x9b\x2d\ -\x04\x46\x47\x41\x52\x54\x04\x2d\x3d\x3d\xd7\x29\x8a\x9a\x47\xa3\ -\xcf\x84\xcf\x35\xa8\x61\x59\xd6\xf1\x6a\x72\x32\xbc\xbc\xb4\x04\ -\xbe\xfe\xfe\x6c\x61\x64\x6c\x0c\x8c\xf5\xf5\xd0\xdc\xdd\xed\x41\ -\xf1\x1b\x51\x46\x9c\x6b\xa0\x21\xe2\xd7\x53\x53\xf7\x23\x8b\x8b\ -\xe0\xef\xeb\xcb\x8a\xef\x85\xc3\x60\xb6\x58\xa0\xb1\xab\xeb\x06\ -\x8a\xe7\x50\xfc\x29\x77\x65\x62\xa0\xe1\x52\x29\xe7\xfc\xf4\x74\ -\x28\x8a\xe2\x00\xae\x2d\x91\x48\x84\xe2\xed\x60\x10\x2c\x56\x2b\ -\xd8\x3b\x3b\xfb\x80\x88\x19\x26\x2b\xfe\x8a\xdf\xe7\xcb\xea\x2a\ -\x30\x38\xf9\xf2\xdb\x99\x99\x91\xbd\x78\x1c\xc6\x87\x87\x41\x2a\ -\x95\x0a\x0d\x37\x7d\x3e\x41\x6c\x6b\x6f\x1f\xc0\xc9\x2f\x71\xf2\ -\x47\xc2\xef\xe0\xab\xec\x6c\x6c\xfc\x5d\x41\xdf\xda\xaa\x3d\xeb\ -\x76\x0f\xfc\xe4\x79\x7e\x2e\x12\xe1\x5d\x2e\x17\x3f\x1f\x8d\xf2\ -\xbf\xf0\x4c\x78\x52\x37\xb4\xb5\x81\xa2\xb6\xb6\xf0\x83\x9f\xd0\ -\x6a\xa1\xc6\xe9\xd4\xa9\x1d\x0e\x2f\x31\xd9\xde\xdb\xe3\xf7\x31\ -\x93\x33\xe1\x49\xfd\x7f\x41\xfe\x98\x92\x12\x95\xaa\x49\x6e\x36\ -\x0f\x11\x13\x92\x8f\xaa\x54\x76\xe4\x8f\x21\x4a\x49\x1d\x71\x04\ -\x51\x8c\x28\x42\x88\x33\x3a\x8a\xca\x1c\x4e\x22\x8e\x4b\x64\x32\ -\x85\x58\x26\x3b\x97\x4a\x24\x96\x0f\x13\x89\x6f\xc8\xa5\x10\x6c\ -\x26\x13\x1c\xe6\x70\x04\xdc\x6f\x01\x06\x00\x2d\x06\x04\x62\x7f\ -\xe8\x51\x71\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x03\x37\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x02\xd9\x49\x44\x41\x54\x78\xda\x6c\ -\x93\x5d\x48\x14\x51\x14\xc7\xff\xbb\xb3\xbb\xb6\xab\x95\x5b\x46\ -\xae\x99\xba\x7e\x40\x42\x46\x12\xa5\x4b\x49\xe8\x83\x60\x22\x11\ -\x3d\x94\xf6\xd6\x43\xe9\x6b\x81\x6f\x46\xd1\x43\x54\xd0\xf6\x49\ -\xf6\x24\x44\x81\x6f\x3d\x04\x8b\x08\x9a\x60\x59\xb6\x18\x65\xe9\ -\x22\xbb\xce\xb8\xea\xfa\xb1\x8b\xdf\xe3\x3a\xbb\xb3\xce\xed\xcc\ -\x75\x35\xad\x2e\x73\xb9\x77\xe6\x9c\xf3\xbf\xbf\x73\xee\x19\xc3\ -\xad\xf6\x76\x18\x0c\x06\x3e\x35\x4d\x83\x3e\x68\x5f\x47\x8b\x03\ -\xff\x1f\xdd\xeb\x89\x44\x40\x4d\x24\xc0\x18\x83\x69\xbb\xc5\x48\ -\x22\x09\x32\xd0\xc8\x6a\xa9\xaf\x6f\x9d\x8e\x44\x61\xb7\x5b\xb8\ -\xb0\x46\xce\x43\x81\x00\x3a\x07\x07\x1b\xf5\x33\x68\xfa\x79\xcc\ -\x0e\x6d\x12\x30\x0a\x02\x74\xf5\xc8\x9c\x02\x8a\x44\x24\xb2\x86\ -\x99\xd9\x28\xa6\x66\x56\x21\xcb\x32\xee\x36\x34\xb4\x92\xbd\x8a\ -\xbc\x8b\xf4\x90\x4d\x82\x3a\xc2\x71\x24\xf1\x21\x08\x42\xc5\xe4\ -\x62\x08\xcb\xb2\x8a\xe2\x9c\x83\xc8\xb0\x5a\xa1\xae\xaf\xe3\xc7\ -\xf0\x3c\xde\x7a\x3c\x28\xc9\xc8\x68\x7d\xd2\xde\x7e\x83\xdc\xdd\ -\x26\x8d\x0c\x9b\xc8\x23\x81\x15\xe4\xe6\x59\x77\x20\x0f\x07\xa7\ -\x91\x99\xbe\x1f\xa9\x29\x36\x9c\x38\xea\x42\x82\x6c\x66\xb3\x19\ -\xe5\xc1\xa0\xc2\x09\xd4\x8d\x9c\xe1\x17\x65\x3d\x03\x04\xc7\xd6\ -\x78\x71\xf4\x7a\xea\xc8\x35\xe5\xe5\xf8\xe8\xf3\xc1\xbe\xc7\x8c\ -\x0c\xbb\x8d\x93\x08\x24\x10\x8b\xc5\x0c\x1b\x02\xaa\xca\xc9\x8b\ -\x9c\xa9\xf0\x4b\xab\x70\x1e\xb6\xf0\x53\x74\xc7\x21\x71\x03\x59\ -\x1f\x83\xbf\xfc\xa8\xad\xa8\x24\x1b\xa3\xca\xa9\x88\x93\xc0\xc9\ -\xee\x6e\x12\x88\xc7\xb9\x80\x38\x1e\x85\xd1\x68\xc0\xd8\x64\x9c\ -\x13\xd0\x83\x92\xc2\xd3\x9c\x44\x7f\x5f\x54\xc7\x71\x60\x5f\x0a\ -\xdf\xc7\x07\x06\xd0\xe8\x76\x5f\xd3\xc2\xe1\x21\x23\xa1\x70\x9c\ -\xc2\x1c\x1b\x4f\xa1\x20\x67\x17\xf2\xf9\x4c\x41\x2e\xd1\x64\x67\ -\x0b\xc8\xcb\xb7\x52\x41\xe3\x98\x5f\x4a\x60\xc4\x1f\x42\xaf\xf7\ -\x3b\xca\x9a\x9a\x8e\x45\x80\x3b\x26\x42\xe1\x04\x52\x68\x8d\xdf\ -\xc0\x58\x28\xc6\x4f\xd7\x34\xb6\x45\xc2\x98\x02\x9b\x05\xb0\xa8\ -\xfd\x08\x8e\x2e\xa3\xe6\xfa\x55\xd4\xb9\x5c\x3d\x17\x19\xbb\x67\ -\x8a\x25\x05\x0a\xb2\x6d\x18\x9d\x8c\x22\x2f\xcb\xca\x6f\x80\x17\ -\x32\xb9\x1a\xa8\x37\xc4\x2e\x2f\x7c\xa1\xf7\xa8\x39\x75\x1c\xee\ -\xa7\x12\x66\x9d\xce\xaf\xdf\x04\xa1\xd3\xa4\x28\xca\x06\xc1\x54\ -\x92\x60\x4a\xd9\xca\x7b\x93\x24\xb6\xf8\x09\xc6\xb9\x37\x28\xab\ -\x3c\x8b\x8e\x8e\x7e\x5c\xba\xd0\x82\xd7\x7d\x3d\xe1\xb6\x89\x09\ -\xfc\x21\x38\x44\x04\xa1\x28\xf2\x75\x02\x60\x8b\x60\x61\xb6\x17\ -\xe2\xec\x73\x54\x53\xf0\xc3\x47\xee\xd1\x8e\x61\xc7\x87\xa1\x97\ -\xcd\x7e\x4d\x96\x97\xe5\x70\x38\xdf\x34\x23\x8a\xd8\xeb\x70\x18\ -\x44\x22\xd0\x5b\x5c\x9a\x56\x92\x79\x33\x44\x17\x46\x11\x09\x3c\ -\xc0\x19\x57\x29\x1e\x3f\x7b\x21\x05\x25\xa5\xb9\xcf\x23\x7d\x01\ -\xa4\xcd\xe6\xd7\x83\x90\x7e\xe4\xfc\xf9\x9b\x14\xc0\x88\x40\x5f\ -\x18\x9d\xcc\xd6\x89\xfd\xb3\xe7\x36\x63\xf2\x08\x7b\xd7\x56\xc5\ -\x6a\xaf\x94\xbe\x22\x5f\xfb\xdf\xbf\xa6\xde\x4d\xb9\xbb\x8b\x8b\ -\xab\x8d\x69\x69\xff\x18\x97\xbc\xde\xfb\x97\xcf\xa5\xfe\x1c\x5e\ -\xcd\xec\x93\xc2\x96\x81\x15\x9f\xaf\x8b\x3e\x8b\xdb\x7d\x7e\x0b\ -\x30\x00\x66\x8d\xa1\xfd\x87\x65\xe6\x27\x00\x00\x00\x00\x49\x45\ -\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x02\xa1\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x02\x43\x49\x44\x41\x54\x78\xda\xa4\ -\x93\xcf\x6b\x13\x51\x10\xc7\xbf\xfb\x23\x6b\xd3\x4d\x63\x53\x51\ -\x69\x93\x2d\x52\x62\xc5\xb6\x54\x08\x52\x28\x28\xb4\xa8\x08\xc5\ -\x5e\x84\xfe\x01\x3d\x49\x2f\x9e\x7a\x29\x96\x9e\xf2\x07\xf4\xa4\ -\x88\x17\x7f\x1c\x82\x22\x2a\xb9\x05\x5a\xe8\x49\x94\x0a\xb1\x58\ -\x24\x97\x68\x49\xa5\x31\x35\x90\xd2\xcd\x26\xd9\xcd\x6e\xd6\x99\ -\xad\x5b\x82\xb4\x50\x71\x60\xf6\xbd\x37\xcc\x7c\xdf\x7c\xde\xdb\ -\x27\xb8\xae\x8b\xff\x31\x79\x66\x69\xe9\x70\x21\x08\xc2\x34\x0d\ -\x7d\xff\x50\xbf\x23\xf3\xd7\x69\xb5\xfc\x40\xf4\x4d\x32\xf9\xe8\ -\x24\x3d\x09\xe4\x77\x17\x17\xe7\x64\xda\x15\x92\x28\xc2\x34\x4d\ -\x8e\x8b\x0e\x21\x7d\x2e\xba\xa8\x14\xbe\x42\x8b\x0f\x63\x20\xd2\ -\x3a\x52\x40\xa4\x1a\xbb\xd9\x14\xbd\x0e\x04\x5a\x28\x8a\x82\x9a\ -\x61\x88\x36\x09\xec\x15\xbe\xa0\x98\xdf\x84\x08\x07\x5a\xe2\x32\ -\x0a\xa5\x12\x82\xc1\x20\x6c\xdb\x46\x6f\x4f\x8f\x27\x20\xd1\xc6\ -\xbe\xc0\x34\x5c\xb7\x8f\x15\x03\x8a\x72\x6d\x65\x7d\x1d\xdb\xbb\ -\x3a\x8a\xe5\x32\x6a\xe1\x5f\xa8\x7e\x32\xd0\xa0\x42\xdf\xce\x77\ -\x77\xe3\x4a\x3c\x8e\x00\xe5\x37\x2d\x4b\x94\x6d\xc7\x39\x86\xfb\ -\xe6\x91\xdc\x4f\x33\x19\x9c\x56\x55\x5c\xd0\x34\x58\x96\x25\xc9\ -\xdc\x06\x73\x3f\xcb\xba\xf8\xfe\xfe\x35\xc6\x6f\xcf\xe0\xd6\xc0\ -\xf1\xdc\x6a\x67\x27\x62\xd1\x28\x6c\x3a\x78\xcb\x34\x45\x91\x05\ -\x98\xfb\xe7\x87\x57\xd8\x5c\x4d\x61\x63\xe5\x25\x9a\x8e\x83\xb5\ -\x6c\x16\x1b\x5b\x5b\xf8\x98\xcb\x79\x6b\x76\xce\x4b\x2e\x2f\xa7\ -\x9f\xa4\x52\xab\xcd\x03\x01\x49\x66\x0e\x56\x3b\xa3\x0d\xa1\x5a\ -\xad\xe2\x5c\xff\x10\x2c\x62\x8e\xc5\x62\xde\xae\x2a\xb5\x6b\xfd\ -\x39\x03\xe6\x56\x43\x21\x69\x6e\x76\xf6\x06\xd5\xc1\xd0\xf5\x80\ -\xcc\x1c\xac\xf6\xee\x6d\x1a\x86\x61\x60\x2d\x93\xc6\x9d\xeb\xf7\ -\x91\xa3\x9d\x7d\x2b\x45\x22\xa8\xd7\xeb\x18\x4f\x24\x50\xd3\xf5\ -\xca\xd9\x78\x7c\x21\x14\x0e\x77\x39\x86\x51\x96\x99\x83\x3b\x78\ -\xf1\x70\x9e\x52\xe7\xbd\x82\x7a\xad\x86\xab\xa3\xa3\xde\x3c\x48\ -\xcc\xbe\x71\x9e\x24\x49\xdf\xec\x7c\xfe\xf9\x1e\xc0\xe7\x5e\x11\ -\x99\x83\x3b\x60\xae\xde\x91\x91\x05\x1e\x2d\xe2\xf5\xbd\x3d\xce\ -\x79\xa4\x60\x5c\x9c\x9c\xdc\xa1\xe2\x22\x79\x03\x97\xa6\xa6\x1e\ -\xec\x9a\xa6\x5b\xa1\x57\xc5\x73\x1e\x7f\xe8\xfa\xa1\xb7\xc7\x39\ -\x8f\xe7\xe4\x88\x8d\x8d\x1d\x5c\x6d\xd7\xe0\xe0\x3d\x49\x55\xfb\ -\xab\xfb\xfb\xba\xd2\x68\x6c\x5b\x1d\x1d\x1a\xf3\xf9\x6d\xff\x1d\ -\x27\xee\x02\xf9\xe3\xf6\x7f\xe3\x14\x79\x84\xaf\xf9\x04\x6f\xc8\ -\xe3\xf6\x5a\x27\x1b\x9e\x98\xc0\x6f\x01\x06\x00\x48\xae\x45\x78\ -\x60\x4e\x1f\xe2\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\ -\x00\x00\x02\x75\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x02\x17\x49\x44\x41\x54\x78\xda\xa4\ -\x93\x4f\x6b\x13\x51\x14\xc5\xcf\x9b\x37\x93\x49\xc6\xa9\x25\xa3\ -\x15\x63\x16\x2e\x02\x06\xad\x0b\x45\xc1\x95\x5f\xc0\x3f\x0b\x21\ -\x1b\xa5\x20\xb8\x31\xea\x7c\x82\xac\xf4\x2b\x54\x5b\x23\x88\x1b\ -\x4b\xb3\x70\xe1\x42\xb7\x82\x20\xe8\x42\xbb\x70\xa1\x62\x36\xa2\ -\xa0\x51\xda\x1a\xc2\x4c\xa6\x26\x93\xcc\x78\xef\x75\x5a\xb4\x2a\ -\x54\x7c\x70\xb8\x2f\x93\x7b\x7e\xf7\xbc\x37\x89\xaa\xd5\x6a\x50\ -\x4a\x9d\x06\xb0\x07\xff\xb6\x3e\xa5\x69\xfa\xc0\x4c\x92\x84\x3f\ -\x94\x9b\xcd\xe6\xcd\x38\x8e\xb7\xe4\xb4\x2c\x0b\xf5\x7a\xfd\x12\ -\xef\xcd\xd1\x68\xc4\xd5\x18\x0c\x06\xf0\x7d\x1f\x0c\x64\x11\x5d\ -\xea\x78\x3c\x46\x18\xf6\xa9\xa6\x62\xf4\x3c\x0f\xf3\xf3\xd7\x41\ -\x3e\xe3\x67\x80\xe2\xca\x86\x6a\xb5\x0a\x4e\xf2\xed\x68\x05\xa3\ -\xc7\x2f\xb1\xb2\xf2\x95\x9e\x6b\x32\xdb\xb0\xed\x3c\xa2\x28\x60\ -\x33\x4b\x09\x20\x8b\x6d\xf0\x43\x9e\xc6\x49\x58\x69\x79\x07\x56\ -\x57\xbb\x64\x88\x91\xcf\x6f\x13\xb3\x65\xe5\xa9\x27\x16\x00\xf9\ -\x8c\x0d\x23\x49\x33\x48\x00\x34\x39\x8a\x22\xa8\xbd\xbb\x08\x94\ -\x60\xf2\x60\x05\xe5\xd9\x3a\x26\x4f\x1c\x13\x90\x69\xda\x92\x90\ -\x3d\xec\x35\x86\xc3\x21\x48\x3f\x40\xa5\x22\x9d\x37\x84\x73\xed\ -\xbc\x5c\xd6\xf6\xe9\x0a\x3c\xff\x14\x7a\x8d\x16\x01\x8b\xa8\x35\ -\xaf\x12\xc0\x94\x04\xec\x61\xef\xc6\x11\xb8\x26\xef\xbf\xa0\xdf\ -\x5d\x43\xf7\xe1\x53\xb8\x07\xf6\xa1\x78\xf9\x24\xfa\xb7\x1f\xc1\ -\x75\x8b\x48\x5b\x4b\xb8\x77\xf7\x19\xbf\x72\x49\xb0\x7e\x04\x93\ -\x29\xb4\x24\x8e\xe3\x38\xe8\xf5\x7a\x30\x0c\x0b\xfa\xed\x07\x84\ -\xfe\x2c\x0a\x85\x09\x0c\x0c\x2d\x46\x5e\xb6\xad\xd7\x13\x68\x01\ -\xb4\xdb\x6d\x94\x4a\xa5\x1c\x37\x34\x1a\x8d\x2d\xfd\x0e\xb8\x37\ -\x08\x82\x5c\xa7\xd3\x01\x63\x3d\xd7\x75\x67\xb4\xd6\xbb\x37\x37\ -\xd2\xa4\xb2\x4c\x31\xcd\x8f\x9b\xbf\xa3\x0b\xff\x4c\xf7\xb5\xc0\ -\x80\x02\x69\x82\xfb\x7e\xe9\x98\xce\x01\xaf\x86\x7e\xb6\xbf\x41\ -\xfb\xdf\xf8\xa4\x40\xfd\x35\xe7\xe2\xd4\x2d\xbc\x89\x8f\xc8\x7e\ -\xbf\xb5\x84\x73\xcb\x17\xff\xd4\xc6\x53\x77\x92\x2a\xa4\x43\xa4\ -\xc3\xa4\xaa\x24\x5a\x0c\x71\xe6\xce\x59\x01\xdc\xbf\xd0\xe2\xf2\ -\x82\x93\x93\xde\x65\xfb\xe7\xa4\xd7\x9c\xc0\xca\x8e\xe1\x66\x72\ -\xf8\xad\xe0\x8a\x33\x83\x29\x75\x5c\xc6\x2c\xa7\x4f\x30\x17\x2d\ -\x64\x43\xd7\x38\x7a\xa6\x48\xf1\x9f\xe6\x7f\xd6\x77\x01\x06\x00\ -\xf9\x1f\x11\xa0\x42\x25\x9c\x34\x00\x00\x00\x00\x49\x45\x4e\x44\ -\xae\x42\x60\x82\ -\x00\x00\x07\x62\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x01\x97\x70\x0d\x6e\ -\x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\ -\xa7\x93\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\ -\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x07\x02\x49\x44\x41\x54\x48\ -\xc7\x55\x95\x6b\x70\x5d\x55\x15\xc7\x7f\xfb\x3c\xee\x3d\xe7\x9e\ -\x7b\x93\xfb\x68\x4a\x93\x86\xb6\x69\x4b\xaa\x38\x16\x06\x3a\x16\ -\x4b\x81\x22\x4c\xad\x55\xa9\x83\xce\xe0\xa3\x55\xa1\x0a\xc3\x07\ -\xf0\x05\x0c\x33\x38\xea\x28\xc2\xe0\x03\x44\x1c\x18\x64\x8a\x03\ -\xa3\x32\x08\x88\xa0\x0c\x8f\x3e\xd2\x34\x84\x16\xab\x96\x3e\xd2\ -\x34\x49\x9b\x26\x6d\x92\x9b\xc7\xcd\x4d\x9a\xe6\xde\xdc\x7b\xf6\ -\x39\x67\xf9\x21\x24\xc0\xfa\xb8\x67\xd6\xfa\xed\xb5\xd6\xff\xbf\ -\xb7\x12\x11\x82\x20\xa0\xe2\x57\x2f\x41\x44\x78\xef\xf0\x51\x29\ -\x57\x66\x04\x11\xa1\xa9\xa9\x59\x0e\x1f\x3d\x22\x68\xad\x69\x6b\ -\x6b\x7f\x48\x6b\x8d\xaa\x56\xab\x4c\x97\x4b\x72\xf8\xbd\xa3\x18\ -\x57\xac\xbd\x4a\x9c\xb8\xc3\xe2\xc6\x06\xc5\xfb\xd5\x6a\x47\xc7\ -\x0a\xd7\x74\xf5\x74\x8b\xef\xfb\xf2\xce\x81\x03\x62\x00\xb4\xee\ -\x6b\x9b\x0c\x42\xbd\x77\xf9\xb2\x26\x76\xed\xde\x8d\x6d\x59\xeb\ -\x0d\x80\x96\xdd\xfb\x9e\xfc\xfa\x4d\xdf\xe4\xca\x75\x1b\x48\x67\ -\xd2\x6a\xcd\xe5\x6b\xde\x26\x08\x02\x82\x20\x20\x0c\x43\x0e\x1d\ -\x3a\xfc\x42\x10\x04\x68\xad\x21\x08\x02\xa5\xb5\x66\x4f\x4b\xab\ -\xe4\x47\x86\x45\x6b\x2d\x22\x82\xa1\x94\x92\xb6\xb6\xf6\xb1\x65\ -\x4d\x4b\x58\x90\xcd\xd1\xd6\xde\xce\xc0\xc0\xd0\x76\xd5\xdd\xd3\ -\x43\x32\x99\x94\x64\x32\x89\x48\xc4\xa1\x43\x47\x58\x50\x97\x55\ -\x6a\xed\xa7\xd6\xcb\xe4\xe4\x24\x7e\xe0\xf3\xf8\xe3\x7f\xc0\x30\ -\x0d\xe3\xd2\x4b\x57\x8b\x71\xff\x03\x3f\x57\xb9\x5c\x8e\x86\x0b\ -\xea\x19\x2f\x14\xd8\x78\xfd\x17\xa2\xa1\x81\x85\xa2\x44\x84\x30\ -\x0c\x11\x11\xa5\x94\x32\x80\xb0\xe3\x78\x67\x7b\xa9\x34\xb3\xae\ -\x7e\xf1\x42\x1a\x16\x35\x30\x75\xee\x5c\x21\x9b\xcd\xd6\x45\x51\ -\x88\xc1\xfb\xa1\x94\x12\xd3\x34\xc3\x7f\xbd\xf6\xba\x80\x5a\x77\ -\xe1\x92\x06\xea\x72\x75\xd8\x96\x05\x8a\x05\xbb\x5b\xf6\x4a\x7f\ -\xff\xd9\x9b\x95\x88\x70\xac\xe3\x04\x41\xa8\x1f\x9d\x3a\x37\x75\ -\x67\x43\x43\x3d\xa9\x9a\x24\xe9\x54\x2d\x96\x6d\xd1\xd5\xdd\xcd\ -\xe0\x60\x9e\xfa\x86\x45\xac\x6a\x6e\x56\x4a\x44\xb8\x62\xed\xd5\ -\x92\x1f\x1e\x22\x0c\x23\x1e\x7c\xf0\x97\x6c\xdb\xf6\x35\xb4\xd6\ -\xb4\xef\xdf\x8f\x5f\xd5\xd4\xd6\xa6\x4e\x15\x27\x8a\x2b\x2f\xbb\ -\xec\x32\x94\x88\xf0\xd8\x63\x4f\x7c\xef\xaf\x7f\x79\xee\x77\x5a\ -\x6b\xb4\xd6\x84\x51\x44\xa9\x34\xcd\x8b\x7f\x7f\x9e\x6a\xb5\xf2\ -\xd6\xba\x2b\xee\xdb\x08\x1d\x84\xe1\xc4\x6c\xc2\xce\xb7\x5a\xf0\ -\xb5\xaf\x06\x06\x07\x6b\x0e\x1e\x3c\xf8\x8f\xb3\x67\x07\x36\x44\ -\x41\xc8\x8f\xee\xfe\xfe\x77\x1a\x16\xd7\xef\x08\xfc\xd5\xfe\xd8\ -\x18\x76\x47\x07\xcf\xcf\xef\x6e\x6e\x67\x5a\x6b\x46\x47\x47\xd7\ -\xb6\xb4\xec\xdb\x7b\xba\xaf\xef\xae\x48\xa2\xb9\x73\x25\x22\x1f\ -\x4c\x09\xb0\x94\x52\x18\x86\x61\xff\xef\xd0\x91\x03\x0b\xea\x72\ -\xd7\x78\xa9\xe4\xaf\xc3\x20\x94\x62\xb1\xf8\x25\x40\x01\xf3\x09\ -\x1e\x10\x98\xa6\xc9\x1b\x6f\xee\x2a\xcd\x4d\x2a\x1e\x8b\x61\x18\ -\x06\x75\x75\x75\x2f\x9f\x3c\xd5\x7b\xe7\x87\x13\x4a\xb3\xe3\x3d\ -\xfe\x70\x26\x93\xb6\x1d\x37\x46\x22\x91\xc0\x75\x5c\x82\x20\xe0\ -\xc0\xbf\xdf\xa5\xb7\xb7\xef\x11\x00\x43\x29\x85\x52\x8a\x99\x4a\ -\xf5\xaa\xfe\xfe\x33\x3f\x48\x67\xd2\x78\x9e\x47\xca\xf3\x30\x0d\ -\x83\xa1\xfc\x10\xe5\x52\x85\x85\x0b\xeb\x66\xef\x7d\xbc\xb3\x1b\ -\xa5\x14\x3d\x3d\xdd\xfb\x56\x35\x37\xe3\x79\x2e\x5e\xc2\xc3\xb2\ -\x6c\x2a\x95\x0a\xdd\x3d\x27\xb1\x4c\x9b\x8b\x9a\x57\xdc\x00\x60\ -\x84\x91\x06\x09\x5f\x6a\x6e\xbe\x88\x4c\x2e\x4d\x22\x91\x20\xee\ -\xc4\x51\x4a\xd1\xd9\x75\x82\x28\x8c\x48\x26\x93\xd5\x7c\x3e\xff\ -\x4f\x00\xa3\xab\xb3\xfb\x8e\x2f\xdf\x78\xd3\x8d\xd7\x6e\xb8\x9e\ -\xad\xdf\xb8\x99\x94\x97\xc2\x89\xc5\x19\x2f\x8e\x33\x38\x90\x27\ -\x9b\xcd\x31\x31\x39\xbe\xb4\x50\x28\x00\x60\x8c\x0c\x8f\xd6\x64\ -\xb3\x59\xea\xea\xea\xe8\xeb\xeb\x63\x71\x63\x13\xcf\x3d\xf7\x3c\ -\xa7\x7a\x7b\xc9\xe6\x72\x04\x81\xde\xa7\x94\x39\x72\xf1\xc5\x1f\ -\x9f\x15\xe9\xce\xdd\x2d\xfc\xf4\xc7\x3f\x93\x30\x0c\xf1\x7d\x1f\ -\xad\x03\x7c\x5d\xe5\xf6\xdb\x6f\x63\xd3\xe6\x8d\x7c\xac\xf9\xbe\ -\x5e\x38\xb4\x1c\x26\x98\x98\x98\xd8\x6c\x48\x14\x71\xf7\x3d\x77\ -\x27\x52\xa9\x14\xa9\x54\x8a\x4c\x26\x4d\x36\x93\xe5\xe9\x1d\xcf\ -\x50\xa9\x54\xfe\x08\xef\x2c\x87\x5e\xca\xe5\x09\xda\xdb\xb9\xd5\ -\x22\x52\xa4\x92\x5e\x75\xfb\x77\xb7\xab\xe3\xc7\x8f\xdf\x72\xf4\ -\xe8\xb1\x27\xa7\xcf\x4f\x5b\xcb\x96\x37\x95\x6d\xdb\xbe\xad\xbf\ -\x3f\x7f\x6c\x70\x90\xdf\x77\x76\x82\xd6\x6c\x9d\x77\xdc\x5c\x88\ -\x08\x4a\x29\x00\x25\x22\x02\x60\x9a\x26\x85\xc2\xf8\xe7\x8e\x1c\ -\x39\xfa\xd2\x74\xa9\xe4\xe6\x72\x59\x72\x0b\x72\x24\x93\x1e\xa6\ -\x61\xcc\x58\xa6\xf5\x94\xeb\xba\x8f\x24\x12\x89\x3e\x80\x28\x12\ -\x40\xb0\x6d\x9b\x8f\x00\x44\xc4\x02\x16\x29\xa5\xf2\x40\x38\x57\ -\xbc\xf7\x74\xdf\xfd\x9d\x27\xba\xef\x4b\xd7\xd6\x90\x4c\x26\xf1\ -\xbc\x04\xb1\xb8\x8d\xeb\xba\x24\x13\x1e\xf1\x78\x1c\xc3\x30\x98\ -\x9a\x9a\x92\xf3\xe7\xcf\x3f\x5c\x5f\x5f\x7f\x97\x42\x50\x86\xf9\ -\x11\x80\xfa\xa0\x89\xd9\x2e\xb4\xd6\xa9\xfd\xfb\xdf\x7d\xbb\xea\ -\xfb\xab\x73\xb9\x1c\x6e\xc2\xc1\xf3\x12\xc4\xe3\x71\x1c\xc7\xc1\ -\x4b\x24\xb0\x2d\x1b\x11\xc1\xf7\x7d\x86\x47\x46\xe8\xeb\xef\xa7\ -\x5c\x9a\x29\x7c\xe6\xda\x0d\x97\xbb\xae\x73\xe6\xc3\xee\x11\x40\ -\x94\x52\x58\x96\x45\xb1\x38\xb1\xe9\x8d\x37\x77\x4e\x99\x96\xb5\ -\xba\xb1\xb1\x91\x74\x3a\x4d\x2a\x95\xc4\x71\x1d\x3c\xcf\x23\x99\ -\x48\x60\x9b\x16\x22\x42\xb9\x5c\xa6\xb7\xef\x34\xbd\xa7\xfb\x08\ -\x83\x88\x54\x2a\xb5\xa0\xab\xab\xfb\x16\x00\x0b\x60\x64\x74\xf4\ -\x43\x14\x89\x9d\xe9\x1f\x78\x75\x64\x64\xec\xb3\x8d\x8d\x8d\x78\ -\x09\x8f\xb8\x13\x23\xe6\xd8\x38\xb1\x38\xae\xe3\x12\x8b\xc5\x30\ -\x0d\x83\x50\x84\xa9\xa9\x29\x7a\x7a\x4e\x52\x2a\x95\x09\xc3\x90\ -\x78\x3c\xc6\xd2\x65\x4b\x06\x16\xd7\xd7\xef\x98\x07\x14\xc6\xc6\ -\x67\x8d\x64\x59\x6b\xf2\xc3\xc3\xed\x5a\xeb\x58\xd3\xb2\xa5\xc4\ -\xdd\x38\x8e\x13\x23\x16\x8b\x11\x77\x1c\xdc\x78\x1c\xdb\xb2\x31\ -\x0c\x83\x20\x0c\x29\x8c\x17\xe8\xea\xea\x41\xeb\x00\x11\xa1\xb6\ -\xb6\x96\x4c\x26\xbd\x2f\x1e\x8f\x5d\x33\x5e\x2c\x72\xc1\xc2\x85\ -\xb3\x00\xc7\x71\x38\x71\xa2\xfb\xde\xd6\xd6\xd6\x07\xc3\x28\x62\ -\xe9\x92\x0b\xb1\x6c\x93\x35\x6b\x2e\xa7\x26\x55\x83\x44\x11\x22\ -\xb3\x1f\x9f\xa1\x0c\xb4\xd6\x0c\x0e\xe7\xe9\xe9\xea\x41\x04\x1c\ -\xc7\x25\x93\x49\x33\x75\xfe\xdc\x43\xf9\xe1\xa1\x7b\x87\xf2\x83\ -\xb8\xae\xfb\x01\xe0\x85\xbf\xbd\xf8\xea\xcb\x2f\xbf\xf2\x45\xa5\ -\x14\x95\x4a\x05\x5f\xfb\xf8\x5a\x53\x2a\x95\xa8\x56\xab\xac\x5f\ -\x7f\x25\x0f\x3c\xf0\x0b\x2e\x59\xfd\x49\x44\x84\x9e\x13\x27\x39\ -\x75\xaa\x77\xbe\xb0\x9b\x70\x28\x14\x0a\xb7\x06\x81\x7e\x2a\x16\ -\x8b\xb1\x72\xe5\x4a\x5c\xd7\x65\x4e\xeb\xbc\xf2\xca\x6b\x8d\xcf\ -\x3e\xf3\xec\x7f\x06\x07\x87\x2e\x98\x83\x84\x61\x48\x18\x86\x44\ -\x51\x84\x20\x54\x7c\x9f\xca\xcc\x0c\xdf\xfa\xf6\x56\xb6\x6c\xb9\ -\x61\xf6\xf9\x4f\xd7\x00\x42\x6d\x4d\xcb\x1d\xcb\x9b\x8e\x6c\x87\ -\x73\x97\x42\x11\x98\x28\xc0\xd0\x4f\x44\xa6\x9f\x50\x22\xc2\xae\ -\xdd\x2d\xa0\x14\xad\x7b\x5a\x7f\xdb\xb2\x67\xef\x0f\xc3\x28\xc4\ -\x34\x4d\xa2\x28\x9a\x87\x44\x51\x84\x44\xc2\x4c\x65\x86\xfa\x86\ -\x7a\x1e\x79\xf4\x37\xd8\xb6\xe5\x17\x0a\x85\xeb\xae\x5e\x7f\x4f\ -\x1b\x9c\x01\x86\x81\x88\x28\x82\xb1\x31\x18\x1b\xe3\xcf\xb3\xdf\ -\xc8\xce\x96\x59\x05\x45\xc2\xe4\xe4\x24\xbd\xa7\x4f\x7f\xf5\xd8\ -\xb1\x63\xbf\x1a\x19\x1e\xb9\x30\x08\x02\x4c\xd3\x44\x29\x35\x6f\ -\xc8\x72\x79\x86\x1b\xbf\xb2\xe5\xfc\xe6\xcf\x6f\xfa\x74\x14\x45\ -\x1d\xab\x2e\xba\xa4\xec\xfb\xe2\x2a\x05\x61\x08\x03\x03\xd0\xd1\ -\x01\xa5\x12\x77\xcc\x76\xb0\x6b\xcf\x9c\x4a\x4d\x11\xe1\xdc\xd4\ -\x74\x18\x86\x21\x95\x4a\x85\x62\xb1\x48\xb5\xea\xaf\x31\x4d\x63\ -\xb3\x32\x68\x4e\xd7\xa6\x27\x57\xac\x58\xfe\xa7\x74\xa6\xf6\xbf\ -\x86\xa9\xb0\x4c\x8b\xb8\xfd\x09\xfa\xfb\xb9\x6e\x68\x88\x6d\xc5\ -\x22\xb5\x33\x33\xbc\x6e\xdb\x3c\x9d\x48\x10\xfc\x1f\x86\x93\xb9\ -\x1a\xfd\x43\x9a\xa3\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\ -\x82\ -\x00\x00\x02\x77\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x02\x19\x49\x44\x41\x54\x78\xda\x8c\ -\x93\xbd\xaf\xa1\x41\x14\xc6\xcf\xcb\x2b\xe2\x26\x16\x05\xb1\x42\ -\x43\xa2\x11\xbd\x66\x23\xa2\x22\xb9\xff\xc1\xaa\xf7\xd6\x1a\x0a\ -\x09\x89\x46\xb7\xd1\xdd\x68\x97\xbf\x60\x2b\x8a\x0d\xb1\x85\x8f\ -\x42\x50\xae\x50\x21\x44\x04\xb1\xbe\x3f\x76\x9e\xb3\x5e\xb9\x37\ -\x5b\xac\x93\x8c\x31\x73\xcc\xef\x79\xce\x99\x21\x25\x93\x49\xba\ -\x5e\xaf\x24\x49\xd2\x33\x11\x7d\xa4\xff\xc7\x8f\xf3\xf9\xdc\x3b\ -\x1e\x8f\x94\xc9\x64\x48\xc6\x8e\x4a\xa5\xa2\xd3\xe9\x64\x4b\x24\ -\x12\xaf\x22\xc9\x40\x0c\xb1\x77\x1f\x58\xf7\x7a\x3d\x2a\x95\x4a\ -\x2f\x37\x50\x0f\x1f\x00\x3c\x8b\x24\x94\x3f\xb5\x5a\x2d\x9a\x4e\ -\xa7\xa4\x56\xab\xc9\x60\x30\xd0\x78\x3c\x26\x9d\x4e\x47\xbb\xdd\ -\x8e\xdc\x6e\x37\xad\xd7\x6b\x4a\xa7\xd3\xaf\xf1\x78\x1c\x10\x49\ -\x8c\x5f\x92\x50\xfd\xf2\x88\x32\x20\xc3\xe1\x90\x34\x1a\x0d\xcb\ -\x67\xb3\xd9\x68\xa3\xd1\xf8\x2a\xa3\x16\xfc\xe8\x72\xb9\xfc\x33\ -\x2b\x10\x28\x87\x42\x21\x2a\x16\x8b\x64\xb5\x5a\xc9\x62\xb1\x50\ -\xbd\x5e\xdf\x71\xf9\x02\x20\xfa\x27\x51\xb3\xd9\xa4\x6e\xb7\xcb\ -\xfd\xa8\x56\xab\xd4\xef\xf7\xa9\xdd\x6e\x93\x2c\xcb\x34\x9f\xcf\ -\xa9\x50\x28\xd0\x6c\x36\xa3\x72\xb9\x8c\x86\xd3\x7e\xbf\x97\xb8\ -\x07\x0a\xc0\xe7\xf3\xdd\x95\x03\x81\x00\xcf\x18\x70\xe8\xf7\xfb\ -\xd9\x89\x56\xab\xa5\x5a\xad\xc6\xd0\xc3\xe1\xf0\x17\x00\x12\x00\ -\xb9\x5c\x8e\xed\x79\xbd\x5e\x4a\xa5\x52\x64\xb3\xd9\xc8\xe9\x74\ -\x52\x24\x12\xa1\x7c\x3e\x4f\x66\xb3\x99\x3c\x1e\x0f\x94\x19\x70\ -\x77\x00\x12\x00\xe1\x70\x98\x1d\x38\x1c\x0e\xee\xbc\xc9\x64\xe2\ -\x32\x50\x52\x30\x18\x64\x37\xc8\x0d\x06\x03\x06\x88\xa6\x32\x40\ -\xa5\x38\xc0\x95\x2d\x16\x0b\xae\x0f\xca\x88\xd1\x68\xc4\x80\xc9\ -\x64\x42\xcb\xe5\x92\x0f\xe2\xb6\xde\xf5\x00\x24\x6c\xc0\x32\x1c\ -\xe0\x40\x2c\x16\xbb\x5f\x29\x94\xed\x76\x3b\xcf\x6f\x01\xdb\xed\ -\xf6\xbd\x03\x58\x53\x1c\x54\x2a\x15\xea\x74\x3a\x3c\x03\x88\x1c\ -\xe6\xdb\x41\x5a\xad\x56\x70\xcc\xaf\x58\x56\x48\x8a\xed\xb7\x25\ -\xa0\x0f\x58\xbb\x5c\x2e\xe5\xff\x82\x07\xf4\x3d\x1a\x8d\xfe\x14\ -\x8e\x26\x0c\x00\x49\x51\xc7\x7d\x23\xb0\x36\x1a\x8d\xac\x86\x40\ -\x0e\x00\xc4\x66\xb3\x69\x89\x7e\x7c\x13\x5f\x57\x2c\xa8\xd7\xeb\ -\x3f\x0b\x7b\x36\x7a\x30\x84\xf2\x48\xf4\x2d\xaf\xbc\x60\xd8\x7f\ -\x12\xe3\x03\xfa\xf1\xc0\xf9\xeb\x4d\xf9\x37\x2f\x04\xe0\x8f\x00\ -\x03\x00\xe7\xe3\x7a\x6e\x30\xbb\xf3\xb7\x00\x00\x00\x00\x49\x45\ -\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x06\x71\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x90\x00\x00\x00\x90\x08\x02\x00\x00\x00\x68\x24\x75\xef\ -\x00\x00\x00\x03\x73\x42\x49\x54\x08\x08\x08\xdb\xe1\x4f\xe0\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0e\xc4\x00\x00\x0e\xc4\x01\ -\x95\x2b\x0e\x1b\x00\x00\x06\x14\x49\x44\x41\x54\x78\x9c\xed\x9d\ -\x4b\x76\xa3\x30\x10\x45\x71\x8e\x07\xc9\x72\xb2\x9d\x64\x7d\xde\ -\x4e\xb2\x1c\x0f\xe9\x01\x69\x2c\x0b\xa1\x6f\x7d\x5e\x95\xf5\x66\ -\xdd\xb1\x41\xd4\xd5\xe5\x23\x0b\xb8\x7c\xbd\xff\x2c\xcb\x72\xbb\ -\x7f\x2e\x18\xf9\xfe\xf8\xd5\x6e\x42\x1c\xa8\xe2\x5c\x36\x60\x5b\ -\xa0\x5a\xa6\xdd\x84\x47\x10\xca\xb2\x17\xe4\xb2\xae\x6b\x54\x1d\ -\x84\xf6\x6d\x01\xc1\xa6\x5b\x90\xa8\x08\xd7\xb3\x4f\x20\x60\xdb\ -\xda\x00\x82\x4d\x3e\xc7\x0d\xbf\xdd\x3f\x2f\xeb\xba\x26\xff\xb6\ -\x7f\x82\xbd\x5d\x75\x51\xc4\x26\x5f\x84\x0c\x8e\x84\x61\xc7\x6f\ -\x22\x60\x7b\x11\xdb\x32\x1b\xb8\x55\xe0\xcf\xb0\xfc\x47\xc3\x2f\ -\x20\x44\x18\x9b\xcc\x86\x57\xd6\xbf\x60\xd8\x71\x89\x08\xd8\x9c\ -\xd9\x56\xb3\x21\x7b\xd9\x1f\x86\x55\x7e\x33\xfa\xbe\x7a\x04\xb0\ -\xf1\x6d\x6c\x47\xc1\x1b\x0c\x3b\xae\x09\x01\x9b\x51\xdb\x9a\x1a\ -\x1c\xd6\xf9\xc9\xb0\xd6\x05\x1d\x17\xa7\x1b\x26\x6c\xb4\x1b\x38\ -\x58\xe1\x4e\xc3\x8e\x2d\x40\xc0\x06\x6e\x5b\x5f\xc3\xa2\xc2\xbe\ -\xe5\xff\xdc\xd4\x1a\x90\x4a\x21\x74\x9d\x28\x84\xc5\x21\x30\x2c\ -\x8c\xba\x6d\x61\x5d\x6e\xf7\x4f\xf5\x3e\x34\xd8\x80\x63\x25\x13\ -\xc0\xc6\xb7\x53\x05\x5b\xb2\xcd\x8a\x3b\x49\xa6\x95\x12\x1b\x16\ -\x46\x0c\x5b\xe5\x25\xa7\x18\x36\xaa\x15\x25\x4b\x97\x06\x46\xb8\ -\x33\x61\xc5\xd6\x71\x72\xcc\x8a\x4d\xa0\x4f\x30\x1a\x16\x86\x1c\ -\x5b\x77\x69\x98\xb0\x91\x2f\xf0\xac\x56\xa7\xc0\x38\x8e\xd8\x24\ -\xd8\x48\x5a\x45\x88\x4d\xf8\x00\x29\x64\x58\x98\x6e\x6c\x4c\xbd\ -\xb8\x7b\xb1\x7c\xa8\x32\xc5\xc9\x01\x63\x3d\x2d\x6e\xc2\xc6\xda\ -\x8b\x3b\xb0\x29\x5e\x2d\x28\x18\x16\xa6\x88\x4d\xac\x34\x95\xd8\ -\xd4\xc7\x9a\x0b\xc0\x64\xae\x3d\x93\xd8\x54\x7a\x71\x06\x9b\xfa\ -\x35\xf8\x96\x78\xf0\xf7\x18\xf9\x5f\x0b\x59\xaf\x63\xea\xa3\xd8\ -\x63\x32\x89\xc7\x12\x3b\x16\x41\x1b\x90\x8e\xbc\x40\x8e\x49\x2e\ -\x35\xc0\xe4\x83\x50\x29\x95\xb1\xec\x9a\x0d\xaf\x02\x26\x5f\xc1\ -\xdb\xfd\x53\x0b\x1b\xce\xcf\x0e\xc9\x28\x9f\x25\xe6\x63\x74\x0c\ -\xb0\x2f\x95\x1d\xb4\x76\x97\xa8\xb8\x9b\x12\xb0\x0d\xdc\xaa\x30\ -\xd0\x86\x85\xb1\x32\x06\xd8\x97\xfa\x1e\xd9\x70\xd2\x81\x70\x2e\ -\x40\x68\x9b\x21\xab\xc2\x98\x31\x2c\x0c\xec\x18\x60\x5f\x9a\xba\ -\x60\xdb\x69\x3d\x82\x64\x7b\x3a\x6c\x33\x6a\x55\x18\x93\x86\x85\ -\xc1\x19\x03\xec\x4b\x6b\x9f\x6b\xbe\x70\x86\x92\x6c\x4f\xc6\x36\ -\x07\x56\x85\x31\x6f\x58\x98\xc8\x36\x7c\x4e\x1d\xbd\xbf\x67\x68\ -\x0a\x53\xb2\x3d\xe0\xcd\x1b\x8c\x2b\xc3\x16\x0b\x56\xed\xe9\xeb\ -\x58\x9d\x83\xbf\x80\xbd\xd8\xd9\xb1\xea\x2c\x1e\x0c\xb3\xc8\xa9\ -\xbb\xc7\xf7\xff\xbc\x82\x20\xd9\x8b\x58\x15\xc6\xaa\x61\xa6\x39\ -\x8d\xf4\xf5\xa1\x1f\x30\x55\x24\x7b\x41\xab\xc2\x58\x32\xcc\x07\ -\xa7\xc1\x5e\x3e\x3a\x45\x40\xec\x0e\x7b\x1f\xb4\xc6\x83\x6e\x98\ -\x33\x4e\xe3\xfd\x9b\x60\x12\x0e\xdf\x9d\x29\xce\x68\x91\x04\xd1\ -\x30\xaf\x9c\x48\x7a\xf6\xd5\x6b\x75\xbc\x06\xd1\x30\xb4\x8c\x9b\ -\x41\x78\x77\x24\xd9\x44\x52\x84\x81\x0f\xa6\xd0\xde\x8c\x3a\x18\ -\x1a\x60\x8e\x69\x8d\x87\x96\x37\xe5\x54\x6d\xc7\xd8\x70\x24\xc3\ -\x3d\xad\xf7\x11\x72\xd2\xc4\x37\x43\x38\x86\x07\x22\x99\x8d\xa1\ -\x29\xa3\xe1\x60\x4c\x7f\xbb\x91\x63\x84\x08\x92\xd9\xfb\x79\xc5\ -\x4a\x98\xe8\xb2\xdc\xd0\xe7\x18\xa4\xba\x64\xb6\xa7\x08\xc0\x86\ -\x8f\x2b\xd7\x2d\xb3\x8e\x71\xea\x4a\xe6\x67\x9a\x1b\x4e\x58\x89\ -\x32\xde\x94\xee\x18\xaa\xa2\x64\x0e\xa7\x6a\xeb\x86\x9b\x25\xef\ -\x63\x1f\x1c\xa3\xd5\x92\xcc\xc9\xed\x46\x20\x11\xa0\xc8\xfe\x60\ -\x15\xc7\x80\x55\x24\x33\x7c\xcb\x2c\x5a\x64\xf8\x49\x3c\xba\xc8\ -\x31\x66\x79\xc9\x8c\x3d\xf6\x01\x36\x62\xe4\x84\x1e\x0e\xe6\x18\ -\xb6\xb0\x64\x4f\x6f\x99\xcd\x04\x67\xe6\xd0\x8b\xa7\x16\xd8\x0c\ -\x48\xe6\xbc\x44\x9a\x88\xed\x81\x44\x9f\x97\x38\x8f\x64\xe3\x91\ -\x7b\x84\xac\x63\x5a\xe3\xa1\x3f\xad\x9f\xd8\x8a\x91\x91\xac\x00\ -\x6c\x72\x12\x08\xd7\xd0\xd4\x84\x57\x8c\x80\x64\x39\x60\x93\x90\ -\x40\x78\x7f\x5e\x99\x08\x8b\xe1\x96\xec\x14\xd8\x64\x23\x10\x89\ -\x29\x02\x13\x64\x31\xac\x92\xa5\x81\x4d\x2a\x02\x91\x9b\xe6\x36\ -\x71\x16\xc3\x27\x59\x02\xd8\xe4\x21\x10\xe9\xa9\xda\x13\x6a\x31\ -\x4c\x92\x91\xbd\xda\x9e\x69\x39\x2e\xa3\x73\xbb\xd1\x44\x5b\x0c\ -\x87\x64\x4f\xc0\x26\x03\x81\x68\xde\x32\x3b\x01\x17\x43\x2e\xd9\ -\x03\xd8\xac\xbe\x40\xf4\x1f\xfb\x30\x31\x17\x43\x2b\xd9\x1f\xb0\ -\x59\x77\x81\xa0\x3c\xba\x68\xc2\x2e\x86\x50\xb2\xb7\x65\x56\x5c\ -\x24\x54\xcc\x2e\x5f\xef\x3f\x24\x85\x9e\xf3\x44\x65\x52\x7e\x53\ -\x7a\x4d\xbc\xd2\x22\x7c\x6d\xfb\x42\xb4\x07\x42\x7c\xf1\x36\x42\ -\x38\x5e\x6d\x4b\xc2\x9e\x60\xe6\xaf\x33\xbd\xc0\x8f\xc4\xd3\xb0\ -\x47\x64\x5e\x18\x3d\xb8\x84\x51\xc3\x7c\xe8\x05\x6e\x55\x98\x57\ -\x37\x4c\xc0\xaa\x28\x83\x5d\x7c\xc8\x30\xd3\x7a\x19\xb2\x2a\xcc\ -\x2b\x1a\x26\x6f\x55\x94\x91\x8e\xde\x6f\x98\x45\xbd\x8c\x5a\x15\ -\xe6\x55\x0c\x53\xb7\x2a\x4a\x77\x77\xef\x34\xcc\x90\x5e\x50\x9c\ -\xc6\xe3\xdc\x30\x64\x5a\x72\x13\x49\xf1\xf5\xda\x39\xf9\x7b\xa7\ -\x95\x37\xc3\x92\xc7\x2a\x58\x6c\x1d\xad\x6a\x3e\x86\x61\x6e\xf9\ -\x52\xb1\xf7\xdb\x5a\x8e\xbc\x93\xac\x89\x07\xc3\x9a\xce\x00\xd1\ -\x6c\x6b\x6d\x4c\x9b\x61\x50\x9b\xba\x0c\xe8\x62\xd7\x36\xab\x86\ -\x91\x5c\x57\x81\xd8\xd6\xd4\x86\x06\xc3\x10\xb6\x6d\x61\xd0\xc2\ -\x96\x6d\x96\x0c\x63\x1d\xad\xd0\xb5\xad\x7e\xd5\xb5\x86\xe9\xea\ -\x25\xd6\xfd\xf1\x6d\x43\x37\x4c\x65\x0c\x50\xc5\xb6\xca\x35\x56\ -\x19\xa6\xa2\x97\x7a\x37\x07\x39\x66\x47\x01\x35\x4c\x9d\x96\x4a\ -\x6a\xba\x48\x19\x98\x64\x47\x43\x1b\x03\xdc\x76\xc8\x50\xbd\x07\ -\xe5\x01\x97\xc9\xa2\x28\x9e\x02\x44\x2b\xdd\xfe\x29\xd0\x87\xbe\ -\x3f\x7e\xf3\xdb\x5b\x00\x26\xd0\x44\xb4\x31\xc0\xcc\x8a\xc4\xb0\ -\x65\xa2\x69\x58\x13\x03\x01\x6c\x95\x0b\xe7\xc6\x96\x97\x2c\x07\ -\x8c\xaf\x4d\x68\x63\x80\x1d\x0b\xd4\xb2\x4d\xda\x30\xc2\x3b\x65\ -\x48\x16\x35\xb8\x10\x26\x6c\x19\xc9\x4e\x81\x91\x37\x02\x6d\x0c\ -\x90\xb0\x3d\x92\xb6\x49\x18\xc6\x7a\xe0\xe9\xc0\xc6\xd4\x1e\x5a\ -\x6c\x67\x92\xa5\x81\x51\xad\x15\x6d\x0c\x50\xa0\x3d\xdc\xb6\x71\ -\x19\xa6\x72\xf1\x94\xc1\x26\xdc\x1e\x12\x6c\x49\xc9\x12\xc0\x06\ -\x57\xa3\x3e\x2e\x10\xb5\x5f\xb1\x3d\x1c\xb6\x51\x8e\x25\xa2\x8d\ -\xe2\x2c\x00\xbd\x67\x19\x2b\xcb\x11\x76\x6c\x58\x5f\x77\x40\xa8\ -\x4b\x32\x38\xbf\x6f\x51\xd9\x16\xdf\x94\xde\xba\x44\xcc\x1b\x81\ -\x93\x41\xc0\xb6\x65\xa4\xc8\x4f\x86\x35\x2d\x08\x67\xfb\x2b\xe3\ -\xc3\xb6\x27\xc3\x2a\x17\x21\x70\x5d\xc5\x1d\x04\x6c\x5b\x5a\x6b\ -\xfe\x30\xac\xe6\x9b\x38\xdb\x39\x18\xbb\xb6\x3d\x0c\xcb\x7f\x47\ -\xf8\x12\x58\x32\x08\xd8\xb6\xd4\x20\xb8\x16\x3f\x8a\xb3\x3d\x4c\ -\xb1\x65\xdb\x9f\x61\xc9\x0f\x29\x8e\x56\x68\x05\x01\xdb\x96\x33\ -\x22\xd7\xe4\xdf\x70\xda\x2d\x1c\x7c\xdb\x2e\xeb\xba\x86\xff\xab\ -\xde\x56\x84\xb9\x37\x5b\xd4\x4b\xb1\x27\xac\xc9\xe3\xb5\xc0\x20\ -\xed\xc3\x01\xb6\x05\xa4\x2c\xcb\xff\xca\xfc\x03\x0c\x3a\xb7\xd7\ -\x9d\x1e\xca\x90\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\ -\x00\x00\x07\x3c\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x01\x97\x70\x0d\x6e\ -\x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\ -\xa7\x93\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\ -\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x06\xdc\x49\x44\x41\x54\x48\ -\xc7\x55\x95\x59\x6c\x54\xd7\x19\xc7\xff\xe7\x2e\x73\x77\xcf\x6e\ -\x7b\xc6\x0e\x60\x30\x13\xb1\x04\x12\x07\x95\x94\x90\x1a\xda\x4a\ -\x49\x53\x35\x54\xb4\x52\x9a\x86\xb4\x4d\xe9\xf2\xd4\x28\x6d\x43\ -\x15\xa9\x2f\x51\x93\x80\xd2\x2c\x28\x52\x15\xa9\x8d\x68\x95\xbc\ -\x44\x6d\x1a\x51\xd2\x46\x4d\xd8\x8c\xed\xb8\x40\x69\x4b\xd8\xed\ -\x19\xb0\xc7\x60\x7b\x6c\x33\x1e\xcc\xc0\x8c\x67\xee\xb9\xe7\x7c\ -\x7d\x18\x6c\xc8\x79\x3c\xba\xff\xef\xf7\x2d\xff\xef\x1e\x46\x44\ -\x08\x82\x00\x35\xbf\xbe\x16\x44\x84\xcf\x4e\x9d\xa1\x6a\x6d\x8e\ -\x40\x44\xe8\xe8\xc8\xd0\xa9\x33\xa7\x09\x9c\x73\xf4\xf7\x0f\xbc\ -\xc2\x39\x07\xab\xd7\xeb\xb8\x59\xad\xd0\xa9\xcf\xce\x40\x79\x60\ -\xfd\x43\x64\x1a\x26\xda\xda\xd3\x0c\xb7\xa2\x85\xa7\xaf\x16\xbb\ -\x87\x72\x59\xf2\x7d\x9f\xfe\x75\xec\x18\x29\x00\xd0\xdb\xd7\x3f\ -\x1b\x08\x7e\x64\xe9\x92\x0e\x1c\x3c\x74\x08\xba\xa6\x6d\x54\x00\ -\xa0\xe7\x50\xdf\xef\xbf\xfb\xf8\xf7\xf0\xe0\x86\x4d\x88\x44\x23\ -\x6c\x5d\x57\xd7\x00\x82\x20\x40\x10\x04\x10\x42\xe0\xe4\xc9\x53\ -\xef\x07\x41\x00\xce\x39\x10\x04\x01\xe3\x9c\xe3\x70\x4f\x2f\x15\ -\xa6\x26\x89\x73\x4e\x44\x04\x85\x31\x46\xfd\xfd\x03\x57\x97\x74\ -\x2c\x42\x22\x16\x47\xff\xc0\x00\xc6\xc6\x26\xb6\xb3\x6c\x2e\x07\ -\xd7\x75\xc9\x75\x5d\x10\x49\x9c\x3c\x79\x1a\x89\x64\x8c\xb1\xf5\ -\x5f\xd8\x48\xb3\xb3\xb3\xf0\x03\x1f\x6f\xbd\xf5\x3b\x28\xaa\xa2\ -\xdc\x7b\xef\x1a\x52\x5e\xda\xf9\x1b\x16\x8f\xc7\x91\x6e\x49\x61\ -\xa6\x58\x44\x24\xd2\x44\xc9\x78\x02\x8c\x88\x20\x84\x00\x11\x31\ -\xc6\x98\x02\x40\x9c\x3b\x7f\x61\xa0\x52\x99\xdb\x90\x6a\x6b\x46\ -\xba\x35\x8d\xf2\xf5\xeb\xc5\x58\x2c\x96\x94\x52\x40\xc1\xad\xc3\ -\x18\x23\x55\x55\xc5\x3f\x3e\xfa\x27\x01\x6c\xc3\x5d\x8b\xd2\x48\ -\xc6\x93\xd0\x35\x0d\x60\x48\x1c\xea\x39\x42\xa3\xa3\x57\x9e\x66\ -\x44\x84\xb3\xe7\x06\x11\x08\xfe\x66\xf9\x7a\xf9\x99\x74\x3a\x05\ -\xaf\xc9\x45\xc4\x0b\x43\xd3\x35\x0c\x65\xb3\x18\x1f\x2f\x20\x95\ -\x6e\xc5\xdd\x99\x0c\x63\x44\x84\x07\xd6\x7f\x89\x0a\x93\x13\x10\ -\x42\x62\xd7\xae\x97\xf1\xd4\x53\x4f\x80\x73\x8e\x81\xa3\x47\xe1\ -\xd7\x39\xc2\x61\xef\x52\xe9\x5a\xa9\xb3\xab\xab\xab\x91\xd2\x93\ -\xdb\x9e\x78\x36\x9d\x4a\xa3\xb5\xa5\x05\xaf\xbd\xfa\x3a\x56\xaf\ -\xee\x42\x26\xb3\x0a\x9e\xe7\xc1\xf5\xec\xfd\x3e\xf7\x3b\xd3\xe9\ -\x74\x23\x75\x22\xc2\x81\xfd\x3d\xf0\xb9\xcf\xc6\xc6\xc7\x9b\x4e\ -\x9c\x38\xf1\xb7\x2b\x57\xc6\x36\xc9\x40\xe0\x97\x3b\x9e\xfd\x51\ -\xba\x2d\xb5\x67\xd5\x8a\x95\x60\x8c\x35\x8a\x9d\x9f\xdd\xfc\xcc\ -\x38\xe7\x98\x9e\x9e\x5e\xdf\xd3\xd3\x77\x64\x24\x9f\x7f\x4e\x92\ -\x9c\xbf\x67\x44\x74\xbb\x4b\x00\x34\xc6\x18\x14\x45\xd1\xff\x77\ -\xf2\xf4\xb1\x44\x32\xde\xed\x78\xee\xab\x22\x10\x54\x2a\x95\xbe\ -\x09\x80\x01\x58\x10\x38\x00\x02\x55\x55\xf1\xf1\x27\x07\x2b\xf3\ -\x9d\x32\x42\x21\x28\x8a\x82\x64\x32\xb9\xf7\xe2\xa5\xe1\x67\xee\ -\x14\x54\x1a\xed\x3d\xff\x46\x34\x1a\xd1\x4d\x2b\x04\xdb\xb6\x61\ -\x99\x16\x82\x20\xc0\xb1\x7f\x1f\xc7\xf0\x70\x7e\x37\x00\x28\x8c\ -\x31\x30\xc6\x30\x57\xab\x3f\x34\x3a\x7a\xf9\xe7\x91\x68\x04\x8e\ -\xe3\xc0\x73\x1c\xa8\x8a\x82\x89\xc2\x04\xaa\x95\x1a\x9a\x9b\x93\ -\x8d\xbc\xcf\x5f\xc8\x82\x31\x86\x5c\x2e\xdb\x77\x77\x26\x03\xc7\ -\xb1\xe0\xd8\x0e\x34\x4d\x47\xad\x56\x43\x36\x77\x11\x9a\xaa\x63\ -\x79\x66\xd9\x63\x00\xa0\x08\xc9\x01\x12\x1f\x64\x32\xcb\x11\x8d\ -\x47\x60\xdb\x36\x0c\xd3\x00\x63\x0c\x17\x86\x06\x21\x85\x84\xeb\ -\xba\xf5\x42\xa1\xf0\x77\x00\x50\x86\x2e\x64\x7f\xf6\xad\xad\x8f\ -\x6f\xdd\xbc\xe9\xab\xd8\xf6\xe4\xd3\xf0\x1c\x0f\x66\xc8\xc0\x4c\ -\x69\x06\xe3\x63\x05\xc4\x62\x71\x5c\x9b\x9d\x59\x5c\x2c\x16\x01\ -\x00\xca\xd4\xe4\x74\x53\x2c\x16\x43\x32\x99\x44\x3e\x9f\x47\x5b\ -\x7b\x07\xde\x7b\xef\xcf\xb8\x34\x3c\x8c\x58\x3c\x8e\x20\xe0\x7d\ -\x8c\xa9\x53\x2b\x57\xae\x68\x08\x56\xac\x5e\xf1\x32\x63\x0c\xae\ -\xe3\xa2\xc9\xf5\x90\x6a\x69\xc5\x0b\x2f\xbc\x88\xe3\x47\x4f\x20\ -\x91\x88\x22\x10\xbc\xdb\x30\x34\x08\x21\x1b\x02\x92\x12\x3b\x7e\ -\xb5\xc3\xf6\x3c\x0f\x9e\xe7\x21\x1a\x8d\x20\x16\x8d\xe1\x8f\x7b\ -\xde\x41\xad\x56\xfb\x83\xae\xeb\xb8\x6f\xed\x7d\x48\xc4\xe2\x0d\ -\x01\x24\x83\xe7\x3a\xf5\xed\x3f\xde\xce\xba\x37\x77\x6f\x8f\x27\ -\x13\x81\xeb\x79\x58\x75\xcf\xaa\xaa\xae\xeb\x3f\x4d\xb5\xa6\xe0\ -\x79\x1e\x88\xe8\xb6\xf9\x84\x10\x0b\xfe\x20\xa2\x79\xa3\x31\xba\ -\xf5\x95\xaa\xaa\x28\x16\x67\xbe\x76\xfa\xf4\x99\x0f\x6e\x56\x2a\ -\x56\x3c\x1e\x43\x3c\x11\x87\xeb\x3a\x50\x15\x65\x4e\x53\xb5\xb7\ -\x2d\xcb\xda\x6d\xdb\x76\x1e\x00\xa4\x24\x00\x04\x5d\xd7\x3f\x0f\ -\x20\x22\x0d\x40\x2b\x63\xac\x00\x40\xcc\x07\x1f\x1e\xc9\xbf\x74\ -\x61\x30\xfb\xeb\x48\xb8\x09\xae\xeb\xc2\x71\x6c\x84\x0c\x1d\x96\ -\x65\xc1\xb5\x1d\x18\x86\x01\x45\x51\x50\x2e\x97\xe9\xc6\x8d\x1b\ -\x6f\xa4\x52\xa9\xe7\x18\x08\x4c\x51\x3f\x07\x60\xb7\x8b\x68\x54\ -\xc1\x39\xf7\x8e\x1e\x3d\xfe\x69\xdd\xf7\xd7\xc4\xe3\x71\x58\xb6\ -\x09\xc7\xb1\x61\x18\x06\x4c\xd3\x84\x63\xdb\xd0\x35\x1d\x44\x04\ -\xdf\xf7\x31\x39\x35\x85\xfc\xe8\x28\xaa\x95\xb9\xe2\x97\x37\x6f\ -\xba\xdf\xb2\xcc\xcb\x77\x6e\x0f\x01\x20\xc6\x18\x34\x4d\x43\xa9\ -\x74\xed\x91\x8f\x3f\x39\x50\x56\x35\x6d\x4d\x7b\x7b\x3b\x22\x91\ -\x08\x3c\xcf\x85\x69\x99\x70\x1c\x07\xae\x6d\x43\x57\x35\x10\x11\ -\xaa\xd5\x2a\x86\xf3\x23\x18\x1e\xc9\x43\x04\x12\x9e\xe7\x25\x86\ -\x86\xb2\x3f\x04\x00\x0d\x00\xa6\xa6\xa7\xef\xa0\x50\xe8\xf2\xe8\ -\xd8\x87\x53\x53\x57\x1f\x6e\x6f\x6f\x87\x63\x3b\x30\xcc\x10\x42\ -\xa6\x0e\x33\x64\xc0\x32\x2d\x84\x42\x21\xa8\x8a\x02\x41\x84\x72\ -\xb9\x8c\x5c\xee\x22\x2a\x95\x2a\x84\x10\x30\x8c\x10\x16\x2f\x59\ -\x34\xd6\x96\x4a\xed\x59\x00\x14\xaf\xce\x34\x16\x49\xd3\xd6\x15\ -\x26\x27\x07\x38\xe7\xa1\x8e\x25\x8b\x61\x58\x06\x4c\x33\x84\x50\ -\x28\x04\xc3\x34\x61\x19\x06\x74\x4d\x87\xa2\x28\x08\x84\x40\x71\ -\xa6\x88\xa1\xa1\x1c\x38\x0f\x40\x44\x08\x87\xc3\x88\x46\x23\x7d\ -\x86\x11\xea\x9e\x29\x95\xd0\xd2\xdc\xdc\x00\x98\xa6\x89\xc1\xc1\ -\xec\xf3\xbd\xbd\xbd\xbb\x84\x94\x58\xbc\xe8\x2e\x68\xba\x8a\x75\ -\xeb\xee\x47\x93\xd7\x04\x92\x12\x44\x8d\x87\x4f\x61\x0a\x38\xe7\ -\x18\x9f\x2c\x20\x37\x94\x03\x11\x60\x9a\x16\xa2\xd1\x08\xca\x37\ -\xae\xbf\x52\x98\x9c\x78\x7e\xa2\x30\x0e\xcb\xb2\x6e\x03\xde\xff\ -\xcb\x5f\x3f\xdc\xbb\x77\xdf\x37\x18\x63\xa8\xd5\x6a\xf0\xb9\x0f\ -\x9f\x73\x54\x2a\x15\xd4\xeb\x75\x6c\xdc\xf8\x20\x76\xee\x7c\x11\ -\x6b\xd7\xdc\x03\x22\x42\x6e\xf0\x22\x2e\x5d\x1a\x5e\x08\x6c\xd9\ -\x26\x8a\xc5\xe2\x4f\x82\x80\xbf\x1d\x0a\x85\xd0\xd9\xd9\x09\xcb\ -\xb2\x6e\xef\xc1\xbe\x7d\x1f\xb5\xbf\xfb\xce\xbb\xff\x19\x1f\x9f\ -\x68\x99\x87\x08\x21\x20\x84\x80\x94\x12\x04\x42\xcd\xf7\x51\x9b\ -\x9b\xc3\xf7\x7f\xb0\x0d\x5b\xb6\x3c\xd6\xf8\xfd\x47\x9a\x00\x10\ -\x4a\xa5\x6b\x0f\x83\xb1\xfd\x52\x4a\x2c\x5d\xda\x81\x70\x53\x13\ -\xa4\x94\x68\x4e\x24\x1b\x80\x83\x87\x7a\x00\xc6\xd0\x7b\xb8\xf7\ -\xf5\x9e\xc3\x47\x7e\x21\xa4\x80\xaa\xaa\x90\x52\x2e\x40\xa4\x94\ -\x20\x49\x98\xab\xcd\x21\x95\x4e\x61\xf7\x9b\xaf\x41\xd7\x35\xbf\ -\x58\x2c\x7e\xc5\x34\xcd\x4f\x89\x08\xaa\xaa\xc2\x73\x3d\xa4\xd3\ -\x69\xa8\xaa\x0a\xdb\xb2\x6e\x3d\x23\x07\x7a\x1a\x0e\x92\x84\xd9\ -\xd9\x59\x0c\x8f\x8c\x7c\xe7\xec\xd9\xb3\xbf\x9d\x9a\x9c\xba\x2b\ -\x08\x02\xa8\xaa\x0a\xc6\xd8\xc2\x42\x56\xab\x73\xd8\xfa\xed\x2d\ -\x37\x1e\xfd\xfa\x23\x5f\x94\x52\x9e\xe3\x9c\xa3\xb5\xa5\x15\x6d\ -\xe9\xf4\xc2\xac\x00\x40\x51\x94\x5b\x15\x1c\x3c\x3c\xef\x52\x95\ -\x88\x70\xbd\x7c\x53\x08\x21\x50\xab\xd5\x50\x2a\x95\x50\xaf\xfb\ -\xeb\x54\x55\x79\x94\x29\xc8\x44\xc2\x91\xd9\x65\xcb\x96\xfe\x29\ -\x12\x0d\xff\x57\x51\x19\x34\x55\x43\xa6\x73\x39\x74\x5d\x87\x94\ -\x12\x77\x1e\x45\x51\xf0\x7f\x60\x84\x69\x65\x48\xcf\xfa\x14\x00\ -\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x07\x65\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x01\x97\x70\x0d\x6e\ -\x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\ -\xa7\x93\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\ -\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x07\x05\x49\x44\x41\x54\x48\ -\xc7\x55\x95\x6b\x70\x54\xe5\x19\xc7\x7f\xef\x39\x67\x77\xcf\xd9\ -\x73\x36\xd9\x4b\x16\xd8\x10\xb9\x08\xc6\x82\x8a\x8a\x99\x62\x11\ -\x05\x6a\x47\xad\x9d\x8a\x83\x9d\xb1\x17\x6c\x6b\x69\xeb\x27\xed\ -\x4d\x3a\x76\xec\x4c\x6d\x55\x1c\xab\x15\x1d\x3b\xf6\xc2\xd0\x8e\ -\x8e\x2d\xb5\x6a\x29\xb6\x4e\xbd\x00\x21\x89\x29\x58\x6c\xb9\x5f\ -\xb2\x81\x64\x03\x49\x36\x09\x9b\x4d\x08\x6c\xb2\xbb\xe7\xf2\xf4\ -\x43\x48\xd0\xe7\xe3\x3b\xf3\x3c\xbf\xf7\x79\x9e\xff\xff\x7d\x95\ -\x88\xe0\x79\x1e\xe5\x6a\xe5\x5a\x44\x84\x03\x07\x0f\xcb\x78\x79\ -\x42\x10\x11\xe6\xcf\x6f\x94\x83\x87\x0f\x09\xae\xeb\xd2\xd6\xd6\ -\xfe\xb4\xeb\xba\xa8\x4a\xa5\xc2\x85\xf1\x92\x1c\x3c\x70\x18\xed\ -\xc6\x65\x37\x8b\x19\x31\x99\xdd\x50\xaf\xb8\x58\xad\x76\xe8\x6c\ -\x61\x65\x47\x67\x56\xaa\xd5\xaa\xfc\x7b\xef\x5e\xd1\x00\x5a\x5a\ -\xdb\x46\x3d\xdf\xdd\x7d\xf9\xbc\xf9\xec\xd8\xb9\x93\x90\x61\xac\ -\xd0\x00\x9a\x77\xb6\xfe\xee\xab\xf7\x7e\x9d\x9b\x96\xaf\x22\x9e\ -\x88\xab\xa6\xa6\xa6\x0f\xf0\x3c\x0f\xcf\xf3\xf0\x7d\x9f\xfd\xfb\ -\x0f\xbe\xee\x79\x1e\xae\xeb\x82\xe7\x79\xca\x75\x5d\x76\x35\xb7\ -\x48\x7e\x70\x40\x5c\xd7\x15\x11\x41\x53\x4a\x49\x5b\x5b\xfb\xd9\ -\x79\xf3\xe7\x50\x97\x4c\xd1\xd6\xde\x4e\x6f\x6f\xff\x7a\x95\xed\ -\xec\xc4\x71\x1c\x71\x1c\x07\x91\x80\xfd\xfb\x0f\x51\x97\x4e\x2a\ -\xb5\xec\xd3\x2b\x64\x74\x74\x94\xaa\x57\xe5\xa5\x97\x7e\x8d\xa6\ -\x6b\xda\x75\xd7\x2d\x11\xed\x89\x8d\xbf\x50\xa9\x54\x8a\xfa\x99\ -\x19\x86\x0b\x05\x6e\xbb\x67\x6d\xd0\x9b\x9e\x21\x4a\x44\xf0\x7d\ -\x1f\x11\x51\x4a\x29\x0d\xf0\x8f\x1e\x3b\xde\x5e\x2a\x4d\x2c\xcf\ -\xcc\x9e\x41\xfd\xac\x7a\xc6\xce\x9d\x2b\x24\x93\xc9\x74\x10\xf8\ -\x68\x5c\x0c\xa5\x94\xe8\xba\xee\xff\xf3\xed\x7f\x09\xa8\xe5\x97\ -\xcd\xa9\x27\x9d\x4a\x13\x32\x0c\x50\xd4\xed\x6c\xde\x2d\x3d\x3d\ -\x67\xee\x57\x22\xc2\x91\xa3\x27\xf0\x7c\xf7\x85\xb1\x73\x63\x0f\ -\xd5\xd7\x67\x88\xd5\x38\xc4\x63\xb5\x18\x21\x83\x8e\x6c\x96\xbe\ -\xbe\x3c\x99\xfa\x59\x5c\xd9\xd8\xa8\x94\x88\x70\xe3\xb2\x5b\x24\ -\x3f\xd0\x8f\xef\x07\x3c\xf5\xd4\x93\xdc\x77\xdf\x57\x70\x5d\x97\ -\xf6\x3d\x7b\xa8\x56\x5c\x6a\x6b\x63\xa7\x8a\x23\xc5\x85\x4b\x97\ -\x2e\x45\x89\x08\x2f\xbe\xf8\x9b\xef\xfd\xf9\x4f\x5b\x9f\x77\x5d\ -\x17\xd7\x75\xf1\x83\x80\x52\xe9\x02\x6f\xfc\xed\x35\x2a\x95\xf2\ -\x7b\xcb\x9f\x79\xf6\x36\x0e\x1c\x40\xfa\xfb\xb3\x4a\x44\x78\xff\ -\xbd\x66\xaa\x6e\x55\xf5\xf6\xf5\xd5\xec\xdb\xb7\xef\xef\x67\xce\ -\xf4\xae\x0a\x3c\x9f\x1f\x6d\xf8\xfe\xb7\xeb\x67\x67\xb6\x04\xd7\ -\x2c\x39\xd8\x07\x4b\xb2\xf0\xc4\xf4\xee\xa6\x76\xe6\xba\x2e\x43\ -\x43\x43\xcb\x9a\x9b\x5b\x77\x77\xe7\x72\x0f\x07\x12\x4c\x9d\x2b\ -\x11\xb9\x34\x25\xc0\x50\x4a\xa1\x69\x5a\xe8\x7f\xfb\x0f\xed\xad\ -\x4b\xa7\x56\xda\x31\xe7\x19\xdf\xf3\xa5\x58\x2c\xde\x0d\x28\x60\ -\x3a\xc1\x06\x3c\x5d\xd7\x79\xe7\xdd\x1d\xa5\xa9\x49\x45\xc2\x61\ -\x34\x4d\x23\x9d\x4e\x6f\x3b\x79\xaa\xeb\xa1\x8f\x27\x94\x26\xc7\ -\x7b\xec\xb9\x44\x22\x1e\x32\xad\x30\xd1\x68\x14\xcb\xb4\xf0\x3c\ -\x8f\xbd\xff\xf9\x90\xae\xae\xdc\x26\x00\x4d\x29\x85\x52\x8a\x89\ -\x72\xe5\xe6\x9e\x9e\xd3\x3f\x88\x27\xe2\xd8\xb6\x4d\xcc\xb6\xd1\ -\x35\x8d\xfe\x7c\x3f\xe3\xa5\x32\x33\x66\xa4\x27\xef\x7d\xec\x78\ -\x16\xa5\x14\x9d\x9d\xd9\xd6\x2b\x1b\x1b\xb1\x6d\x0b\x3b\x6a\x63\ -\x18\x21\xca\xe5\x32\xd9\xce\x93\x18\x7a\x88\x2b\x1a\x17\xdc\x05\ -\xa0\xf9\x81\x0b\xe2\xbf\xd9\xd8\x78\x05\x89\x54\x9c\x68\x34\x4a\ -\xc4\x8c\xa0\x94\xe2\x78\xc7\x09\x02\x3f\xc0\x71\x9c\x4a\x3e\x9f\ -\xff\x07\x80\xd6\x71\x3c\xfb\xe0\x3d\x6b\xef\x5d\xbb\x7a\xd5\xe7\ -\x58\xf7\xb5\xfb\x89\xd9\x31\xcc\x70\x84\xe1\xe2\x30\x7d\xbd\x79\ -\x92\xc9\x14\x23\xa3\xc3\x73\x0b\x85\x02\x00\xda\xe0\xc0\x50\x4d\ -\x32\x99\x24\x9d\x4e\x93\xcb\xe5\x98\xdd\x30\x9f\xad\x5b\x5f\xe3\ -\x54\x57\x17\xc9\x54\x0a\xcf\x73\x5b\x95\xd2\x07\x17\x2f\x5e\x34\ -\x99\xb0\xe8\xea\x45\x4f\x2a\xa5\x70\x6c\x87\x1a\x27\x46\x66\xe6\ -\x2c\x1e\x7b\xec\x71\x3e\xdc\xb3\x8f\xba\xba\x04\xcb\x9f\xdf\xf4\ -\x97\xdb\x7e\xf2\x88\x24\x17\x5f\x2d\x25\x5d\xbf\x5e\x93\x20\x60\ -\xc3\x8f\x37\x44\x63\xb1\x18\xb1\x58\x8c\x44\x22\x4e\x32\x91\xe4\ -\x0f\x5b\x5e\xa6\x5c\x2e\xff\x9e\x7d\xfb\x7e\xce\x47\x1f\x31\x91\ -\xcf\xd3\x1a\x04\x3f\xd5\x08\x14\x31\xc7\xae\xac\xff\xce\x7a\xb5\ -\x72\xf5\xca\xf5\xa9\x74\x9d\xe7\xc4\x62\x5c\x75\xcd\x55\xe3\xa1\ -\x50\xe8\x81\xfe\xee\xee\x9b\xdb\x80\x57\xc0\xcf\xc1\xba\x69\xc7\ -\x4d\x85\x88\xa0\x94\x02\x50\x22\x22\x00\xba\xae\x53\x28\x0c\x7f\ -\xfe\xd0\xa1\xc3\x6f\x5e\x28\x95\xac\x54\x2a\x49\xaa\x2e\x85\xe3\ -\xd8\xe8\x9a\x36\x61\xe8\xc6\x66\xcb\xb2\x36\x45\xa3\xd1\x1c\x40\ -\x10\x08\x20\x84\x42\x21\x3e\x01\x10\x11\x03\x98\xa5\x94\xca\x03\ -\xfe\x54\xf1\xae\xee\xdc\x13\xc7\x4f\x64\x1f\x8d\xd7\xd6\xe0\x38\ -\x0e\xb6\x1d\x25\x1c\x09\x61\x59\x16\x4e\xd4\x26\x12\x89\xa0\x69\ -\x1a\x63\x63\x63\x72\xfe\xfc\xf9\xe7\x32\x99\xcc\xc3\x0a\x41\x69\ -\xfa\x27\x00\xea\x52\x13\x93\x5d\xb8\xae\x1b\xdb\xb3\xe7\xc3\x0f\ -\x2a\xd5\xea\x92\x54\x2a\x85\x15\x35\xb1\xed\x28\x91\x48\x04\xd3\ -\x34\xb1\xa3\x51\x42\x46\x08\x11\xa1\x5a\xad\x32\x30\x38\x48\xae\ -\xa7\x87\xf1\xd2\x44\xe1\xb3\xab\x57\xdd\x60\x59\xe6\xe9\x8f\xbb\ -\x47\x00\x51\x4a\x61\x18\x06\xc5\xe2\xc8\x1d\xef\xbc\xfb\xfe\x98\ -\x6e\x18\x4b\x1a\x1a\x1a\x88\xc7\xe3\xc4\x62\x0e\xa6\x65\x62\xdb\ -\x36\x4e\x34\x4a\x48\x37\x10\x11\xc6\xc7\xc7\xe9\xca\x75\xd3\xd5\ -\x9d\xc3\xf7\x02\x62\xb1\x58\x5d\x47\x47\xf6\x5b\x00\x06\xc0\xe0\ -\xd0\xd0\xc7\x28\x12\x3e\xdd\xd3\xfb\xd6\xe0\xe0\xd9\xdb\x1b\x1a\ -\x1a\xb0\xa3\x36\x11\x33\x4c\xd8\x0c\x61\x86\x23\x58\xa6\x45\x38\ -\x1c\x46\xd7\x34\x7c\x11\xc6\xc6\xc6\xe8\xec\x3c\x49\xa9\x34\x8e\ -\xef\xfb\x44\x22\x61\xe6\xce\x9b\xd3\x3b\x3b\x93\xd9\x32\x0d\x28\ -\x9c\x1d\x9e\x34\x92\x61\x34\xe5\x07\x06\xda\x5d\xd7\x0d\xcf\x9f\ -\x37\x97\x88\x15\xc1\x34\xc3\x84\xc3\x61\x22\xa6\x89\x15\x89\x10\ -\x32\x42\x68\x9a\x86\xe7\xfb\x14\x86\x0b\x74\x74\x74\xe2\xba\x1e\ -\x22\x42\x6d\x6d\x2d\x89\x44\xbc\x35\x12\x09\xaf\x1c\x2e\x16\x99\ -\x39\x63\xc6\x24\xc0\x34\x4d\x4e\x9c\xc8\x3e\xd2\xd2\xd2\xf2\x94\ -\x1f\x04\xcc\x9d\x73\x19\x46\x48\xa7\xa9\xe9\x06\x6a\x62\x35\x48\ -\x10\x20\x32\xf9\xf1\x69\x4a\xc3\x75\x5d\xfa\x06\xf2\x74\x76\x74\ -\x22\x02\xa6\x69\x91\x48\xc4\x19\x3b\x7f\xee\xe9\xfc\x40\xff\x23\ -\xfd\xf9\x3e\x2c\xcb\xba\x04\x78\xfd\xaf\x6f\xbc\xb5\x6d\xdb\xf6\ -\x2f\x2a\xa5\x28\x97\xcb\x54\xdd\x2a\x55\xd7\xa5\x54\x2a\x51\xa9\ -\x54\x58\xb1\xe2\x26\x36\x6e\x7c\x9c\x6b\x97\x5c\x83\x88\xd0\x79\ -\xe2\x24\xa7\x4e\x75\x4d\x17\xb6\xa2\x26\x85\x42\xe1\xbb\x9e\xe7\ -\x6e\x0e\x87\xc3\x2c\x5c\xb8\x10\xcb\xb2\x98\xd2\x3a\xdb\xb7\xbf\ -\xdd\xf0\xca\xcb\xaf\x7c\xd4\xd7\xd7\x3f\x73\x0a\xe2\xfb\x3e\xbe\ -\xef\x13\x04\x01\x82\x50\xae\x56\x29\x4f\x4c\xf0\x8d\x6f\xae\x63\ -\xcd\x9a\xbb\x26\x9f\xff\x78\x0d\x20\x14\x8b\x23\xb7\xdf\xfa\xea\ -\xab\xb3\xa9\x56\x1f\x62\x64\x64\x16\x83\x83\xbb\xc9\xe5\x1e\xa5\ -\x54\xea\x52\x22\xc2\x8e\x9d\xcd\xa0\x14\x2d\xbb\x5a\x7e\xd5\xbc\ -\x6b\xf7\x0f\xfd\xc0\x47\xd7\x75\x82\x20\x98\x86\x04\x41\x80\x04\ -\xc2\x44\x79\x82\x4c\x7d\x86\x4d\x2f\x3c\x4b\x28\x64\x54\x0b\x85\ -\xc2\xad\xb7\xfc\xec\xb1\xcd\xe4\xf3\x9f\xa2\xbb\x1b\x2a\x15\x04\ -\x28\x88\x30\x02\xbf\x9d\xfc\x46\xde\x6f\x9e\x54\x50\x20\x8c\x8e\ -\x8e\xd2\xd5\xdd\xfd\xe5\x23\x47\x8e\xfc\x72\x70\x60\xf0\x32\xcf\ -\xf3\xd0\x75\x1d\xa5\xd4\xb4\x21\xc7\xc7\x27\x58\xfb\xa5\x35\xe7\ -\xef\xfc\xc2\x1d\x9f\x09\x82\xe0\xe8\xe2\xeb\xae\xdf\x56\xf6\xfd\ -\xbb\xe5\xa2\xd6\x4f\x03\x87\x81\x32\x3c\x38\xd9\xc1\x8e\x5d\x53\ -\x2a\xd5\x45\x84\x73\x63\x17\x7c\xdf\xf7\x29\x97\xcb\x14\x8b\x45\ -\x2a\x95\x6a\x93\xae\x6b\x77\x2a\x8d\xc6\x78\x6d\x7c\x74\xc1\x82\ -\xcb\xff\x18\x4f\xd4\xfe\x57\xd3\x15\x86\x6e\xe0\x2c\xbe\x8a\x3e\ -\x58\x7c\x1a\x1e\x38\x0b\x0d\x25\x78\x2f\x0c\x5b\x2c\xf0\xfe\x0f\ -\xa4\xa4\xa5\x79\xe8\x4b\xcf\x5e\x00\x00\x00\x00\x49\x45\x4e\x44\ -\xae\x42\x60\x82\ -\x00\x00\x03\x34\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x01\x68\xf4\xcf\xf7\ -\x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\ -\xa7\x93\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\ -\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x02\xd4\x49\x44\x41\x54\x38\ -\xcb\x55\x92\x4f\x68\x5d\x65\x10\xc5\x7f\xdf\xbb\x37\xaf\x49\x48\ -\xd5\x04\x23\x22\x22\x88\x85\x08\x12\x05\x0b\xa2\x0b\xc1\x8d\x90\ -\x6c\x14\x04\xa1\xee\x94\x42\x15\x14\x22\xed\x56\x77\x6e\x0d\x82\ -\x2b\x05\x97\xba\x15\xdd\x58\x10\x0a\x82\xab\x5a\x50\x2b\x41\x24\ -\xb1\x31\x31\x79\x6d\xfe\xbc\x97\xfb\xde\xfd\xf7\xdd\xef\x7e\x33\ -\xe3\xe2\x3e\x0a\xce\x76\xe6\x9c\x39\x67\xe6\xb8\x67\x77\xde\xe4\ -\xa9\xfe\xe3\xb1\x57\xab\x6f\xbe\x9d\xdc\x48\x92\xb7\x0f\x3e\x5e\ -\xa9\xde\x7b\x68\xe8\x66\xb7\x5e\x30\x7f\x98\xe3\x5e\xdb\xdb\x58\ -\x3d\x8a\xc3\xdb\xdb\x61\x9f\x5c\x4b\x1c\x37\x57\xe1\xb8\x35\x1a\ -\x85\xdf\x2b\xdc\xeb\x7b\x1b\x3c\x98\x9c\xbf\xb5\x1b\x0e\x7f\xda\ -\x6a\xfe\xbe\x96\x02\x76\xa3\xbc\x49\xa1\xd5\xc5\x6c\x32\xde\x48\ -\x7f\xa9\xb7\x18\xc4\x13\x10\x83\x3f\xab\x24\x1d\x1c\x0c\xa0\x56\ -\x18\x04\xd8\x6b\x36\xdd\xfa\x3f\xef\xb3\x9c\x2e\xfe\x20\x26\x6b\ -\xa7\x92\x91\x49\x4e\xa9\x35\x99\xe6\x8c\x64\x7c\x2e\x2d\xb5\xde\ -\x3e\xf2\xc3\x0b\x07\xf1\x88\xa1\x64\xa8\x19\x00\x56\x44\x18\xc6\ -\x26\xbd\xe5\xb7\xae\x57\xea\x3f\x20\x76\x0d\x0c\x28\x04\xee\x34\ -\x70\x37\xe0\xf8\xf9\x19\x38\x89\x30\x8c\x39\x85\x2c\x50\x08\x8c\ -\x05\xc4\xde\xe5\x91\x99\x2f\xdd\x2b\xbb\x97\x79\x2c\x5d\xe6\x9c\ -\xeb\x7f\x5a\x5b\xb3\x91\x49\xfe\xdb\x71\x1c\x5d\x3a\x96\xd1\xce\ -\x99\x4c\x48\x1f\x4d\x1f\xee\xcf\xb8\xb4\x19\x6b\xc1\x69\xcc\x28\ -\xb4\xba\x38\xd1\x62\xbb\x52\x7f\xbd\xb1\xb0\x9e\x06\x6b\xab\x91\ -\x8c\xd9\x6f\xef\x31\x94\x8c\x68\x42\x34\x21\x8f\xc5\x1a\x13\x59\ -\x49\x8f\xe2\x30\x39\x8e\x23\x0e\xe2\x11\x5e\x43\xa7\x33\x2a\x8c\ -\x22\x64\xf2\x75\x3a\x88\x27\x8c\xa5\xc0\x6b\xc0\xb0\xce\x85\x57\ -\x38\x8b\x70\x1c\x9f\x48\xff\x6d\xef\x11\x25\x42\x04\x12\xa0\x35\ -\x38\x8d\x70\x18\xa0\xd4\x6f\x12\x7d\x6b\x69\x91\x91\xbc\x48\x26\ -\x1d\xed\xdd\x00\xbb\x4d\x67\xbd\xdf\x7b\x29\xa5\xd6\x0f\x19\xb6\ -\xbb\x8c\xe5\x33\x4a\x85\x89\x40\x21\x05\xb3\xbd\xf3\x2c\xa7\xb8\ -\x97\xef\xbc\xc3\x52\xf2\x00\x0b\xbd\x79\xfa\x6e\xe6\xaa\x73\xee\ -\x93\x68\x32\xd7\x58\xc0\x6b\x83\xb7\x40\x63\x81\x5a\x1b\x2a\xf3\ -\x75\xa5\xfe\xa3\x4a\xeb\xcd\xda\x1a\x82\xb5\x5d\x20\xe6\x7a\xb3\ -\x5f\xf4\x70\x57\x5a\x8b\xd4\xd6\x90\x6b\x49\xa1\x35\x5e\xbb\x21\ -\x41\x11\x13\x82\xb5\xf8\x29\x99\xd7\x66\x93\x68\xd7\xd2\xc6\xda\ -\xcf\x83\xc4\x2b\x7e\x0a\x3c\x93\x9c\x4c\x26\xd4\xd6\xd0\x5a\xec\ -\x2e\x03\x38\x1c\xd1\x04\x6b\x15\x1a\x85\x5a\xaf\x12\x2c\x71\xcf\ -\xef\x5c\x6a\x22\xd2\xaf\xd5\x53\x6a\x4d\xae\x15\xb5\x79\xc4\xf4\ -\x3e\xf8\x7e\x48\x1a\x85\x4a\xbb\xb0\x14\x0a\x5e\x4f\xd2\x41\x3c\ -\xd9\x6f\xad\xbd\xd0\x5a\xa4\x25\x76\x92\x55\xf9\x5f\x99\x75\xe7\ -\x2f\xa7\xff\x19\x0b\xe4\x02\xc1\xf6\x1d\xb7\x9f\x5b\x25\xe8\xaf\ -\x44\x4b\x88\xd3\x47\x76\x9a\xbb\xd2\xe9\xe6\x52\x21\x8b\x90\x4d\ -\xc1\xad\x09\x33\xee\xe9\x94\x42\xfe\xa0\xd2\x79\x6a\xfd\x0e\xaf\ -\x6b\xb4\x06\x6a\x20\x40\x34\x08\xd6\x11\x14\xd2\xc9\x0f\x06\xf0\ -\x3d\x73\xbd\x37\x98\xef\x49\x8a\x03\x7a\x04\xcc\xd6\x09\x06\xa5\ -\x3c\x49\xa5\x97\xa9\xf4\x55\xbc\xae\xd0\x1a\xb4\xf6\x17\x6a\x3f\ -\xd2\x73\x5f\x31\xeb\x76\x59\x48\x60\x29\x85\xc5\x94\xff\x00\xe1\ -\x78\x1f\x4c\x73\x1c\xbc\x8b\x00\x00\x00\x00\x49\x45\x4e\x44\xae\ -\x42\x60\x82\ -\x00\x00\x07\x6a\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x01\x97\x70\x0d\x6e\ -\x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\ -\xa7\x93\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\ -\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x07\x0a\x49\x44\x41\x54\x48\ -\xc7\x55\x95\x6b\x70\x5c\x65\x19\xc7\x7f\xef\x39\x67\xf7\x9c\xb3\ -\x97\x64\x2f\xd9\x96\x4d\x43\x2f\x50\xc2\x08\xd2\x32\x50\x2d\x96\ -\x62\x5b\x75\x10\x61\x04\x06\x54\x04\x01\xc5\xaa\x0c\x1f\x44\x40\ -\x40\x1c\x9d\xd1\x51\x04\xf1\x02\x32\x38\xf5\xc2\x54\x07\x66\xb4\ -\x53\x2e\x56\x40\x46\xa0\x97\x34\x09\xb1\x05\x94\xd2\x0b\xb4\x49\ -\xda\x5c\x20\xc9\xe6\xb2\xd9\x26\xdb\xec\x66\xf7\xbc\xe7\x9c\xc7\ -\x0f\x21\x01\x9e\x8f\xef\xcc\xfb\xfc\xde\xe7\x79\xfe\xff\xf7\x51\ -\x22\x82\xef\xfb\xd4\xbc\xfa\x6a\x44\x84\xb7\x0e\x1e\x96\x6a\x6d\ -\x56\x10\x11\x56\xac\x68\x95\x83\x87\x0f\x09\x5a\x6b\x3a\x3b\xbb\ -\x1e\xd2\x5a\xa3\xea\xf5\x3a\x33\xd5\x8a\x1c\x7c\xeb\x30\xc6\x45\ -\x6b\x2f\x11\xc7\x76\x58\xd2\xd2\xac\x78\x3f\x5b\xe3\xf8\x44\x71\ -\x43\x77\x6f\x8f\x78\x9e\x27\xff\xd9\xbf\x5f\x0c\x80\xf6\x8e\xce\ -\x29\x3f\xd0\x7b\xcf\x58\xbe\x82\x5d\xbb\x77\x13\xb1\xac\xf5\x06\ -\x40\xdb\xee\x8e\x3f\xdd\x70\xdd\xcd\x5c\xbc\x6e\x23\xa9\x74\x4a\ -\x7d\xc2\xdd\xfc\x2a\xbe\xef\xe3\xfb\x3e\x41\x10\x70\xe0\xc0\xc1\ -\xa7\x7d\xdf\x47\x6b\x0d\xbe\xef\x2b\xad\x35\x7b\xda\xda\xa5\x30\ -\x36\x2a\x5a\x6b\x11\x11\x0c\xa5\x94\x74\x76\x76\x4d\x2c\x5f\xb1\ -\x94\xa6\x4c\x96\xce\xae\x2e\x86\x86\x46\x36\xab\x9e\xde\x5e\x12\ -\x89\x84\x24\x12\x09\x44\x42\x0e\x1c\x38\x44\x53\x2e\xa3\xd4\xda\ -\x4f\xae\x97\xa9\xa9\x29\x3c\xdf\x63\xcb\x96\xdf\x63\x98\x86\x71\ -\xfe\xf9\xab\xc4\xb8\xff\x81\x9f\xa9\x6c\x36\x4b\xf3\xe2\x3c\x93\ -\xc5\x22\x57\x2c\xf9\x41\xb8\xf8\xa5\x75\xa2\x44\x84\x20\x08\x10\ -\x11\xa5\x94\x32\x80\xe0\xed\x77\x8e\x76\x55\x2a\xb3\xeb\xf2\x4b\ -\x16\xd1\x7c\x5a\x33\xe5\xe9\xe9\x62\x26\x93\xc9\x85\x61\x80\xc1\ -\xfb\xa1\x94\x12\xd3\x34\x83\x7f\xbd\xf8\x6f\x01\xb5\xee\xf4\xa5\ -\xcd\xe4\xb2\x39\x22\x96\x05\x8a\xa6\xdd\x6d\x7b\x65\x70\xf0\xbd\ -\x5b\x94\x88\x70\xe4\xed\x63\xf8\x81\x7e\xb4\x3c\x5d\xbe\xbd\xb9\ -\x39\x4f\xb2\x21\x41\x2a\xd9\x88\x15\xb1\xe8\xee\xe9\x61\x78\xb8\ -\x40\xbe\xf9\x34\xce\x6e\x6d\x55\x4a\x44\xb8\x68\xed\xa7\xa5\x30\ -\x3a\x42\x10\x84\x3c\xf8\xe0\x2f\xb8\xe9\xa6\xeb\xd1\x5a\xd3\xb5\ -\x6f\x1f\x5e\x5d\xd3\xd8\x98\x3c\x51\x3a\x59\x5a\x79\xc1\x05\x17\ -\xa0\x44\x84\xc7\x1e\xfb\xc3\xf7\xfe\xfe\xb7\x6d\xbf\xd3\x5a\xa3\ -\xb5\x26\x08\x43\x2a\x95\x19\x9e\xf9\xc7\x76\xea\xf5\xda\x2b\x77\ -\x2d\xda\x72\x69\x8f\x37\xc8\x74\xad\x1c\x28\x11\x61\xe7\x2b\x6d\ -\x78\xda\x53\x43\xc3\xc3\x0d\x6f\xbc\xf1\xc6\x3f\xdf\x7b\x6f\x68\ -\x63\xe8\x07\x7c\xff\x9e\x3b\xbe\xd5\xbc\x24\xbf\x75\xf5\xa1\x6b\ -\xa7\x99\xd0\x0d\x0c\x7b\x77\x2c\xcc\x6e\x7e\x66\x5a\x6b\xc6\xc7\ -\xc7\xd7\xb6\xb5\x75\xec\xed\x1f\x18\xb8\x3b\x94\x70\xfe\x5c\x89\ -\xc8\x07\x5d\x02\x2c\xa5\x14\x86\x61\x44\xde\x3c\x70\x68\x7f\x53\ -\x2e\xbb\x21\x9e\x4c\xfc\x3a\xf0\x03\x29\x95\x4a\x57\x03\x0a\x58\ -\xb8\x10\x07\x7c\xd3\x34\x79\xe9\xe5\x5d\x95\xf9\x4e\xd9\xd1\x28\ -\x86\x61\x90\xcb\xe5\x76\x1c\x3f\xd1\x77\xfb\x87\x2f\x54\xe6\xda\ -\xfb\xce\xc3\xe9\x74\x2a\xe2\xb8\x51\x62\xb1\x18\xae\xe3\xe2\xfb\ -\x3e\xfb\x5f\x7f\x8d\xbe\xbe\x81\x47\x00\x0c\xa5\x14\x4a\x29\x66\ -\x6b\xf5\x4b\x06\x07\xdf\xbd\x33\x95\x4e\x11\x8f\xc7\x49\xc6\xe3\ -\x98\x86\xc1\x48\x61\x84\x6a\xa5\xc6\xa2\x45\xb9\xb9\x77\xbf\x73\ -\xb4\x07\xa5\x14\xbd\xbd\x3d\x1d\x67\xb7\xb6\x12\x8f\xbb\xc4\x63\ -\x71\x2c\x2b\x42\xad\x56\xa3\xa7\xf7\x38\x96\x19\xe1\xac\xd6\x33\ -\xaf\x04\x30\x82\x50\x83\x04\xcf\xb6\xb6\x9e\x45\x3a\x9b\x22\x16\ -\x8b\x61\x3b\x36\x4a\x29\x8e\x76\x1f\x23\x0c\x42\x12\x89\x44\xbd\ -\x50\x28\xbc\x00\x60\x74\x1f\xed\xf9\xee\xb5\xd7\x5c\x77\xcd\xa6\ -\x8d\x9f\xe3\xc6\xaf\xdd\x42\x32\x9e\xc4\x89\xda\x4c\x96\x26\x19\ -\x1e\x2a\x90\xc9\x64\x39\x39\x35\xb9\xac\x58\x2c\x02\x60\x8c\x8d\ -\x8e\x37\x64\x32\x19\x72\xb9\x1c\x03\x03\x03\x2c\x69\x59\xc1\xb6\ -\x6d\xdb\x39\xd1\xd7\x47\x26\x9b\xc5\xf7\x75\x87\x52\xe6\xd8\x39\ -\xe7\x7c\x6c\x4e\xa4\x3b\x77\xb7\xf1\x93\x1f\xff\x54\x82\x20\xc0\ -\xf3\x3c\xb4\xf6\xf1\x74\x9d\xdb\x6e\xbb\x95\xcb\x2e\xbf\x94\x1f\ -\x3a\x7f\x9c\x7d\xbd\x76\xc4\x2d\x87\x15\x66\x26\xcb\xd7\x19\x12\ -\x86\xdc\x73\xef\x3d\xb1\x64\x32\x49\x32\x99\x24\x9d\x4e\x91\x49\ -\x67\xf8\xcb\xd6\x27\xa8\xd5\x6a\x7f\x7e\xb5\x76\xc0\x1d\xf1\x27\ -\x98\x39\x59\x86\x37\xab\x5f\x36\x08\x15\xc9\x44\xbc\xbe\xf9\xdb\ -\x9b\xd5\x86\x4d\x1b\x36\x67\x73\x4d\x7e\x22\x99\xe4\xdc\xf3\xce\ -\xad\x46\x22\x91\x5b\x8b\x7d\x63\xb7\xb2\x7f\x06\x5e\x98\xaa\x32\ -\xa1\xaf\x5f\x70\xdc\x7c\x88\x08\x4a\x29\x00\x25\x22\x02\x60\x9a\ -\x26\xc5\xe2\xe4\x17\x0e\x1d\x3a\xfc\xec\x4c\xa5\xe2\x66\xb3\x19\ -\xb2\x4d\x59\x12\x89\x38\xa6\x61\xcc\x5a\xa6\xf5\xb8\xeb\xba\x8f\ -\xc4\x62\xb1\x01\x80\x30\x14\x40\x88\x44\x22\x7c\x04\x20\x22\x16\ -\x70\x9a\x52\xaa\x00\x04\xf3\xc9\xfb\xfa\x07\xee\x3f\x7a\xac\xe7\ -\x47\xa9\xc6\x06\x12\x89\x04\xf1\x78\x8c\xa8\x1d\xc1\x75\x5d\x12\ -\xb1\x38\xb6\x6d\x63\x18\x06\xe5\x72\x59\x4e\x9d\x3a\xf5\x70\x3e\ -\x9f\xbf\x5b\x21\x28\xc3\xfc\x08\x40\x7d\x50\xc4\x5c\x15\x5a\xeb\ -\xe4\xbe\x7d\xaf\xbd\x5a\xf7\xbc\x55\xd9\x6c\x16\x37\xe6\x10\x8f\ -\xc7\xb0\x6d\x1b\xc7\x71\x88\xc7\x62\x44\xac\x08\x22\x82\xe7\x79\ -\x8c\x8e\x8d\x31\x30\x38\x48\xb5\x32\x5b\xfc\xcc\xa6\x8d\x17\xba\ -\xae\xf3\xee\x87\xdd\x23\x80\x28\xa5\xb0\x2c\x8b\x52\xe9\xe4\x65\ -\x2f\xbd\xbc\xb3\x6c\x5a\xd6\xaa\x96\x96\x16\x52\xa9\x14\xc9\x64\ -\x02\xc7\x75\x88\xc7\xe3\x24\x62\x31\x22\xa6\x85\x88\x50\xad\x56\ -\xe9\x1b\xe8\xa7\xaf\x7f\x80\xc0\x0f\x49\x26\x93\x4d\xdd\xdd\x3d\ -\xdf\x04\xb0\x00\xc6\xc6\xc7\x3f\x44\x91\xe8\xbb\x83\x43\xcf\x8f\ -\x8d\x4d\x7c\xbe\xa5\xa5\x85\x78\x2c\x8e\xed\x44\x89\x3a\x11\x9c\ -\xa8\x8d\xeb\xb8\x44\xa3\x51\x4c\xc3\x20\x10\xa1\x5c\x2e\xd3\xdb\ -\x7b\x9c\x4a\xa5\x4a\x10\x04\xd8\x76\x94\x65\xcb\x97\x0e\x2d\xc9\ -\xe7\xb7\x2e\x00\x8a\x13\x93\x73\x46\xb2\xac\x35\x85\xd1\xd1\x2e\ -\xad\x75\x74\xc5\xf2\x65\xd8\xae\x8d\xe3\x44\x89\x46\xa3\xd8\x8e\ -\x83\x6b\xdb\x44\xac\x08\x86\x61\xe0\x07\x01\xc5\xc9\x22\xdd\xdd\ -\xbd\x68\xed\x23\x22\x34\x36\x36\x92\x4e\xa7\x3a\x6c\x3b\xba\x61\ -\xb2\x54\x62\xf1\xa2\x45\x73\x00\xc7\x71\x38\x76\xac\xe7\xbe\xf6\ -\xf6\xf6\x07\x83\x30\x64\xd9\xd2\xd3\xb1\x22\x26\x6b\xd6\x5c\x48\ -\x43\xb2\x01\x09\x43\x44\xe6\x16\x9f\xa1\x0c\xb4\xd6\x0c\x8f\x16\ -\xe8\xed\xee\x45\x04\x1c\xc7\x25\x9d\x4e\x51\x3e\x35\xfd\x50\x61\ -\x74\xe4\xbe\x91\xc2\x30\xae\xeb\x7e\x00\x78\xfa\xa9\x67\x9e\xdf\ -\xb1\xe3\xb9\x2f\x2a\xa5\xa8\xd5\x6a\x78\xda\xc3\xd3\x9a\x4a\xa5\ -\x42\xbd\x5e\x67\xfd\xfa\x8b\x79\xe0\x81\x9f\xb3\x7a\xd5\x79\x88\ -\x08\xbd\xc7\x8e\x73\xe2\x44\xdf\x42\x62\x37\xe6\x50\x2c\x16\xbf\ -\xe3\xfb\xfa\xf1\x68\x34\xca\xca\x95\x2b\x71\x5d\x97\x79\xad\xf3\ -\xdc\x73\x2f\xb6\x3c\xf9\xc4\x93\xff\x1d\x1e\x1e\x59\x3c\x0f\x09\ -\x82\x80\x20\x08\x08\xc3\x10\x41\xa8\x79\x1e\xb5\xd9\x59\xbe\xfe\ -\x8d\x1b\xb9\xea\xaa\x2b\xe7\xbe\xff\x54\x03\x20\xec\xb0\x3b\x7e\ -\x39\x90\x1c\xdf\x3c\x19\x4c\xe7\x4e\xfa\x65\xa6\xc2\xf2\x54\xc1\ -\x2f\xde\x3b\x1b\xd4\xb6\x2a\x11\x61\xd7\xee\x36\x50\x8a\xf6\x3d\ -\xed\xbf\x6d\xdb\xb3\xf7\xae\x20\x0c\x30\x4d\x93\x30\x0c\x17\x20\ -\x61\x18\x22\xa1\x30\x5b\x9b\x25\xdf\x9c\xe7\x91\x47\x7f\x43\x24\ -\x62\x79\xc5\x62\xf1\xb3\x77\xe6\xb7\x74\x0e\xea\x11\x26\x83\xe9\ -\x39\xb1\xeb\x10\x26\x7c\x98\x0e\xb6\xcf\xad\x91\x9d\x6d\x73\x0a\ -\x0a\x85\xa9\xa9\x29\xfa\xfa\xfb\xbf\x7a\xe4\xc8\x91\x5f\x8d\x8d\ -\x8e\x9d\xee\xfb\x3e\xa6\x69\xa2\x94\x5a\x30\x64\xb5\x3a\xcb\x35\ -\x5f\xba\xea\xd4\xe5\x57\x5c\xf6\xa9\x30\x0c\xdf\x5e\x5d\xbb\xa1\ -\x8e\x27\x51\x94\x02\x2d\x30\x54\x87\xfe\x3a\x04\xdc\x3c\x57\xc1\ -\xae\x3d\xf3\x2a\x35\x45\x84\xe9\xf2\x4c\x10\x04\x01\xb5\x5a\x8d\ -\x52\xa9\x44\xbd\xee\xad\x31\x4d\xe3\x72\x65\xd0\x9a\x6a\x4c\x4d\ -\x9d\x79\xe6\x19\x7f\x4d\xa5\x1b\xff\x67\x98\x0a\xcb\xb4\xf8\x78\ -\xdf\xb5\x30\xe2\x5d\xcd\x84\xfe\x0a\xe5\xc0\xa4\x2e\xcf\x12\x55\ -\x4f\x61\x1b\xfc\x1f\x0b\x03\xc8\x05\x59\x65\x3b\x42\x00\x00\x00\ -\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x00\xc5\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x90\x00\x00\x00\x90\x08\x06\x00\x00\x00\xe7\x46\xe2\xb8\ -\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ -\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0e\xc4\x00\x00\x0e\xc4\ -\x01\x95\x2b\x0e\x1b\x00\x00\x00\x67\x49\x44\x41\x54\x78\x9c\xed\ -\xc1\x31\x01\x00\x00\x00\xc2\xa0\xf5\x4f\xed\x69\x09\xa0\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x06\ -\x44\x9f\x00\x01\xc3\xcd\x96\xea\x00\x00\x00\x00\x49\x45\x4e\x44\ -\xae\x42\x60\x82\ -\x00\x00\x02\x7a\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ -\x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\ -\xa7\x93\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\ -\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\ -\xdd\x06\x0d\x08\x1d\x33\x51\xf1\xd4\x9e\x00\x00\x02\x07\x49\x44\ -\x41\x54\x38\xcb\x65\x91\x3d\x6b\x14\x41\x18\xc7\x7f\x73\xb7\x2c\ -\x6c\x0c\xb9\x20\xa6\x0d\x58\x45\x90\x88\x1f\x40\xb1\x12\x2e\x8d\ -\x82\x5f\x41\x08\x58\x05\xb4\xb5\x0c\xd8\x09\xa2\x20\x08\x7e\x88\ -\x58\x05\xac\x04\xfb\x08\x56\x42\x40\x48\x9d\xdc\xe5\xf6\x66\xe7\ -\x75\x67\xc7\xe2\x19\x72\x07\x0e\xfc\x18\x06\xe6\xff\x32\xcf\xa8\ -\x9c\x1f\x00\xb7\x81\x09\x70\x0b\xa8\xf7\x40\x1d\x42\x7a\x02\xe1\ -\x21\x78\xc0\xfe\x82\xee\x07\x74\x9f\x41\x9f\x83\x41\xf0\xa8\x9c\ -\x1f\x17\x83\x4d\xa0\x7e\x0d\xea\x18\xfa\x46\x84\xae\xe0\x01\x0b\ -\x18\x0b\xe6\x2d\x98\xf7\x72\x0e\xa8\x9c\x0f\x80\x49\x0d\xf5\x09\ -\xa8\x29\xf4\xe5\x72\x57\x76\x0f\x44\x20\xac\x19\x9a\x53\x70\xcf\ -\x21\x84\x11\xd4\x00\x1f\xa1\x9f\x4a\xad\x05\x70\x05\x5c\x96\x7d\ -\x06\x5c\x03\xcb\x62\xda\x01\x66\x9a\xb3\x79\x17\x42\x8f\xca\xf9\ -\xd9\x3e\x54\x67\x90\xc6\x92\xb8\x28\xe8\x92\x9e\x80\x5c\x48\x80\ -\x23\xa5\x88\x31\xa4\x10\xb8\x5f\x41\x38\x84\x38\x96\x6a\x4b\x60\ -\x5e\x12\x6d\xa9\x9e\x91\xa5\x80\x9e\x10\x32\xd6\x82\x31\x8c\xbd\ -\xe7\x55\x05\x66\x2a\xce\xb6\x18\x2c\xcb\x84\x03\x30\xb0\xbe\x62\ -\x14\x71\xd7\x09\xd6\xf2\xa8\x02\xbd\xfb\xff\xe0\x62\x11\xe7\x1b\ -\x71\xce\x10\x23\x78\x0f\xc6\xc0\x72\x09\xc6\xb0\x5b\x49\x62\xcf\ -\xea\xdb\xe2\xda\xbb\x57\xe2\x94\xa0\xef\xb9\x69\x50\x0c\x18\xc1\ -\xf2\x02\xda\x32\x34\x49\xcf\x39\x93\x33\x37\x0c\x83\xa4\x5b\x0b\ -\x5a\x43\xdb\x0a\x5d\xc7\xc5\x08\xda\x53\x99\x7a\x4b\x4a\x96\x18\ -\x13\x21\x48\x5a\x4a\xab\xda\x5a\xc3\xf5\x35\xcc\x66\x42\xdb\x82\ -\xb5\xfc\x54\x29\xb1\xef\x1c\x67\x31\x32\xee\x7b\x49\x04\x50\x4a\ -\xf6\x94\xc0\x39\xa9\x7c\x79\x09\x57\x57\xb0\x58\x40\x08\xa4\xba\ -\xe6\x5e\x65\x0c\xbf\xad\xe5\x93\x73\x1c\xc5\x28\xc9\xc3\xb0\x12\ -\xf7\xbd\xbc\xb5\x6d\x61\x3e\x17\xb1\xf7\x30\x1a\xf1\xa1\x69\x38\ -\x57\xb3\x19\x68\x4d\xdd\x75\x9c\x58\xcb\x34\x04\x11\xae\xd7\xb7\ -\x56\x0c\xb4\x96\x33\xf0\x6d\x63\x83\x17\x77\xee\x90\xaa\x61\x80\ -\x61\x20\xc4\xc8\x81\x73\x1c\x19\xc3\xb1\x73\x6c\x7a\x0f\x21\x48\ -\x7d\x6b\x85\x18\xd1\x4a\xf1\xa6\x69\xf8\xb2\xb5\x05\xdb\xdb\xa0\ -\xe6\x73\xf9\x96\xb6\x95\x7a\x6d\xcb\x5d\xad\x79\xa9\x35\x4f\xad\ -\x65\xcf\x7b\x88\x91\x3f\x29\xf1\x7d\x3c\xe6\x6b\xd3\xf0\x77\x32\ -\x81\x9d\x1d\xe1\x1f\x3c\x20\x6c\x94\x65\x65\x77\x27\x00\x00\x00\ -\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x06\xc9\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x90\x00\x00\x00\x90\x08\x02\x00\x00\x00\x68\x24\x75\xef\ -\x00\x00\x00\x03\x73\x42\x49\x54\x08\x08\x08\xdb\xe1\x4f\xe0\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0e\xc4\x00\x00\x0e\xc4\x01\ -\x95\x2b\x0e\x1b\x00\x00\x06\x6c\x49\x44\x41\x54\x78\x9c\xed\x9d\ -\x3b\x7a\xd3\x40\x14\x46\xc7\x7c\x2e\x48\x9f\x02\x0a\x16\x91\x2d\ -\x50\xb2\x04\x52\x7a\x5b\xa4\xcc\x16\x52\x66\x09\x64\x11\x14\x2e\ -\x48\xef\x52\x14\x0a\xf2\x58\x96\x34\x8f\xfb\xfa\xe7\x7a\x4e\x05\ -\xc4\x1e\x8b\x39\x3e\x1e\x7b\x90\xc5\xee\xd7\xdf\x2f\x21\x84\xc3\ -\xfd\x31\x60\xf0\x78\xf7\x66\x7d\x08\x73\x9e\x4f\x0f\xd6\x87\xf0\ -\xc1\xd3\xfb\xd7\xfd\xf4\xab\x80\xa1\x6d\x9c\x1d\x40\x6d\xb6\x8c\ -\x82\x42\x08\xbb\x61\x18\xa6\xdf\x8c\x20\x68\x1b\x01\xd1\x66\x5b\ -\xd8\xcc\xce\x7e\xed\x16\x08\xda\x6e\xbc\xb6\x99\xaa\x10\xc2\xe1\ -\xfe\xb8\x1b\x86\x61\xf1\x67\xd3\x2d\xc4\x8f\x2b\x0f\x43\x6d\xfa\ -\x85\x6d\xe8\x58\x28\xec\xfa\x9e\x08\xda\x6e\xa4\xb6\x35\x55\xe1\ -\xbf\x85\x8f\xc2\xb6\x6f\x1a\xdf\x01\x01\x65\x6d\x3a\x85\x65\xce\ -\x7f\xa2\xb0\xeb\x11\x11\xb4\x39\xab\x2d\xa9\x2a\x44\xd3\x7e\x2e\ -\x2c\xf3\x9e\xb3\xfb\x9b\xa3\xa0\x4d\xae\xb0\x8a\x09\x2f\x28\xec\ -\xfa\x91\x10\xb4\x35\x5a\x5b\xbe\xaa\x70\x39\xcf\x17\x85\x95\x0e\ -\x74\x3d\x9c\x2d\x42\xda\x78\x0b\x23\xce\x70\x65\x61\xd7\x47\x80\ -\xa0\x0d\xbc\xb6\x0a\x55\xe1\x6a\x62\x3f\x6d\xff\xb8\xe8\x68\xea\ -\x0e\x88\x1d\x9c\xad\xbf\x09\xc6\xc9\x61\x28\x2c\xc6\xbc\xb6\x38\ -\xaf\xe7\xd3\x83\x79\x6d\x44\x4f\xd7\x33\xb9\x20\xec\x70\x7f\x24\ -\x3e\x8c\x89\xb6\x45\x37\x86\x2f\x92\x42\xaf\x37\xcc\x85\xc5\xa8\ -\x69\x4b\xfa\x50\xd6\xc6\xa5\x6a\x71\xea\x96\x85\xd1\x23\x9b\x10\ -\xd5\x56\xe4\x40\x41\x9b\xc2\x2a\x2e\x58\x58\x0c\xbb\xb6\xea\x79\ -\x17\xd2\xc6\xae\x6a\x6d\xae\x56\x85\x31\x46\x36\xc1\xa2\x8d\x65\ -\xae\x19\xb5\x29\xbf\x37\x56\x2a\x2c\xa6\x5a\x1b\x7b\x16\x44\x6d\ -\x72\xaa\x36\x26\x67\x4b\x98\x44\x64\x13\x45\xda\x44\x17\x9e\x0a\ -\x6d\x86\x9f\x38\x0d\x0a\x8b\x49\x6a\x53\x7b\x6b\x97\xa9\x4d\x41\ -\xd5\xf6\x93\x38\x21\x4c\x34\xb2\x89\x45\x6d\x26\x1f\x9e\x36\xb4\ -\x81\xec\xe3\x18\x17\x16\x33\x69\x33\xdf\x9e\x98\x69\xd3\x54\x95\ -\x5c\x23\xe6\x7b\x89\x15\x43\xf0\x02\xf2\x44\x0e\xff\xb5\x7d\xff\ -\xf3\xc3\xfa\x40\x2e\x48\x0b\xd3\x07\x61\xf7\xf6\xf1\xee\x4d\x3f\ -\xf4\x9c\x36\xb2\x5e\x12\x75\x56\xb2\x18\xc3\x3d\x40\xf3\x17\xe4\ -\x6d\x80\xd6\xb0\x6b\x94\xb5\xd9\xaa\xca\x5c\x7a\x72\x85\xe9\x47\ -\x36\xa1\xa0\x0d\xbc\xaa\x18\xe8\xc2\x62\x84\xb4\x81\xa8\xca\x7f\ -\x67\x57\x20\xcc\x30\xb2\x09\x46\x6d\x20\xaa\x4a\x69\xa6\xb0\x18\ -\xa2\x36\x34\x55\x45\x1f\x9c\xca\x84\x21\x44\x36\x51\xa1\x0d\x4d\ -\x55\x05\x4d\x16\x16\x93\xa9\x0d\x56\x55\xe9\xbe\x44\xb1\x30\xa8\ -\xc8\x26\x36\xb4\xc1\xaa\xaa\xa3\xf9\xc2\x62\x66\xda\xf0\x55\x55\ -\x6c\xfb\xd5\x6c\x4d\x21\x9c\x33\xba\x01\xc2\xce\x96\x1c\xae\x0a\ -\x0b\x2d\x54\x35\x51\xf7\xbc\xaf\x14\x06\xb8\x92\x35\xa4\x8a\x82\ -\x87\xc2\x5a\x54\x55\xbd\xac\xd4\x0b\x43\x88\xac\x45\x55\x44\x5a\ -\x2d\xac\x69\x55\x94\x77\x6d\x24\x61\x26\x91\x35\xad\x8a\x4e\x4b\ -\x85\xf9\x50\x45\xfc\x50\x44\x15\xa6\x13\x99\x0f\x55\x2c\xa0\x17\ -\xe6\x4c\x15\x7d\xcf\x81\x41\x98\x50\x64\xce\x54\x71\x81\x58\x98\ -\x57\x55\x2c\x5b\x7a\x7b\xa6\xd9\x79\x41\x3b\x7f\x0f\x8d\xd7\x6f\ -\x2f\x87\x13\xc3\x38\xbb\x9f\x9f\x7f\x33\x0c\xe3\x1a\xfa\x6e\xf2\ -\x58\x05\xcb\x38\x6c\x27\x92\x3a\xde\x23\xe7\x7a\x89\x66\x19\x87\ -\x47\x98\x63\x5b\x74\x78\x7d\x73\x9e\xaa\xed\x58\x1b\x4e\x64\x0c\ -\xc2\x1c\x7b\xa2\xc3\x6e\x9a\xf9\xcb\x10\x8e\xe5\x81\x44\x46\x15\ -\xe6\xd8\x10\x1d\x09\xc7\xfc\x5f\x37\x72\xac\x10\x21\x32\x92\x30\ -\xc7\x6e\xe8\x08\xd9\x15\xf9\x42\x9f\x63\x91\xe6\x91\xd5\x0b\x73\ -\x6c\x85\x8e\x9c\x57\xa9\xaf\xcc\x3a\xd6\x69\x1b\x59\xa5\x30\xc7\ -\x3e\xe8\x88\x1a\x15\xfc\x52\xba\x63\xa9\x86\x91\xd5\x08\x73\x6c\ -\x82\x8e\xb4\x4b\xd9\xcb\x3e\x38\x56\x6b\x15\x59\xb1\x30\xc7\x0e\ -\xe8\x28\x58\x14\xbf\xb0\x8a\x63\xc1\x26\x91\x95\x09\x73\x3c\xfb\ -\x74\x74\xfc\x69\x5c\xba\xc8\xb1\x66\xfd\xc8\x0a\x84\x39\x9e\x77\ -\x3a\x6a\xe6\x94\x2e\x0e\xe6\x58\xb6\x72\x64\xb9\x67\x4d\x71\x9d\ -\x39\xd4\x21\xd2\x4f\x73\x6b\x0c\xc4\x33\x7f\x5b\x44\xed\x15\x28\ -\x6b\x0d\xe3\x5a\x81\xfa\x4a\x46\x27\x2d\xac\xdb\x52\x80\xff\x6d\ -\x7d\xd7\x96\x44\x27\xb2\x84\xb0\xee\x49\x01\xa9\xad\xa9\x2e\x2f\ -\x89\x42\x64\x5b\xc2\xba\x21\x05\x64\xff\x79\xa5\x2b\x4c\x22\x1d\ -\xd9\xaa\xb0\xee\x46\x01\x8d\x53\x04\xba\xc8\x24\xa2\x91\x2d\x0b\ -\xeb\x56\x14\xd0\x3b\xcd\xad\xeb\x4c\x22\x17\xd9\x82\xb0\xee\x43\ -\x01\xed\x53\xb5\xbb\xd4\x24\x42\x91\xcd\x85\x75\x13\x0a\xd8\x7c\ -\xdd\xa8\xab\x4d\x22\x11\xd9\x85\xb0\xee\x40\x01\xcb\xaf\xcc\x76\ -\xc1\x49\xd8\x23\x3b\x0b\xeb\xb3\xaf\x80\xfd\x65\x1f\xba\xe6\x24\ -\xbc\x91\x7d\x08\xeb\xf3\xae\x00\xca\xa5\x8b\xba\xec\x24\x8c\x91\ -\x7d\x0a\x7d\xc6\x55\xe0\x72\xb6\xfb\xf9\xf9\x37\xcb\x44\x33\x5e\ -\x94\xf4\xf5\xdb\x0b\xd7\x50\x74\x18\xaf\x03\xc9\xf2\xf7\xda\xa3\ -\xd9\x82\xe2\xf9\xf4\xf0\xf4\xce\x39\x1a\x7d\x90\x7e\x22\xe9\x32\ -\x12\x2f\xef\x4f\xef\x5f\x21\x2e\xd2\xec\x2c\x2f\xf0\x95\xb8\x17\ -\x76\x46\x41\x15\x3d\x32\xaa\x30\x1f\x79\x81\x57\x15\x73\xeb\x85\ -\xe9\xab\x22\x46\x46\x12\xd6\x74\x5e\x0d\x55\x15\x73\x8b\x85\x99\ -\xab\xa2\x44\x56\x2f\xac\xc5\xbc\xcc\x55\xd1\xb9\x95\xc2\xd0\x54\ -\x55\x47\x56\x29\xac\xa1\xbc\xd0\x54\x11\x51\xba\x8a\x80\x15\xc8\ -\xb6\xea\x9e\xf4\x35\xc2\xf0\xf3\x9a\x3c\x3d\xde\xbd\x39\xbb\x7a\ -\x81\xb7\x35\x6c\x31\x29\xae\xff\xab\x86\x9d\x8a\x95\xac\x58\x18\ -\x6c\x5e\x49\x1f\xb0\xda\x8a\xf0\x50\x58\x91\x03\x34\x6d\xa5\x91\ -\x95\x09\x43\xcb\xab\x7a\xde\xd1\xb4\xe5\xd3\x6a\x61\x2c\x73\x0d\ -\xa2\xad\x28\xb2\x02\x61\x20\x79\xb1\xcf\x2f\x88\xb6\x4c\x5a\x2a\ -\x4c\x74\x4e\x6d\xb5\xe5\x47\x96\x2b\xcc\x36\x2f\xb5\x79\xc4\xaf\ -\x0d\xbd\x30\x93\xb9\x33\xd1\x96\x19\x59\x96\x30\x93\xbc\xcc\x9f\ -\xe6\xa3\xb6\xef\x7f\x6c\x8f\x62\x0e\xdc\x5e\xe2\x78\xf2\x9e\xb9\ -\x2d\x13\x72\xc2\x48\x17\xa6\x99\xd7\xe1\xfe\xf8\x1a\xde\x02\xcc\ -\x5a\xf2\x7c\x7a\x08\xe1\x18\xac\x97\xf0\x18\x94\x35\x6c\xf1\xe5\ -\xdb\x50\xdb\xec\x41\xc7\xc3\x53\xd0\x96\x5c\xc9\x12\xc2\x14\x0e\ -\x31\xb9\xd2\x2a\x6b\xdb\x78\x20\x35\x6d\x1b\x58\x16\x56\xb4\x87\ -\xa6\xa0\x2d\x73\x70\x69\x6d\xdb\x91\x6d\x09\x93\x3b\xa6\xea\x53\ -\x50\x84\xb4\x55\x0c\x68\x55\x9b\x76\x61\xf4\x93\xcb\x03\xab\x36\ -\xe2\x20\x42\xda\x36\x22\x5b\x15\xc6\x7e\x10\x2c\xaa\x62\x88\xda\ -\x18\x33\xd5\xac\x4d\xa3\x30\x76\x55\x31\x15\xda\x84\x16\x42\x5e\ -\x6d\x6b\x91\x2d\x0b\xe3\x7a\x54\x51\x55\x31\x99\xda\x14\xde\x6a\ -\x4a\xd7\x26\x55\x98\x9a\xaa\x98\x0d\x6d\xca\x1f\xe6\x58\xb4\x2d\ -\x46\xb6\x20\x8c\xf8\x30\x26\xaa\x62\x66\xa7\x49\x19\x6e\x97\x48\ -\xd4\xc6\xb9\x97\x78\xb8\x3f\x9a\xdb\x9a\x61\xbe\xb9\x15\x68\xd3\ -\x72\x2d\x7b\x5e\x58\xdd\xd3\x01\xcd\xd3\x04\xc8\x9e\x64\xe0\xab\ -\x6d\x37\x0c\x43\xfc\xfb\xd2\x11\x85\xde\xac\x4b\x80\xa0\x6d\x84\ -\x32\xc9\x17\x85\x15\x0d\x04\x5b\xd5\x1a\x3e\x6a\xbb\x28\x2c\x73\ -\x08\x85\xcf\x55\xd2\x20\x68\x1b\x29\x9d\xf3\x73\x61\x39\xf7\x6c\ -\xae\xaa\x35\xda\xad\xed\x5c\xd8\xf6\x7d\x94\x3f\x02\x6b\x82\xa0\ -\x6d\x24\x47\xc1\x3e\x79\x53\x37\x55\xad\xd1\x56\x6d\x1f\x85\x2d\ -\xde\xc8\x70\xb7\xc2\x0a\x04\x6d\x23\x6b\x46\xf6\x8b\x3f\x73\x5f\ -\xd5\x1a\xf8\xb5\xed\x86\x61\x88\xff\xd4\x5c\x15\xce\xf7\xef\x10\ -\xb4\x8d\xc4\x82\x76\xbf\xfe\x7e\x19\x7f\x65\xae\x6a\x04\x47\xd8\ -\x08\x9a\xb6\x7f\x3b\xcf\xca\x48\x61\xee\x5b\x97\x00\x00\x00\x00\ -\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x03\xef\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x01\x68\xf4\xcf\xf7\ -\x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\ -\xa7\x93\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\ -\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x03\x8f\x49\x44\x41\x54\x38\ -\xcb\x3d\x91\xdf\x4f\x5b\x75\x00\xc5\xcf\xf7\x7e\x2f\xb4\xbd\xb7\ -\xdc\x76\x8c\xfe\x80\x5b\xe4\x87\x73\xcc\x84\x8c\x9f\x0d\x83\x25\ -\x2e\x6e\x3e\x8c\x44\xb3\x64\x66\x21\x6a\x62\x8c\xba\x68\xa6\x0f\ -\xea\x93\x0f\x26\x8b\x0f\xc6\x7f\xc0\x44\x97\xc5\x2c\x53\x5f\xdc\ -\xe2\xa3\xc8\xa0\xb0\x1f\x22\x14\xc1\x6d\x08\x6d\x2d\x90\x51\xda\ -\x4b\x27\x3f\xd6\x52\xee\xbd\x50\x7a\xbf\xbd\x5f\x1f\x10\x5e\x4f\ -\xce\x39\xf9\xe4\x1c\xca\x18\x43\x4f\x4f\xdf\x23\xc4\xe2\x09\x30\ -\xc6\x38\xb1\x2c\x0b\x00\x20\xe6\xb6\xf2\x7c\xf9\xc9\x0a\x08\x63\ -\x0c\xdb\xdb\x7a\x6f\xb1\x54\x9c\x30\x0c\x03\x88\x8c\xde\x43\xb8\ -\xbb\x8f\xf7\xf6\xbe\xc4\x1f\x8c\xff\x0e\x58\x96\x85\x72\xb9\x8c\ -\x8c\xa6\xfd\x64\x59\x16\xc0\x18\xc3\xf4\xcc\x43\x5e\x2e\x97\xf9\ -\x48\x64\xcc\xa4\xe1\x70\x8f\xd6\x15\xee\x54\xa2\xd1\x28\x8e\x54\ -\x7b\x2b\x71\xee\xec\x79\xb3\xed\x64\x37\x9f\x8b\xcd\x9b\x1b\xcf\ -\x36\xf7\x23\x96\x65\x29\x8c\x31\xcc\xfc\xf5\x68\x28\x91\x4c\xf2\ -\x7c\x61\x8b\xdb\xb6\xcd\x4d\xd3\xac\x21\xa9\x95\x34\x04\x41\x40\ -\x3c\x9e\xe4\x27\x4e\x1c\x43\xd0\x1f\x00\x07\xc7\xfd\x07\xe3\xe8\ -\xec\x6c\xbf\x86\xef\xbe\xbd\xfe\x59\x7b\x5b\x98\x07\x82\xf5\xfc\ -\xe2\xeb\x03\x5c\xd7\x75\x3e\x7a\xf7\x1e\x1f\x1e\x89\xb4\x65\xff\ -\x7d\x0a\x32\x3c\x3c\x06\x70\xe0\xe7\x5b\xb7\x22\xa9\xe5\xd4\x39\ -\xc3\x30\x71\xe3\x87\x6b\xde\x50\x5d\xa8\x20\xcb\xf2\x3e\xc3\xff\ -\x1c\xd0\xb4\xec\xab\xd3\x33\x0f\xe7\x0d\xd3\xec\x38\xd0\x0e\x0c\ -\x4e\x9b\x73\x8c\xdd\xbd\xcf\x57\x32\x69\x5e\x2e\x97\xb9\xb6\xaa\ -\x4d\x70\xce\x21\x02\x00\xa5\xb4\xf8\xeb\xe0\x90\xd5\xd2\xf2\x02\ -\x8e\x56\x1f\x85\x6d\x73\xc4\x13\xc9\xde\xbd\xa2\x75\x41\xd4\x56\ -\xb3\x58\x5b\x5f\xbf\xd1\xdc\xd4\x24\x2a\x1e\x05\x2e\xa7\x13\x8f\ -\x67\x67\xe1\x74\x38\xd0\xd4\xd4\x10\x11\x76\x4c\xb3\xc6\xd0\xf5\ -\x77\x1a\x9b\x1b\xa0\x54\x29\xd0\x75\x1d\x6b\x6b\x1b\xa8\xac\xac\ -\x7c\xf3\xf1\xdf\xb3\x26\xe9\xeb\x3d\xc3\xd7\x37\x36\x90\xcb\xe7\ -\x30\x15\x1d\xc7\xc2\xe2\x22\x82\xc1\x20\x37\x4c\x5d\x68\x6d\x6d\ -\x85\xf0\xd1\xc7\x57\xfa\x8e\x78\xbd\x68\xa8\x7f\x0e\xfd\xe7\x5f\ -\x83\xa2\x28\x30\x77\x8c\xe7\x5d\x2e\x17\x00\x40\xf0\xf9\x7c\x93\ -\xef\xbd\xff\xee\x8b\xb5\x75\xb5\x7b\xaa\xaa\x22\x12\x19\x4b\x29\ -\x8a\xb2\x1c\xee\xea\x86\x57\xf1\xec\x3f\x0e\x00\x9c\x73\x00\xa0\ -\x84\x10\x4e\x08\xb1\x4b\xa5\x52\xf3\x64\xf4\xcf\x39\x87\xc3\x21\ -\xf9\x03\x35\xf0\x7a\xbd\x90\x5d\x92\xb5\xbb\xbb\x7b\xdd\xed\x96\ -\x3f\xa1\x54\xb4\x38\xb7\x0f\x0b\x08\x00\x0a\x80\x51\x4a\xb1\xb0\ -\xb0\xf8\xf9\x93\xe5\xd4\xd7\x75\x75\xb5\x50\x3c\x55\x90\xdd\x32\ -\x14\xb7\x82\x4a\xb1\x02\x7b\xa5\x22\xe6\x62\x71\x80\xe3\xc7\x8e\ -\xf6\x93\x57\x0e\x09\x00\x80\x31\xe6\x9f\x98\x8c\xfe\x51\x51\xe1\ -\x38\xe6\xf3\xd7\x40\x92\x9c\xa8\xaa\xaa\x82\xec\x92\x40\x29\x45\ -\x2e\x9f\x43\x2c\xfe\x0f\xac\x92\x05\x8f\xc7\xb3\x13\x0c\x04\xde\ -\x12\xe7\x63\x09\x08\x44\x00\x21\xe4\xed\x8c\x96\xb9\x19\x0a\x85\ -\x20\x49\x4e\xb8\x24\x17\xdc\x6e\x37\x24\xa7\x0b\x84\x10\xa4\xd3\ -\x69\x24\x17\x96\x40\x29\x85\x3f\xe0\x5b\x57\xd5\xba\x16\x4a\xe9\ -\x96\x98\xcf\xe5\x4e\xdd\xbe\xfd\xcb\x37\x53\x53\xd3\x5d\x82\x40\ -\x50\xab\xd6\xe2\x8d\x81\x01\x5c\xba\x74\x11\x94\x52\x94\x4a\x25\ -\xc4\x12\x09\x68\x99\x55\x54\x57\x57\x83\x08\x18\x5a\x5d\xd5\xfa\ -\x4d\xd3\x40\x30\x18\x84\x90\xcf\x17\xb6\x0c\xdd\x14\x08\x00\x66\ -\x31\x2c\x25\x97\x70\xf5\xea\x97\x50\xd5\x46\x74\x74\xf6\xe0\xb7\ -\x3b\x77\xa0\x6f\x1b\x08\xd5\xab\x00\xe1\xdf\xeb\xba\xde\xaf\xaa\ -\x21\xb4\x1c\x3f\x0e\x49\x92\x40\x46\x22\x63\x10\x45\x11\xb1\xb9\ -\xc4\x85\xc1\xc1\xc1\x9b\xf9\x7c\xde\x23\x08\xc2\xc1\x26\x28\x14\ -\x0a\xf8\xe0\xc3\xcb\x38\xfb\xca\x99\x2f\x4c\x73\xe7\x2b\x59\x96\ -\xd1\xd4\xd0\x08\x49\x92\x20\x08\x02\xc8\xc8\xf0\x28\x00\x80\x08\ -\x84\x1a\xe6\x4e\x59\xd7\x0d\x64\xb3\xd9\xe6\xcd\xcd\x67\x97\x19\ -\xb3\x5e\xf6\xfb\xfd\x4f\x4f\x9f\x3e\xf5\xa9\xc7\xab\xa4\x82\x81\ -\x00\xfc\x3e\x3f\x6c\xdb\x3e\x1c\xfe\x3f\x11\x5f\xc4\xbb\xcd\x16\ -\x27\xa0\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x07\x22\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x90\x00\x00\x00\x90\x08\x06\x00\x00\x00\xe7\x46\xe2\xb8\ -\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ -\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0e\xc4\x00\x00\x0e\xc4\ -\x01\x95\x2b\x0e\x1b\x00\x00\x06\xc4\x49\x44\x41\x54\x78\x9c\xed\ -\x9d\xbb\x71\x1b\x31\x14\x45\xb1\x1e\x05\x6a\x80\x33\x4a\xdc\x04\ -\x4b\x70\xe8\x12\x14\xb3\x2c\xc5\x2a\xc1\xa1\x4b\x50\x13\x4e\x38\ -\xa3\x06\x94\xd1\x81\x06\x36\x04\xee\x2e\x7e\xef\x73\x1f\xf6\x9d\ -\x50\x23\xed\x82\x78\x87\x97\x58\x40\x00\x97\x97\xf7\xa7\x5b\x08\ -\x21\x5c\x4e\xd7\x25\x18\xe4\xf9\xf1\xed\xa6\xdd\x86\x51\x5e\x3f\ -\xce\x26\xfb\xfe\xe5\xfd\xe9\xb6\x44\x81\x22\x2e\x92\x3c\xd6\x04\ -\x4a\x9d\x59\xf2\x1f\x44\x5c\x24\x39\xac\x08\xb4\xe6\xc9\xa6\x40\ -\x11\x17\x89\x1f\x74\x81\xb6\xfc\xb8\x9c\xae\xcb\x52\xfa\xa5\xf4\ -\x97\xa9\x1b\x26\x81\x05\x91\x50\x05\xaa\x71\xa2\x5a\xa0\xf4\x8f\ -\x46\x1b\xa6\x01\xb2\x48\x68\x02\xd5\xb8\x10\x3d\xf8\xd2\xf0\x5a\ -\x89\xd2\x0b\x58\x03\x51\x24\x14\x81\x7a\xea\xdf\x2d\x50\x7e\x21\ -\x6b\x20\x89\xa4\x2d\x50\x6b\xdd\xd3\x9a\xdf\x35\xbc\x47\xa2\xfc\ -\xa2\x96\x40\x10\x49\x4b\x20\x8a\x5a\x93\x09\xb4\x76\x71\x4b\x68\ -\x8a\x24\x2d\xd0\x48\x8d\xf3\xfa\xae\x36\x7c\x54\xa2\xb5\x1b\x59\ -\x41\x43\x24\x29\x81\x38\xea\xca\x26\xd0\xd6\x0d\xad\x20\x29\x12\ -\xb7\x40\x54\xf5\x5c\xab\xe5\xb7\xda\x5f\xec\xe5\xe5\xfd\xe9\x46\ -\x29\xa4\x14\xda\x03\x5b\x0a\x24\xfa\xfe\x81\xf3\xe2\x29\x96\x16\ -\x6d\xf3\xf4\x79\xfd\x38\x2f\x08\x83\xed\x5a\x38\xa4\xd9\xaa\xdb\ -\xa6\x40\x97\xd3\xf5\x6e\xa1\x95\x02\x64\x91\xf6\x24\x89\x89\x84\ -\x2c\x92\x46\xd2\x8b\x25\x50\x0e\x92\x48\x2d\x52\x20\x8a\xc4\x2d\ -\xce\x5e\x8d\x76\x05\xe2\x4a\xa1\x14\x4d\x91\x46\x24\x40\x10\x09\ -\x61\x6c\xa9\x96\x40\x39\x92\x22\x51\x16\x5d\x43\x24\x49\x71\x4a\ -\xf5\x28\x0a\x24\x91\x42\x29\x9c\x22\x71\x16\x59\x42\x24\x84\xc4\ -\xc9\x81\x49\xa0\x1c\x4a\x91\x34\xe6\x74\x28\xef\xa9\x25\x4e\x4d\ -\xdf\x57\x09\x24\x9d\x42\x29\x23\x22\x21\x2c\x4f\x8c\xb4\x01\x31\ -\x71\x72\x60\x13\x28\xa7\x45\x24\xa4\x27\xa4\x1e\x91\x10\xc4\xa9\ -\x7d\xc3\x56\x0b\xa4\x99\x42\x29\x7b\x22\x21\x89\x93\x53\x23\x12\ -\x42\xff\xb6\x62\x26\x81\x72\x52\x91\x90\xc5\xc9\x59\x13\x09\x4d\ -\x9c\x96\xe1\xc2\xea\x5a\x18\xc5\x85\xa5\x40\xeb\xfc\x5a\xa2\x48\ -\x3f\xfe\xfc\xd4\x6e\xca\x10\x4d\x02\xa1\x62\x6d\xe1\xf3\xf9\xf1\ -\xed\x86\x9a\x9a\xad\x21\xd1\xfc\x11\x86\x32\x16\xca\x41\x98\x19\ -\x2e\x81\xdc\xb6\x5e\xcc\x8e\x81\xb6\x40\x14\x09\xa9\x2d\x7b\xf4\ -\x0c\x51\xba\x04\x42\x4d\xa1\x14\x04\x91\xac\x88\x33\xc2\x74\x09\ -\x94\xa3\x21\x92\x45\x71\x7a\x1f\x90\xba\x05\xb2\x90\x42\x29\x12\ -\x22\x59\x14\x67\x94\xe9\x13\x28\x87\x43\x24\xeb\xe2\x8c\x4c\xcf\ -\x0c\x09\x64\x2d\x85\x52\x28\x44\xb2\x2e\x0e\x05\x87\x4b\xa0\x9c\ -\x1e\x91\x66\x12\x67\x74\x72\x78\x58\x20\xcb\x29\x94\x52\x23\xd2\ -\x4c\xe2\x50\x71\xf8\x04\xca\x59\x13\x69\x56\x71\x28\x96\xa6\x48\ -\x96\x32\x10\xd7\xc8\x46\xb1\xb6\x3c\xa2\x85\x27\xd0\x0a\xb3\x26\ -\x4e\x0a\xd5\x9b\x9e\x4c\xa0\x19\xc6\x42\x47\x10\x87\x1a\x4f\xa0\ -\x70\x3c\x71\x28\x87\x1c\xa4\x02\x59\x4b\xa1\xa3\x89\xc3\xc1\x21\ -\x13\xe8\xc8\xe2\x50\x3f\xf0\x90\x0b\x84\x9c\x42\x47\x16\x87\x8b\ -\x43\x24\x90\x8b\xf3\x09\xc7\x74\x0b\x8b\x40\x28\x29\xe4\xe2\xf0\ -\x33\x65\x02\xb9\x38\xf7\x70\x4d\xf6\xb2\x09\xa4\x91\x42\x2e\x8e\ -\x3c\x53\x24\x90\x8b\xb3\x0f\xe7\x52\xd3\x03\x67\xe7\xff\xfe\x6e\ -\x7f\xdf\x93\x75\x7e\x7f\xff\x15\x42\x38\xb3\x5d\xdf\xd4\xae\xce\ -\x59\xe1\x5a\xb8\x8d\xb5\xe5\xbc\x3e\xfb\xc6\x42\x5f\xd5\x2e\xc3\ -\xfd\x26\xe6\xbc\x3e\xab\x40\x2e\x8f\x1e\x52\x52\x8a\x6c\x6d\x76\ -\x91\xca\x58\x4d\x21\x36\x81\x5c\x1a\x3d\x24\x65\x14\x3b\x5c\xc1\ -\x85\x2a\x63\x31\x85\x58\x04\x72\x59\xf4\x90\x96\x50\xf4\x78\x17\ -\x17\xab\x8c\xb5\x14\x22\x17\xc8\x25\xd1\x43\x43\x3e\xf1\x03\xa6\ -\x5c\xb0\x32\x96\x52\x88\x54\x20\x97\x43\x0f\x2d\xe9\x54\x8e\xb8\ -\x73\xd1\xca\x58\x49\x21\x32\x81\x5c\x0a\x3d\x34\x65\x53\x3b\x64\ -\xd3\x85\x2b\x63\x21\x85\x48\x04\x72\x19\xf4\xd0\x96\x4c\xf5\x98\ -\x5f\x17\xaf\x8c\xb6\x20\x25\x86\x05\x72\x09\xf4\x40\x90\x4b\xfd\ -\xa0\x71\x17\xb0\x0c\x82\x28\x5b\x0c\x09\xe4\xc5\xd7\x03\x45\x2a\ -\xf5\x04\x0a\xc1\x45\xac\x01\x45\x98\x9c\x6e\x81\xbc\xe8\x7a\x20\ -\xc9\x04\x91\x40\x21\xb8\x90\x35\x20\x89\x13\xe9\xda\x95\xc1\xbd\ -\x8b\xc0\xb1\x83\x6f\xeb\x71\x86\x98\x62\x67\xea\xd1\x40\xfa\x04\ -\x68\x1e\x03\x71\x8f\x55\x7c\x2c\x54\x06\xe9\x53\xa3\x49\x20\x97\ -\x67\x5e\x44\x1f\xe3\x5d\x24\x7d\x50\x52\xa8\x5a\x20\x97\x66\x5e\ -\x54\x96\x32\x5c\x28\x7d\x10\x52\xa8\x4a\x20\x97\x65\x5e\x54\xff\ -\x9d\xc3\xc5\xd2\x47\x3b\x85\x8a\x02\xb9\x24\xf3\x02\xf1\x2f\xad\ -\x2e\x98\x3e\x9a\x29\xb4\x2b\x90\xcb\x31\x2f\x50\xdb\x7a\x5c\x34\ -\x7d\xb4\x52\x68\x53\x20\x97\x62\x5e\x20\xb7\x36\xbb\x70\xfa\x68\ -\xa4\xd0\xaa\x40\x2e\xc3\xbc\x40\x1f\xef\xe2\xe2\xe9\x23\x9d\x42\ -\x77\x02\xb9\x04\xf3\x62\xe2\x88\x3b\x17\x50\x1f\xc9\x14\xfa\x22\ -\x90\x17\x7f\x5e\x4c\x1d\xf3\xeb\x22\xea\x23\x95\x42\xff\x04\xf2\ -\xa2\xcf\x8b\xc9\xaf\x3a\x70\x21\xf5\x91\x48\xa1\x6f\x21\x78\xb1\ -\x67\x86\x5b\xa2\xe5\xf9\xf1\xed\xc6\x59\x60\x89\x6f\x2d\xfc\xfc\ -\x4e\x2c\x9b\x48\x7c\x9f\x1a\x67\xff\x3c\x58\x97\xc7\x32\xaf\x1f\ -\xe7\xe5\x47\xe0\xef\x23\xce\x1a\xfb\xc6\x42\x05\xa4\x3f\xd2\x5f\ -\xde\x9f\x6e\xe6\xbe\x74\xd7\xd3\xe7\x9e\x19\xc7\x82\x9e\x40\x02\ -\x20\x88\xc3\x95\x42\x2c\x02\x79\xfa\x7c\x82\x20\x0e\x37\x9e\x40\ -\x0c\xa0\x8a\xc3\x91\x42\xe4\x02\x1d\x39\x7d\x50\xc5\xe1\xc4\x13\ -\x88\x00\x4b\xe2\x50\xa7\x10\xa9\x40\x47\x4b\x1f\x4b\xe2\x70\xe1\ -\x09\xd4\x81\x75\x71\x28\x53\x88\x4c\xa0\x23\xa4\x8f\x75\x71\x38\ -\x80\x39\xa5\x15\x9d\xd9\xe4\xa1\x7a\xc3\x93\x08\x34\x6b\xfa\xa4\ -\xd2\x3c\x3f\xbe\xdd\xb4\x0f\x32\x40\xc4\xc7\x40\x2b\xec\xa5\x4d\ -\x94\x68\x86\x44\xa2\x18\x0b\x0d\x0b\x34\x53\xfa\xb4\x48\x31\x93\ -\x48\x23\x78\x02\x85\x31\x09\xac\x8b\x34\x9a\x42\x43\x02\x59\x4f\ -\x1f\xca\xa2\x5b\x17\xa9\x97\x43\x26\x10\x67\x91\x2d\x8a\x34\x92\ -\x42\xdd\x02\x59\x4c\x1f\xc9\xa2\x5a\x14\xa9\x87\x43\x24\x90\x66\ -\x11\xad\x88\xd4\x9b\x42\x5d\x02\x59\x49\x1f\xa4\xa2\x59\x11\xa9\ -\x95\x29\x13\x08\xb9\x48\xc8\x22\xf5\xa4\x50\xb3\x40\xc8\xe9\x83\ -\x58\x94\x2d\xfe\xcf\x6a\xf3\x6f\xeb\xe1\xc4\xfc\x5a\x58\xdc\xf3\ -\x64\x49\x1e\x64\x5a\x03\xa2\x49\x20\xb4\xf4\x49\xe3\xd6\xe2\x5a\ -\xd5\xeb\xc7\x79\xb9\x9c\xae\x0b\xd7\x96\x1b\x09\x4c\x8e\x81\xf6\ -\x3a\x1c\x79\x8c\x11\x59\x6b\x5b\x7c\x4d\x08\x6f\xd2\x96\xb1\x50\ -\xb5\x40\x08\x2f\xac\xe5\x9d\x8a\x28\x52\x4d\x5b\x90\x44\xaa\xc1\ -\x44\x02\x8d\x44\x3c\x82\x48\x3d\xf7\xd6\x16\xa9\x36\x85\xaa\x04\ -\xd2\x7a\x11\x94\x63\x03\x0d\x91\x28\xee\xa5\x2d\x52\x09\xc8\x04\ -\xe2\x1c\x54\x4a\x88\xc4\x71\x6d\x0d\x91\x6a\x52\xa8\x28\x90\x64\ -\x83\x25\x9f\x46\x38\x44\x92\x48\x37\xb4\x44\x82\x48\x20\xcd\xc7\ -\x58\x0a\x91\x34\xc6\x57\x52\x22\x95\x52\x68\x57\x20\xee\xc6\x21\ -\xcd\x7f\xf4\x88\x84\xf0\x84\xa7\x9d\x48\x2a\x09\x84\x24\x4e\x4e\ -\x8d\x48\x08\xe2\xe4\x70\x8a\xb4\x97\x42\x9b\x1d\xc1\xd1\x10\x0e\ -\x71\x24\x67\x9f\x11\xc5\xd9\x82\xba\x7e\x5b\xb5\x13\x59\x0b\xb3\ -\x3e\x5d\x1f\x82\x2d\x79\x42\xa0\xef\xf3\x2d\x21\x57\x6f\x40\x65\ -\xaf\x84\x34\xd2\xeb\x5f\xd6\x44\x8a\x50\xd4\x74\xad\x9e\x2c\x02\ -\x69\x3c\x8e\x4b\x73\x54\x91\xf2\xda\xde\x75\xc2\xc8\x0d\x34\x3e\ -\xa6\xb4\x57\xe0\x8f\x26\x12\x8b\x40\x08\xf3\x38\xda\x1c\x49\xa4\ -\xb4\xde\x5f\x5e\x74\xeb\xc5\x10\x06\xc6\x28\x02\x45\x8e\x20\xd2\ -\xb0\x40\x08\xe2\x44\xd0\x04\x8a\xcc\x2e\x52\x74\xe0\xdf\x8b\xac\ -\xf9\x43\x24\x71\x22\xa8\x02\x45\x66\x15\xa9\x49\x20\x44\x71\x22\ -\xe8\x02\x45\x66\x14\xe9\x72\xba\x2e\xbb\xd3\xdf\xc8\xe2\x44\xac\ -\x08\x14\x99\x49\xa4\x4d\x81\x2c\x88\x13\xb1\x26\x50\x64\x16\x91\ -\x96\xf4\x07\x96\xc4\x89\x58\x15\x28\x62\x5d\xa4\xbf\xa8\xcc\xde\ -\x47\x76\xb8\xb3\xea\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\ -\x82\ -" - -qt_resource_name = "\ -\x00\x09\ -\x0c\x78\x54\x88\ -\x00\x6e\ -\x00\x65\x00\x77\x00\x50\x00\x72\x00\x65\x00\x66\x00\x69\x00\x78\ -\x00\x06\ -\x07\x03\x7d\xc3\ -\x00\x69\ -\x00\x6d\x00\x61\x00\x67\x00\x65\x00\x73\ -\x00\x0e\ -\x0a\x51\x2d\xe7\ -\x00\x69\ -\x00\x64\x00\x65\x00\x6e\x00\x74\x00\x69\x00\x74\x00\x69\x00\x65\x00\x73\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x09\ -\x09\x6b\xb7\xc7\ -\x00\x69\ -\x00\x6e\x00\x62\x00\x6f\x00\x78\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0b\ -\x0a\xd0\x22\xa7\ -\x00\x72\ -\x00\x65\x00\x64\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x08\ -\x0c\x57\x58\x67\ -\x00\x73\ -\x00\x65\x00\x6e\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x10\ -\x0c\xc3\x45\x27\ -\x00\x71\ -\x00\x69\x00\x64\x00\x65\x00\x6e\x00\x74\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x5f\x00\x78\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0d\ -\x02\xe8\x12\x87\ -\x00\x62\ -\x00\x6c\x00\x61\x00\x63\x00\x6b\x00\x6c\x00\x69\x00\x73\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x08\ -\x0c\x47\x58\x67\ -\x00\x73\ -\x00\x65\x00\x6e\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0f\ -\x05\x46\x9a\xc7\ -\x00\x61\ -\x00\x64\x00\x64\x00\x72\x00\x65\x00\x73\x00\x73\x00\x62\x00\x6f\x00\x6f\x00\x6b\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x11\ -\x07\x34\x2d\xc7\ -\x00\x6e\ -\x00\x65\x00\x74\x00\x77\x00\x6f\x00\x72\x00\x6b\x00\x73\x00\x74\x00\x61\x00\x74\x00\x75\x00\x73\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\ -\x00\x18\ -\x02\x47\xd6\x47\ -\x00\x63\ -\x00\x61\x00\x6e\x00\x2d\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x2d\x00\x32\x00\x34\x00\x70\x00\x78\x00\x2d\x00\x79\x00\x65\x00\x6c\ -\x00\x6c\x00\x6f\x00\x77\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x11\ -\x02\xa0\x44\xa7\ -\x00\x73\ -\x00\x75\x00\x62\x00\x73\x00\x63\x00\x72\x00\x69\x00\x70\x00\x74\x00\x69\x00\x6f\x00\x6e\x00\x73\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\ -\x00\x0e\ -\x09\x39\xff\x47\ -\x00\x71\ -\x00\x69\x00\x64\x00\x65\x00\x6e\x00\x74\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x11\ -\x05\x89\x73\x07\ -\x00\x63\ -\x00\x61\x00\x6e\x00\x2d\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x2d\x00\x32\x00\x34\x00\x70\x00\x78\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\ -\x00\x15\ -\x0c\xfc\x45\x87\ -\x00\x63\ -\x00\x61\x00\x6e\x00\x2d\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x2d\x00\x32\x00\x34\x00\x70\x00\x78\x00\x2d\x00\x72\x00\x65\x00\x64\ -\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0d\ -\x07\x76\xdf\x07\ -\x00\x67\ -\x00\x72\x00\x65\x00\x65\x00\x6e\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x17\ -\x00\xd3\x62\xc7\ -\x00\x63\ -\x00\x61\x00\x6e\x00\x2d\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x2d\x00\x32\x00\x34\x00\x70\x00\x78\x00\x2d\x00\x67\x00\x72\x00\x65\ -\x00\x65\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x11\ -\x02\x8c\x5e\x67\ -\x00\x6e\ -\x00\x6f\x00\x5f\x00\x69\x00\x64\x00\x65\x00\x6e\x00\x74\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x73\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\ -\x00\x0e\ -\x02\x47\x93\x47\ -\x00\x79\ -\x00\x65\x00\x6c\x00\x6c\x00\x6f\x00\x77\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x12\ -\x03\xf4\x2e\xc7\ -\x00\x71\ -\x00\x69\x00\x64\x00\x65\x00\x6e\x00\x74\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x5f\x00\x74\x00\x77\x00\x6f\x00\x2e\x00\x70\x00\x6e\ -\x00\x67\ -\x00\x11\ -\x03\x89\x73\x27\ -\x00\x63\ -\x00\x61\x00\x6e\x00\x2d\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x2d\x00\x31\x00\x36\x00\x70\x00\x78\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\ -\x00\x14\ -\x07\x12\xd0\xa7\ -\x00\x71\ -\x00\x69\x00\x64\x00\x65\x00\x6e\x00\x74\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x5f\x00\x74\x00\x77\x00\x6f\x00\x5f\x00\x78\x00\x2e\ -\x00\x70\x00\x6e\x00\x67\ -" - -qt_resource_struct = "\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ -\x00\x00\x00\x18\x00\x02\x00\x00\x00\x15\x00\x00\x00\x03\ -\x00\x00\x02\x36\x00\x00\x00\x00\x00\x01\x00\x00\x3f\x80\ -\x00\x00\x02\x92\x00\x00\x00\x00\x00\x01\x00\x00\x47\xb7\ -\x00\x00\x01\x3e\x00\x00\x00\x00\x00\x01\x00\x00\x1d\x49\ -\x00\x00\x02\x6a\x00\x00\x00\x00\x00\x01\x00\x00\x46\xee\ -\x00\x00\x01\x74\x00\x00\x00\x00\x00\x01\x00\x00\x24\xaf\ -\x00\x00\x00\xbc\x00\x00\x00\x00\x00\x01\x00\x00\x11\xfc\ -\x00\x00\x02\xde\x00\x00\x00\x00\x00\x01\x00\x00\x51\x02\ -\x00\x00\x02\xb4\x00\x00\x00\x00\x00\x01\x00\x00\x4a\x35\ -\x00\x00\x00\xf2\x00\x00\x00\x00\x00\x01\x00\x00\x18\x2b\ -\x00\x00\x01\xbe\x00\x00\x00\x00\x00\x01\x00\x00\x2d\x9f\ -\x00\x00\x03\x06\x00\x00\x00\x00\x00\x01\x00\x00\x54\xf5\ -\x00\x00\x01\x16\x00\x00\x00\x00\x00\x01\x00\x00\x1a\xd0\ -\x00\x00\x02\x16\x00\x00\x00\x00\x00\x01\x00\x00\x3c\x48\ -\x00\x00\x01\x9c\x00\x00\x00\x00\x00\x01\x00\x00\x27\x2a\ -\x00\x00\x00\x4c\x00\x00\x00\x00\x00\x01\x00\x00\x03\x6a\ -\x00\x00\x00\x2a\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ -\x00\x00\x00\x64\x00\x00\x00\x00\x00\x01\x00\x00\x06\x1d\ -\x00\x00\x00\xdc\x00\x00\x00\x00\x00\x01\x00\x00\x14\xf0\ -\x00\x00\x00\x80\x00\x00\x00\x00\x00\x01\x00\x00\x08\xed\ -\x00\x00\x00\x96\x00\x00\x00\x00\x00\x01\x00\x00\x0b\x15\ -\x00\x00\x01\xe6\x00\x00\x00\x00\x00\x01\x00\x00\x34\xdf\ -" - -def qInitResources(): - QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) - -def qCleanupResources(): - QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) - -qInitResources() diff --git a/src/bitmessageqt/bitmessageui.py b/src/bitmessageqt/bitmessageui.py deleted file mode 100644 index 961fc093..00000000 --- a/src/bitmessageqt/bitmessageui.py +++ /dev/null @@ -1,817 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file 'bitmessageui.ui' -# -# Created: Mon Mar 23 22:18:07 2015 -# by: PyQt4 UI code generator 4.10.4 -# -# 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 - -try: - _fromUtf8 = QtCore.QString.fromUtf8 -except AttributeError: - def _fromUtf8(s): - return s - -try: - _encoding = QtGui.QApplication.UnicodeUTF8 - - 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): - 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 - ) - MainWindow.setWindowIcon(icon) - MainWindow.setTabShape(QtGui.QTabWidget.Rounded) - self.centralwidget = QtGui.QWidget(MainWindow) - self.centralwidget.setObjectName(_fromUtf8("centralwidget")) - self.gridLayout_10 = QtGui.QGridLayout(self.centralwidget) - self.gridLayout_10.setObjectName(_fromUtf8("gridLayout_10")) - self.tabWidget = QtGui.QTabWidget(self.centralwidget) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.tabWidget.sizePolicy().hasHeightForWidth()) - self.tabWidget.setSizePolicy(sizePolicy) - self.tabWidget.setMinimumSize(QtCore.QSize(0, 0)) - self.tabWidget.setBaseSize(QtCore.QSize(0, 0)) - font = QtGui.QFont() - font.setPointSize(9) - self.tabWidget.setFont(font) - self.tabWidget.setTabPosition(QtGui.QTabWidget.North) - self.tabWidget.setTabShape(QtGui.QTabWidget.Rounded) - self.tabWidget.setObjectName(_fromUtf8("tabWidget")) - self.inbox = QtGui.QWidget() - self.inbox.setObjectName(_fromUtf8("inbox")) - self.gridLayout = QtGui.QGridLayout(self.inbox) - self.gridLayout.setObjectName(_fromUtf8("gridLayout")) - self.horizontalSplitter_3 = settingsmixin.SSplitter() - self.horizontalSplitter_3.setObjectName(_fromUtf8("horizontalSplitter_3")) - self.verticalSplitter_12 = settingsmixin.SSplitter() - self.verticalSplitter_12.setObjectName(_fromUtf8("verticalSplitter_12")) - self.verticalSplitter_12.setOrientation(QtCore.Qt.Vertical) - self.treeWidgetYourIdentities = settingsmixin.STreeWidget(self.inbox) - 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 - ) - self.treeWidgetYourIdentities.headerItem().setIcon(0, icon1) - self.verticalSplitter_12.addWidget(self.treeWidgetYourIdentities) - self.pushButtonNewAddress = QtGui.QPushButton(self.inbox) - self.pushButtonNewAddress.setObjectName(_fromUtf8("pushButtonNewAddress")) - self.pushButtonNewAddress.resize(200, self.pushButtonNewAddress.height()) - self.verticalSplitter_12.addWidget(self.pushButtonNewAddress) - self.verticalSplitter_12.setStretchFactor(0, 1) - self.verticalSplitter_12.setStretchFactor(1, 0) - self.verticalSplitter_12.setCollapsible(0, False) - self.verticalSplitter_12.setCollapsible(1, False) - self.verticalSplitter_12.handle(1).setEnabled(False) - self.horizontalSplitter_3.addWidget(self.verticalSplitter_12) - self.verticalSplitter_7 = settingsmixin.SSplitter() - self.verticalSplitter_7.setObjectName(_fromUtf8("verticalSplitter_7")) - self.verticalSplitter_7.setOrientation(QtCore.Qt.Vertical) - self.horizontalSplitterSearch = QtGui.QSplitter() - self.horizontalSplitterSearch.setObjectName(_fromUtf8("horizontalSplitterSearch")) - self.inboxSearchLineEdit = QtGui.QLineEdit(self.inbox) - self.inboxSearchLineEdit.setObjectName(_fromUtf8("inboxSearchLineEdit")) - self.horizontalSplitterSearch.addWidget(self.inboxSearchLineEdit) - self.inboxSearchOption = QtGui.QComboBox(self.inbox) - self.inboxSearchOption.setObjectName(_fromUtf8("inboxSearchOption")) - self.inboxSearchOption.addItem(_fromUtf8("")) - self.inboxSearchOption.addItem(_fromUtf8("")) - self.inboxSearchOption.addItem(_fromUtf8("")) - 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) - self.horizontalSplitterSearch.setStretchFactor(1, 0) - self.verticalSplitter_7.addWidget(self.horizontalSplitterSearch) - self.tableWidgetInbox = settingsmixin.STableWidget(self.inbox) - self.tableWidgetInbox.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers) - self.tableWidgetInbox.setAlternatingRowColors(True) - self.tableWidgetInbox.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) - self.tableWidgetInbox.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) - self.tableWidgetInbox.setWordWrap(False) - self.tableWidgetInbox.setObjectName(_fromUtf8("tableWidgetInbox")) - self.tableWidgetInbox.setColumnCount(4) - self.tableWidgetInbox.setRowCount(0) - item = QtGui.QTableWidgetItem() - self.tableWidgetInbox.setHorizontalHeaderItem(0, item) - item = QtGui.QTableWidgetItem() - self.tableWidgetInbox.setHorizontalHeaderItem(1, item) - item = QtGui.QTableWidgetItem() - self.tableWidgetInbox.setHorizontalHeaderItem(2, item) - item = QtGui.QTableWidgetItem() - self.tableWidgetInbox.setHorizontalHeaderItem(3, item) - self.tableWidgetInbox.horizontalHeader().setCascadingSectionResizes(True) - self.tableWidgetInbox.horizontalHeader().setDefaultSectionSize(200) - self.tableWidgetInbox.horizontalHeader().setHighlightSections(False) - self.tableWidgetInbox.horizontalHeader().setMinimumSectionSize(27) - self.tableWidgetInbox.horizontalHeader().setSortIndicatorShown(False) - self.tableWidgetInbox.horizontalHeader().setStretchLastSection(True) - self.tableWidgetInbox.verticalHeader().setVisible(False) - self.tableWidgetInbox.verticalHeader().setDefaultSectionSize(26) - self.verticalSplitter_7.addWidget(self.tableWidgetInbox) - self.textEditInboxMessage = MessageView(self.inbox) - self.textEditInboxMessage.setBaseSize(QtCore.QSize(0, 500)) - self.textEditInboxMessage.setReadOnly(True) - self.textEditInboxMessage.setObjectName(_fromUtf8("textEditInboxMessage")) - self.verticalSplitter_7.addWidget(self.textEditInboxMessage) - self.verticalSplitter_7.setStretchFactor(0, 0) - self.verticalSplitter_7.setStretchFactor(1, 1) - self.verticalSplitter_7.setStretchFactor(2, 2) - self.verticalSplitter_7.setCollapsible(0, False) - self.verticalSplitter_7.setCollapsible(1, False) - self.verticalSplitter_7.setCollapsible(2, False) - self.verticalSplitter_7.handle(1).setEnabled(False) - self.horizontalSplitter_3.addWidget(self.verticalSplitter_7) - self.horizontalSplitter_3.setStretchFactor(0, 0) - self.horizontalSplitter_3.setStretchFactor(1, 1) - self.horizontalSplitter_3.setCollapsible(0, False) - self.horizontalSplitter_3.setCollapsible(1, False) - self.gridLayout.addWidget(self.horizontalSplitter_3) - icon2 = QtGui.QIcon() - icon2.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/inbox.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) - self.tabWidget.addTab(self.inbox, icon2, _fromUtf8("")) - self.send = QtGui.QWidget() - self.send.setObjectName(_fromUtf8("send")) - self.gridLayout_7 = QtGui.QGridLayout(self.send) - self.gridLayout_7.setObjectName(_fromUtf8("gridLayout_7")) - self.horizontalSplitter = settingsmixin.SSplitter() - self.horizontalSplitter.setObjectName(_fromUtf8("horizontalSplitter")) - self.verticalSplitter_2 = settingsmixin.SSplitter() - self.verticalSplitter_2.setObjectName(_fromUtf8("verticalSplitter_2")) - self.verticalSplitter_2.setOrientation(QtCore.Qt.Vertical) - self.tableWidgetAddressBook = settingsmixin.STableWidget(self.send) - self.tableWidgetAddressBook.setAlternatingRowColors(True) - self.tableWidgetAddressBook.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) - self.tableWidgetAddressBook.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) - self.tableWidgetAddressBook.setObjectName(_fromUtf8("tableWidgetAddressBook")) - self.tableWidgetAddressBook.setColumnCount(2) - self.tableWidgetAddressBook.setRowCount(0) - 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 - ) - item.setIcon(icon3) - self.tableWidgetAddressBook.setHorizontalHeaderItem(0, item) - item = QtGui.QTableWidgetItem() - self.tableWidgetAddressBook.setHorizontalHeaderItem(1, item) - self.tableWidgetAddressBook.horizontalHeader().setCascadingSectionResizes(True) - self.tableWidgetAddressBook.horizontalHeader().setDefaultSectionSize(200) - self.tableWidgetAddressBook.horizontalHeader().setHighlightSections(False) - self.tableWidgetAddressBook.horizontalHeader().setStretchLastSection(True) - self.tableWidgetAddressBook.verticalHeader().setVisible(False) - self.verticalSplitter_2.addWidget(self.tableWidgetAddressBook) - self.addressBookCompleter = AddressBookCompleter() - self.addressBookCompleter.setCompletionMode(QtGui.QCompleter.PopupCompletion) - self.addressBookCompleter.setCaseSensitivity(QtCore.Qt.CaseInsensitive) - self.addressBookCompleterModel = QtGui.QStringListModel() - self.addressBookCompleter.setModel(self.addressBookCompleterModel) - self.pushButtonAddAddressBook = QtGui.QPushButton(self.send) - self.pushButtonAddAddressBook.setObjectName(_fromUtf8("pushButtonAddAddressBook")) - self.pushButtonAddAddressBook.resize(200, self.pushButtonAddAddressBook.height()) - self.verticalSplitter_2.addWidget(self.pushButtonAddAddressBook) - self.pushButtonFetchNamecoinID = QtGui.QPushButton(self.send) - self.pushButtonFetchNamecoinID.resize(200, self.pushButtonFetchNamecoinID.height()) - self.pushButtonFetchNamecoinID.setObjectName(_fromUtf8("pushButtonFetchNamecoinID")) - self.verticalSplitter_2.addWidget(self.pushButtonFetchNamecoinID) - self.verticalSplitter_2.setStretchFactor(0, 1) - self.verticalSplitter_2.setStretchFactor(1, 0) - self.verticalSplitter_2.setStretchFactor(2, 0) - self.verticalSplitter_2.setCollapsible(0, False) - self.verticalSplitter_2.setCollapsible(1, False) - self.verticalSplitter_2.setCollapsible(2, False) - self.verticalSplitter_2.handle(1).setEnabled(False) - self.verticalSplitter_2.handle(2).setEnabled(False) - self.horizontalSplitter.addWidget(self.verticalSplitter_2) - self.verticalSplitter = settingsmixin.SSplitter() - self.verticalSplitter.setObjectName(_fromUtf8("verticalSplitter")) - self.verticalSplitter.setOrientation(QtCore.Qt.Vertical) - self.tabWidgetSend = QtGui.QTabWidget(self.send) - self.tabWidgetSend.setObjectName(_fromUtf8("tabWidgetSend")) - self.sendDirect = QtGui.QWidget() - self.sendDirect.setObjectName(_fromUtf8("sendDirect")) - self.gridLayout_8 = QtGui.QGridLayout(self.sendDirect) - self.gridLayout_8.setObjectName(_fromUtf8("gridLayout_8")) - self.verticalSplitter_5 = settingsmixin.SSplitter() - self.verticalSplitter_5.setObjectName(_fromUtf8("verticalSplitter_5")) - self.verticalSplitter_5.setOrientation(QtCore.Qt.Vertical) - self.gridLayout_2 = QtGui.QGridLayout() - self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2")) - self.label_3 = QtGui.QLabel(self.sendDirect) - self.label_3.setObjectName(_fromUtf8("label_3")) - self.gridLayout_2.addWidget(self.label_3, 2, 0, 1, 1) - self.label_2 = QtGui.QLabel(self.sendDirect) - self.label_2.setObjectName(_fromUtf8("label_2")) - self.gridLayout_2.addWidget(self.label_2, 0, 0, 1, 1) - self.lineEditSubject = QtGui.QLineEdit(self.sendDirect) - self.lineEditSubject.setText(_fromUtf8("")) - self.lineEditSubject.setObjectName(_fromUtf8("lineEditSubject")) - self.gridLayout_2.addWidget(self.lineEditSubject, 2, 1, 1, 1) - self.label = QtGui.QLabel(self.sendDirect) - self.label.setObjectName(_fromUtf8("label")) - self.gridLayout_2.addWidget(self.label, 1, 0, 1, 1) - self.comboBoxSendFrom = QtGui.QComboBox(self.sendDirect) - self.comboBoxSendFrom.setMinimumSize(QtCore.QSize(300, 0)) - self.comboBoxSendFrom.setObjectName(_fromUtf8("comboBoxSendFrom")) - self.gridLayout_2.addWidget(self.comboBoxSendFrom, 0, 1, 1, 1) - self.lineEditTo = QtGui.QLineEdit(self.sendDirect) - self.lineEditTo.setObjectName(_fromUtf8("lineEditTo")) - self.gridLayout_2.addWidget(self.lineEditTo, 1, 1, 1, 1) - self.lineEditTo.setCompleter(self.addressBookCompleter) - self.gridLayout_2_Widget = QtGui.QWidget() - self.gridLayout_2_Widget.setLayout(self.gridLayout_2) - self.verticalSplitter_5.addWidget(self.gridLayout_2_Widget) - self.textEditMessage = MessageCompose(self.sendDirect) - self.textEditMessage.setObjectName(_fromUtf8("textEditMessage")) - self.verticalSplitter_5.addWidget(self.textEditMessage) - self.verticalSplitter_5.setStretchFactor(0, 0) - self.verticalSplitter_5.setStretchFactor(1, 1) - self.verticalSplitter_5.setCollapsible(0, False) - self.verticalSplitter_5.setCollapsible(1, False) - self.verticalSplitter_5.handle(1).setEnabled(False) - self.gridLayout_8.addWidget(self.verticalSplitter_5, 0, 0, 1, 1) - self.tabWidgetSend.addTab(self.sendDirect, _fromUtf8("")) - self.sendBroadcast = QtGui.QWidget() - self.sendBroadcast.setObjectName(_fromUtf8("sendBroadcast")) - self.gridLayout_9 = QtGui.QGridLayout(self.sendBroadcast) - self.gridLayout_9.setObjectName(_fromUtf8("gridLayout_9")) - self.verticalSplitter_6 = settingsmixin.SSplitter() - self.verticalSplitter_6.setObjectName(_fromUtf8("verticalSplitter_6")) - self.verticalSplitter_6.setOrientation(QtCore.Qt.Vertical) - self.gridLayout_5 = QtGui.QGridLayout() - self.gridLayout_5.setObjectName(_fromUtf8("gridLayout_5")) - self.label_8 = QtGui.QLabel(self.sendBroadcast) - self.label_8.setObjectName(_fromUtf8("label_8")) - self.gridLayout_5.addWidget(self.label_8, 0, 0, 1, 1) - self.lineEditSubjectBroadcast = QtGui.QLineEdit(self.sendBroadcast) - self.lineEditSubjectBroadcast.setText(_fromUtf8("")) - self.lineEditSubjectBroadcast.setObjectName(_fromUtf8("lineEditSubjectBroadcast")) - self.gridLayout_5.addWidget(self.lineEditSubjectBroadcast, 1, 1, 1, 1) - self.label_7 = QtGui.QLabel(self.sendBroadcast) - self.label_7.setObjectName(_fromUtf8("label_7")) - self.gridLayout_5.addWidget(self.label_7, 1, 0, 1, 1) - self.comboBoxSendFromBroadcast = QtGui.QComboBox(self.sendBroadcast) - self.comboBoxSendFromBroadcast.setMinimumSize(QtCore.QSize(300, 0)) - self.comboBoxSendFromBroadcast.setObjectName(_fromUtf8("comboBoxSendFromBroadcast")) - self.gridLayout_5.addWidget(self.comboBoxSendFromBroadcast, 0, 1, 1, 1) - self.gridLayout_5_Widget = QtGui.QWidget() - self.gridLayout_5_Widget.setLayout(self.gridLayout_5) - self.verticalSplitter_6.addWidget(self.gridLayout_5_Widget) - self.textEditMessageBroadcast = MessageCompose(self.sendBroadcast) - self.textEditMessageBroadcast.setObjectName(_fromUtf8("textEditMessageBroadcast")) - self.verticalSplitter_6.addWidget(self.textEditMessageBroadcast) - self.verticalSplitter_6.setStretchFactor(0, 0) - self.verticalSplitter_6.setStretchFactor(1, 1) - self.verticalSplitter_6.setCollapsible(0, False) - self.verticalSplitter_6.setCollapsible(1, False) - self.verticalSplitter_6.handle(1).setEnabled(False) - self.gridLayout_9.addWidget(self.verticalSplitter_6, 0, 0, 1, 1) - self.tabWidgetSend.addTab(self.sendBroadcast, _fromUtf8("")) - self.verticalSplitter.addWidget(self.tabWidgetSend) - self.tTLContainer = QtGui.QWidget() - self.tTLContainer.setSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Fixed) - self.horizontalLayout_5 = QtGui.QHBoxLayout() - self.tTLContainer.setLayout(self.horizontalLayout_5) - self.horizontalLayout_5.setObjectName(_fromUtf8("horizontalLayout_5")) - self.pushButtonTTL = QtGui.QPushButton(self.send) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.pushButtonTTL.sizePolicy().hasHeightForWidth()) - self.pushButtonTTL.setSizePolicy(sizePolicy) - palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(0, 0, 255)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.ButtonText, brush) - brush = QtGui.QBrush(QtGui.QColor(0, 0, 255)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.ButtonText, brush) - brush = QtGui.QBrush(QtGui.QColor(120, 120, 120)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ButtonText, brush) - self.pushButtonTTL.setPalette(palette) - font = QtGui.QFont() - font.setUnderline(True) - self.pushButtonTTL.setFont(font) - self.pushButtonTTL.setFlat(True) - 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.setOrientation(QtCore.Qt.Horizontal) - self.horizontalSliderTTL.setInvertedAppearance(False) - self.horizontalSliderTTL.setInvertedControls(False) - self.horizontalSliderTTL.setObjectName(_fromUtf8("horizontalSliderTTL")) - self.horizontalLayout_5.addWidget(self.horizontalSliderTTL, 0, QtCore.Qt.AlignLeft) - self.labelHumanFriendlyTTLDescription = QtGui.QLabel(self.send) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.labelHumanFriendlyTTLDescription.sizePolicy().hasHeightForWidth()) - self.labelHumanFriendlyTTLDescription.setSizePolicy(sizePolicy) - 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.verticalSplitter.addWidget(self.tTLContainer) - self.tTLContainer.adjustSize() - self.verticalSplitter.setStretchFactor(1, 0) - self.verticalSplitter.setStretchFactor(0, 1) - self.verticalSplitter.setCollapsible(0, False) - self.verticalSplitter.setCollapsible(1, False) - self.verticalSplitter.handle(1).setEnabled(False) - self.horizontalSplitter.addWidget(self.verticalSplitter) - self.horizontalSplitter.setStretchFactor(0, 0) - self.horizontalSplitter.setStretchFactor(1, 1) - self.horizontalSplitter.setCollapsible(0, False) - self.horizontalSplitter.setCollapsible(1, False) - self.gridLayout_7.addWidget(self.horizontalSplitter, 0, 0, 1, 1) - icon4 = QtGui.QIcon() - icon4.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/send.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) - self.tabWidget.addTab(self.send, icon4, _fromUtf8("")) - self.subscriptions = QtGui.QWidget() - self.subscriptions.setObjectName(_fromUtf8("subscriptions")) - self.gridLayout_3 = QtGui.QGridLayout(self.subscriptions) - self.gridLayout_3.setObjectName(_fromUtf8("gridLayout_3")) - self.horizontalSplitter_4 = settingsmixin.SSplitter() - self.horizontalSplitter_4.setObjectName(_fromUtf8("horizontalSplitter_4")) - self.verticalSplitter_3 = settingsmixin.SSplitter() - self.verticalSplitter_3.setObjectName(_fromUtf8("verticalSplitter_3")) - self.verticalSplitter_3.setOrientation(QtCore.Qt.Vertical) - self.treeWidgetSubscriptions = settingsmixin.STreeWidget(self.subscriptions) - self.treeWidgetSubscriptions.setAlternatingRowColors(True) - self.treeWidgetSubscriptions.setSelectionMode(QtGui.QAbstractItemView.SingleSelection) - self.treeWidgetSubscriptions.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) - 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 - ) - self.treeWidgetSubscriptions.headerItem().setIcon(0, icon5) - self.verticalSplitter_3.addWidget(self.treeWidgetSubscriptions) - self.pushButtonAddSubscription = QtGui.QPushButton(self.subscriptions) - self.pushButtonAddSubscription.setObjectName(_fromUtf8("pushButtonAddSubscription")) - self.pushButtonAddSubscription.resize(200, self.pushButtonAddSubscription.height()) - self.verticalSplitter_3.addWidget(self.pushButtonAddSubscription) - self.verticalSplitter_3.setStretchFactor(0, 1) - self.verticalSplitter_3.setStretchFactor(1, 0) - self.verticalSplitter_3.setCollapsible(0, False) - self.verticalSplitter_3.setCollapsible(1, False) - self.verticalSplitter_3.handle(1).setEnabled(False) - self.horizontalSplitter_4.addWidget(self.verticalSplitter_3) - self.verticalSplitter_4 = settingsmixin.SSplitter() - self.verticalSplitter_4.setObjectName(_fromUtf8("verticalSplitter_4")) - self.verticalSplitter_4.setOrientation(QtCore.Qt.Vertical) - self.horizontalSplitter_2 = QtGui.QSplitter() - self.horizontalSplitter_2.setObjectName(_fromUtf8("horizontalSplitter_2")) - self.inboxSearchLineEditSubscriptions = QtGui.QLineEdit(self.subscriptions) - self.inboxSearchLineEditSubscriptions.setObjectName(_fromUtf8("inboxSearchLineEditSubscriptions")) - self.horizontalSplitter_2.addWidget(self.inboxSearchLineEditSubscriptions) - self.inboxSearchOptionSubscriptions = QtGui.QComboBox(self.subscriptions) - self.inboxSearchOptionSubscriptions.setObjectName(_fromUtf8("inboxSearchOptionSubscriptions")) - 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) - self.horizontalSplitter_2.setStretchFactor(1, 0) - self.verticalSplitter_4.addWidget(self.horizontalSplitter_2) - self.tableWidgetInboxSubscriptions = settingsmixin.STableWidget(self.subscriptions) - self.tableWidgetInboxSubscriptions.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers) - self.tableWidgetInboxSubscriptions.setAlternatingRowColors(True) - self.tableWidgetInboxSubscriptions.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) - self.tableWidgetInboxSubscriptions.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) - self.tableWidgetInboxSubscriptions.setWordWrap(False) - self.tableWidgetInboxSubscriptions.setObjectName(_fromUtf8("tableWidgetInboxSubscriptions")) - self.tableWidgetInboxSubscriptions.setColumnCount(4) - self.tableWidgetInboxSubscriptions.setRowCount(0) - item = QtGui.QTableWidgetItem() - self.tableWidgetInboxSubscriptions.setHorizontalHeaderItem(0, item) - item = QtGui.QTableWidgetItem() - self.tableWidgetInboxSubscriptions.setHorizontalHeaderItem(1, item) - item = QtGui.QTableWidgetItem() - self.tableWidgetInboxSubscriptions.setHorizontalHeaderItem(2, item) - item = QtGui.QTableWidgetItem() - self.tableWidgetInboxSubscriptions.setHorizontalHeaderItem(3, item) - self.tableWidgetInboxSubscriptions.horizontalHeader().setCascadingSectionResizes(True) - self.tableWidgetInboxSubscriptions.horizontalHeader().setDefaultSectionSize(200) - self.tableWidgetInboxSubscriptions.horizontalHeader().setHighlightSections(False) - self.tableWidgetInboxSubscriptions.horizontalHeader().setMinimumSectionSize(27) - self.tableWidgetInboxSubscriptions.horizontalHeader().setSortIndicatorShown(False) - self.tableWidgetInboxSubscriptions.horizontalHeader().setStretchLastSection(True) - self.tableWidgetInboxSubscriptions.verticalHeader().setVisible(False) - self.tableWidgetInboxSubscriptions.verticalHeader().setDefaultSectionSize(26) - self.verticalSplitter_4.addWidget(self.tableWidgetInboxSubscriptions) - self.textEditInboxMessageSubscriptions = MessageView(self.subscriptions) - self.textEditInboxMessageSubscriptions.setBaseSize(QtCore.QSize(0, 500)) - self.textEditInboxMessageSubscriptions.setReadOnly(True) - self.textEditInboxMessageSubscriptions.setObjectName(_fromUtf8("textEditInboxMessageSubscriptions")) - self.verticalSplitter_4.addWidget(self.textEditInboxMessageSubscriptions) - self.verticalSplitter_4.setStretchFactor(0, 0) - self.verticalSplitter_4.setStretchFactor(1, 1) - self.verticalSplitter_4.setStretchFactor(2, 2) - self.verticalSplitter_4.setCollapsible(0, False) - self.verticalSplitter_4.setCollapsible(1, False) - self.verticalSplitter_4.setCollapsible(2, False) - self.verticalSplitter_4.handle(1).setEnabled(False) - self.horizontalSplitter_4.addWidget(self.verticalSplitter_4) - self.horizontalSplitter_4.setStretchFactor(0, 0) - self.horizontalSplitter_4.setStretchFactor(1, 1) - self.horizontalSplitter_4.setCollapsible(0, False) - 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 - ) - self.tabWidget.addTab(self.subscriptions, icon6, _fromUtf8("")) - self.chans = QtGui.QWidget() - self.chans.setObjectName(_fromUtf8("chans")) - self.gridLayout_4 = QtGui.QGridLayout(self.chans) - self.gridLayout_4.setObjectName(_fromUtf8("gridLayout_4")) - self.horizontalSplitter_7 = settingsmixin.SSplitter() - self.horizontalSplitter_7.setObjectName(_fromUtf8("horizontalSplitter_7")) - self.verticalSplitter_17 = settingsmixin.SSplitter() - self.verticalSplitter_17.setObjectName(_fromUtf8("verticalSplitter_17")) - self.verticalSplitter_17.setOrientation(QtCore.Qt.Vertical) - self.treeWidgetChans = settingsmixin.STreeWidget(self.chans) - self.treeWidgetChans.setFrameShadow(QtGui.QFrame.Sunken) - self.treeWidgetChans.setLineWidth(1) - self.treeWidgetChans.setAlternatingRowColors(True) - self.treeWidgetChans.setSelectionMode(QtGui.QAbstractItemView.SingleSelection) - self.treeWidgetChans.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) - 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 - ) - self.treeWidgetChans.headerItem().setIcon(0, icon7) - self.verticalSplitter_17.addWidget(self.treeWidgetChans) - self.pushButtonAddChan = QtGui.QPushButton(self.chans) - self.pushButtonAddChan.setObjectName(_fromUtf8("pushButtonAddChan")) - self.pushButtonAddChan.resize(200, self.pushButtonAddChan.height()) - self.verticalSplitter_17.addWidget(self.pushButtonAddChan) - self.verticalSplitter_17.setStretchFactor(0, 1) - self.verticalSplitter_17.setStretchFactor(1, 0) - self.verticalSplitter_17.setCollapsible(0, False) - self.verticalSplitter_17.setCollapsible(1, False) - self.verticalSplitter_17.handle(1).setEnabled(False) - self.horizontalSplitter_7.addWidget(self.verticalSplitter_17) - self.verticalSplitter_8 = settingsmixin.SSplitter() - self.verticalSplitter_8.setObjectName(_fromUtf8("verticalSplitter_8")) - self.verticalSplitter_8.setOrientation(QtCore.Qt.Vertical) - self.horizontalSplitter_6 = QtGui.QSplitter() - self.horizontalSplitter_6.setObjectName(_fromUtf8("horizontalSplitter_6")) - self.inboxSearchLineEditChans = QtGui.QLineEdit(self.chans) - self.inboxSearchLineEditChans.setObjectName(_fromUtf8("inboxSearchLineEditChans")) - self.horizontalSplitter_6.addWidget(self.inboxSearchLineEditChans) - self.inboxSearchOptionChans = QtGui.QComboBox(self.chans) - self.inboxSearchOptionChans.setObjectName(_fromUtf8("inboxSearchOptionChans")) - self.inboxSearchOptionChans.addItem(_fromUtf8("")) - self.inboxSearchOptionChans.addItem(_fromUtf8("")) - self.inboxSearchOptionChans.addItem(_fromUtf8("")) - 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) - self.horizontalSplitter_6.setStretchFactor(1, 0) - self.verticalSplitter_8.addWidget(self.horizontalSplitter_6) - self.tableWidgetInboxChans = settingsmixin.STableWidget(self.chans) - self.tableWidgetInboxChans.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers) - self.tableWidgetInboxChans.setAlternatingRowColors(True) - self.tableWidgetInboxChans.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) - self.tableWidgetInboxChans.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) - self.tableWidgetInboxChans.setWordWrap(False) - self.tableWidgetInboxChans.setObjectName(_fromUtf8("tableWidgetInboxChans")) - self.tableWidgetInboxChans.setColumnCount(4) - self.tableWidgetInboxChans.setRowCount(0) - item = QtGui.QTableWidgetItem() - self.tableWidgetInboxChans.setHorizontalHeaderItem(0, item) - item = QtGui.QTableWidgetItem() - self.tableWidgetInboxChans.setHorizontalHeaderItem(1, item) - item = QtGui.QTableWidgetItem() - self.tableWidgetInboxChans.setHorizontalHeaderItem(2, item) - item = QtGui.QTableWidgetItem() - self.tableWidgetInboxChans.setHorizontalHeaderItem(3, item) - self.tableWidgetInboxChans.horizontalHeader().setCascadingSectionResizes(True) - self.tableWidgetInboxChans.horizontalHeader().setDefaultSectionSize(200) - self.tableWidgetInboxChans.horizontalHeader().setHighlightSections(False) - self.tableWidgetInboxChans.horizontalHeader().setMinimumSectionSize(27) - self.tableWidgetInboxChans.horizontalHeader().setSortIndicatorShown(False) - self.tableWidgetInboxChans.horizontalHeader().setStretchLastSection(True) - self.tableWidgetInboxChans.verticalHeader().setVisible(False) - self.tableWidgetInboxChans.verticalHeader().setDefaultSectionSize(26) - self.verticalSplitter_8.addWidget(self.tableWidgetInboxChans) - self.textEditInboxMessageChans = MessageView(self.chans) - self.textEditInboxMessageChans.setBaseSize(QtCore.QSize(0, 500)) - self.textEditInboxMessageChans.setReadOnly(True) - self.textEditInboxMessageChans.setObjectName(_fromUtf8("textEditInboxMessageChans")) - self.verticalSplitter_8.addWidget(self.textEditInboxMessageChans) - self.verticalSplitter_8.setStretchFactor(0, 0) - self.verticalSplitter_8.setStretchFactor(1, 1) - self.verticalSplitter_8.setStretchFactor(2, 2) - self.verticalSplitter_8.setCollapsible(0, False) - self.verticalSplitter_8.setCollapsible(1, False) - self.verticalSplitter_8.setCollapsible(2, False) - self.verticalSplitter_8.handle(1).setEnabled(False) - self.horizontalSplitter_7.addWidget(self.verticalSplitter_8) - self.horizontalSplitter_7.setStretchFactor(0, 0) - self.horizontalSplitter_7.setStretchFactor(1, 1) - self.horizontalSplitter_7.setCollapsible(0, False) - 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 - ) - 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': - self.blackwhitelist.radioButtonWhitelist.click() - self.blackwhitelist.rerenderBlackWhiteList() - - self.networkstatus = NetworkStatus() - self.tabWidget.addTab(self.networkstatus, QtGui.QIcon(":/newPrefix/images/networkstatus.png"), "") - self.gridLayout_10.addWidget(self.tabWidget, 0, 0, 1, 1) - MainWindow.setCentralWidget(self.centralwidget) - self.menubar = QtGui.QMenuBar(MainWindow) - self.menubar.setGeometry(QtCore.QRect(0, 0, 885, 27)) - self.menubar.setObjectName(_fromUtf8("menubar")) - self.menuFile = QtGui.QMenu(self.menubar) - self.menuFile.setObjectName(_fromUtf8("menuFile")) - self.menuSettings = QtGui.QMenu(self.menubar) - self.menuSettings.setObjectName(_fromUtf8("menuSettings")) - self.menuHelp = QtGui.QMenu(self.menubar) - self.menuHelp.setObjectName(_fromUtf8("menuHelp")) - MainWindow.setMenuBar(self.menubar) - self.statusbar = QtGui.QStatusBar(MainWindow) - self.statusbar.setMaximumSize(QtCore.QSize(16777215, 22)) - self.statusbar.setObjectName(_fromUtf8("statusbar")) - MainWindow.setStatusBar(self.statusbar) - self.actionImport_keys = QtGui.QAction(MainWindow) - self.actionImport_keys.setObjectName(_fromUtf8("actionImport_keys")) - self.actionManageKeys = QtGui.QAction(MainWindow) - self.actionManageKeys.setCheckable(False) - self.actionManageKeys.setEnabled(True) - 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) - self.actionExit.setObjectName(_fromUtf8("actionExit")) - self.actionHelp = QtGui.QAction(MainWindow) - icon = QtGui.QIcon.fromTheme(_fromUtf8("help-contents")) - self.actionHelp.setIcon(icon) - self.actionHelp.setObjectName(_fromUtf8("actionHelp")) - self.actionSupport = QtGui.QAction(MainWindow) - icon = QtGui.QIcon.fromTheme(_fromUtf8("help-support")) - self.actionSupport.setIcon(icon) - self.actionSupport.setObjectName(_fromUtf8("actionSupport")) - self.actionAbout = QtGui.QAction(MainWindow) - icon = QtGui.QIcon.fromTheme(_fromUtf8("help-about")) - self.actionAbout.setIcon(icon) - self.actionAbout.setObjectName(_fromUtf8("actionAbout")) - self.actionSettings = QtGui.QAction(MainWindow) - icon = QtGui.QIcon.fromTheme(_fromUtf8("document-properties")) - self.actionSettings.setIcon(icon) - self.actionSettings.setObjectName(_fromUtf8("actionSettings")) - self.actionRegenerateDeterministicAddresses = QtGui.QAction(MainWindow) - icon = QtGui.QIcon.fromTheme(_fromUtf8("view-refresh")) - self.actionRegenerateDeterministicAddresses.setIcon(icon) - self.actionRegenerateDeterministicAddresses.setObjectName(_fromUtf8("actionRegenerateDeterministicAddresses")) - self.actionDeleteAllTrashedMessages = QtGui.QAction(MainWindow) - icon = QtGui.QIcon.fromTheme(_fromUtf8("user-trash")) - self.actionDeleteAllTrashedMessages.setIcon(icon) - self.actionDeleteAllTrashedMessages.setObjectName(_fromUtf8("actionDeleteAllTrashedMessages")) - self.actionJoinChan = QtGui.QAction(MainWindow) - icon = QtGui.QIcon.fromTheme(_fromUtf8("contact-new")) - self.actionJoinChan.setIcon(icon) - self.actionJoinChan.setObjectName(_fromUtf8("actionJoinChan")) - 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) - self.menuHelp.addAction(self.actionSupport) - self.menuHelp.addAction(self.actionAbout) - self.menubar.addAction(self.menuFile.menuAction()) - self.menubar.addAction(self.menuSettings.menuAction()) - 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) - ) - QtCore.QMetaObject.connectSlotsByName(MainWindow) - MainWindow.setTabOrder(self.tableWidgetInbox, self.textEditInboxMessage) - MainWindow.setTabOrder(self.textEditInboxMessage, self.comboBoxSendFrom) - MainWindow.setTabOrder(self.comboBoxSendFrom, self.lineEditTo) - MainWindow.setTabOrder(self.lineEditTo, self.lineEditSubject) - 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)) - self.pushButtonNewAddress.setText(_translate("MainWindow", "New Identity", None)) - self.inboxSearchLineEdit.setPlaceholderText(_translate("MainWindow", "Search", None)) - self.inboxSearchOption.setItemText(0, _translate("MainWindow", "All", None)) - self.inboxSearchOption.setItemText(1, _translate("MainWindow", "To", None)) - self.inboxSearchOption.setItemText(2, _translate("MainWindow", "From", None)) - self.inboxSearchOption.setItemText(3, _translate("MainWindow", "Subject", None)) - self.inboxSearchOption.setItemText(4, _translate("MainWindow", "Message", None)) - self.tableWidgetInbox.setSortingEnabled(True) - item = self.tableWidgetInbox.horizontalHeaderItem(0) - item.setText(_translate("MainWindow", "To", None)) - item = self.tableWidgetInbox.horizontalHeaderItem(1) - item.setText(_translate("MainWindow", "From", None)) - item = self.tableWidgetInbox.horizontalHeaderItem(2) - item.setText(_translate("MainWindow", "Subject", None)) - item = self.tableWidgetInbox.horizontalHeaderItem(3) - item.setText(_translate("MainWindow", "Received", None)) - self.tabWidget.setTabText(self.tabWidget.indexOf(self.inbox), _translate("MainWindow", "Messages", None)) - self.tableWidgetAddressBook.setSortingEnabled(True) - item = self.tableWidgetAddressBook.horizontalHeaderItem(0) - item.setText(_translate("MainWindow", "Address book", None)) - item = self.tableWidgetAddressBook.horizontalHeaderItem(1) - item.setText(_translate("MainWindow", "Address", None)) - self.pushButtonAddAddressBook.setText(_translate("MainWindow", "Add Contact", None)) - self.pushButtonFetchNamecoinID.setText(_translate("MainWindow", "Fetch Namecoin ID", None)) - 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.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.pushButtonTTL.setText(_translate("MainWindow", "TTL:", None)) - hours = 48 - try: - hours = int(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.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.tableWidgetInboxSubscriptions.setSortingEnabled(True) - item = self.tableWidgetInboxSubscriptions.horizontalHeaderItem(0) - item.setText(_translate("MainWindow", "To", None)) - item = self.tableWidgetInboxSubscriptions.horizontalHeaderItem(1) - item.setText(_translate("MainWindow", "From", None)) - item = self.tableWidgetInboxSubscriptions.horizontalHeaderItem(2) - 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.treeWidgetChans.headerItem().setText(0, _translate("MainWindow", "Chans", None)) - self.pushButtonAddChan.setText(_translate("MainWindow", "Add Chan", None)) - self.inboxSearchLineEditChans.setPlaceholderText(_translate("MainWindow", "Search", None)) - self.inboxSearchOptionChans.setItemText(0, _translate("MainWindow", "All", None)) - self.inboxSearchOptionChans.setItemText(1, _translate("MainWindow", "To", None)) - self.inboxSearchOptionChans.setItemText(2, _translate("MainWindow", "From", None)) - self.inboxSearchOptionChans.setItemText(3, _translate("MainWindow", "Subject", None)) - self.inboxSearchOptionChans.setItemText(4, _translate("MainWindow", "Message", None)) - self.tableWidgetInboxChans.setSortingEnabled(True) - item = self.tableWidgetInboxChans.horizontalHeaderItem(0) - item.setText(_translate("MainWindow", "To", None)) - item = self.tableWidgetInboxChans.horizontalHeaderItem(1) - item.setText(_translate("MainWindow", "From", None)) - item = self.tableWidgetInboxChans.horizontalHeaderItem(2) - item.setText(_translate("MainWindow", "Subject", None)) - item = self.tableWidgetInboxChans.horizontalHeaderItem(3) - 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.networkstatus.retranslateUi() - 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)) - self.actionImport_keys.setText(_translate("MainWindow", "Import keys", None)) - self.actionManageKeys.setText(_translate("MainWindow", "Manage keys", None)) - self.actionExit.setText(_translate("MainWindow", "Quit", None)) - self.actionExit.setShortcut(_translate("MainWindow", "Ctrl+Q", None)) - self.actionHelp.setText(_translate("MainWindow", "Help", None)) - self.actionHelp.setShortcut(_translate("MainWindow", "F1", None)) - 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.actionDeleteAllTrashedMessages.setText(_translate("MainWindow", "Delete all trashed messages", None)) - self.actionJoinChan.setText(_translate("MainWindow", "Join / Create chan", None)) - self.updateNetworkSwitchMenuLabel() - - -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/bitmessageui.ui b/src/bitmessageqt/bitmessageui.ui deleted file mode 100644 index fef40be6..00000000 --- a/src/bitmessageqt/bitmessageui.ui +++ /dev/null @@ -1,1454 +0,0 @@ - - - MainWindow - - - - 0 - 0 - 885 - 580 - - - - Bitmessage - - - - :/newPrefix/images/can-icon-24px.png:/newPrefix/images/can-icon-24px.png - - - QTabWidget::Rounded - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 9 - - - - QTabWidget::North - - - QTabWidget::Rounded - - - 0 - - - - - :/newPrefix/images/inbox.png:/newPrefix/images/inbox.png - - - Messages - - - - - - - - - - - 200 - 16777215 - - - - - Identities - - - - :/newPrefix/images/identities.png - - - - - - - - - - 200 - 16777215 - - - - New Indentitiy - - - - - - - - - - - 0 - - - - - Search - - - - - - - - All - - - - - To - - - - - From - - - - - Subject - - - - - Message - - - - - - - - - - QAbstractItemView::NoEditTriggers - - - true - - - QAbstractItemView::ExtendedSelection - - - QAbstractItemView::SelectRows - - - true - - - false - - - true - - - 200 - - - false - - - 27 - - - false - - - true - - - false - - - 26 - - - - To - - - - - From - - - - - Subject - - - - - Received - - - - - - - - - 0 - 500 - - - - true - - - - - - - - - - - - - :/newPrefix/images/send.png:/newPrefix/images/send.png - - - Send - - - - - - - - - - - 200 - 16777215 - - - - true - - - QAbstractItemView::ExtendedSelection - - - QAbstractItemView::SelectRows - - - true - - - true - - - 200 - - - false - - - true - - - false - - - - Address book - - - - :/newPrefix/images/addressbook.png - - - - - - Address - - - - - - - - - 200 - 16777215 - - - - Add Contact - - - - - - - - 200 - 16777215 - - - - - 9 - - - - Fetch Namecoin ID - - - - - - - - - - - 0 - - - - Send ordinary Message - - - - - - - - - - Subject: - - - - - - - From: - - - - - - - - - - - - - - To: - - - - - - - - 300 - 0 - - - - - - - - - - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Droid Sans'; font-size:9pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2';"><br /></p></body></html> - - - - - - - - - - Send Message to your Subscribers - - - - - - - - - - From: - - - - - - - - - - - - - - Subject: - - - - - - - - 300 - 0 - - - - - - - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Droid Sans'; font-size:9pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2';"><br /></p></body></html> - - - - - - - - - - - - - - - - 0 - 0 - - - - - 32 - 16777215 - - - - - - - - - 0 - 0 - 255 - - - - - - - - - 0 - 0 - 255 - - - - - - - - - 120 - 120 - 120 - - - - - - - - - true - - - - TTL: - - - true - - - - - - - - 35 - 0 - - - - - 70 - 16777215 - - - - Qt::Horizontal - - - false - - - false - - - - - - - - 0 - 0 - - - - - 45 - 0 - - - - - 45 - 16777215 - - - - X days - - - - - - - - 16777215 - 16777215 - - - - Send - - - - - - - - - - - - - - - - :/newPrefix/images/subscriptions.png:/newPrefix/images/subscriptions.png - - - Subscriptions - - - - - - - - - - - 200 - 16777215 - - - - true - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectRows - - - - Subscriptions - - - - :/newPrefix/images/subscriptions.png - - - - - - - - - - 200 - 16777215 - - - - Add new Subscription - - - - - - - - - - - - - Search - - - - - - - - All - - - - - To - - - - - From - - - - - Subject - - - - - Message - - - - - - - - - - QAbstractItemView::NoEditTriggers - - - true - - - QAbstractItemView::ExtendedSelection - - - QAbstractItemView::SelectRows - - - true - - - false - - - true - - - 200 - - - false - - - 27 - - - false - - - true - - - false - - - 26 - - - - To - - - - - From - - - - - Subject - - - - - Received - - - - - - - - - 0 - 500 - - - - true - - - - - - - - - - - - - :/newPrefix/images/can-icon-16px.png:/newPrefix/images/can-icon-16px.png - - - Chans - - - - - - - - - - - 200 - 16777215 - - - - QFrame::Sunken - - - 1 - - - true - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectRows - - - - Chans - - - - :/newPrefix/images/can-icon-16px.png - - - - - - - - - - 200 - 16777215 - - - - Add Chan - - - - - - - - - - - - - Search - - - - - - - - All - - - - - To - - - - - From - - - - - Subject - - - - - Message - - - - - - - - - - QAbstractItemView::NoEditTriggers - - - true - - - QAbstractItemView::ExtendedSelection - - - QAbstractItemView::SelectRows - - - true - - - false - - - true - - - 200 - - - false - - - 27 - - - false - - - true - - - false - - - 26 - - - - To - - - - - From - - - - - Subject - - - - - Received - - - - - - - - - 0 - 500 - - - - true - - - - - - - - - - - - - :/newPrefix/images/blacklist.png:/newPrefix/images/blacklist.png - - - Blacklist - - - - - - Use a Blacklist (Allow all incoming messages except those on the Blacklist) - - - true - - - - - - - Use a Whitelist (Block all incoming messages except those on the Whitelist) - - - - - - - Add new entry - - - - - - - Qt::Horizontal - - - - 689 - 20 - - - - - - - - true - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectRows - - - true - - - true - - - 400 - - - false - - - false - - - true - - - false - - - - Name or Label - - - - - Address - - - - - - - - - - :/newPrefix/images/networkstatus.png:/newPrefix/images/networkstatus.png - - - Network Status - - - - - 680 - 440 - 21 - 23 - - - - - - - - :/newPrefix/images/redicon.png:/newPrefix/images/redicon.png - - - true - - - - - - 20 - 70 - 241 - 241 - - - - - - - - - 212 - 208 - 200 - - - - - - - - - 212 - 208 - 200 - - - - - - - - - 212 - 208 - 200 - - - - - - - - QFrame::Box - - - QFrame::Plain - - - false - - - true - - - QAbstractItemView::NoSelection - - - true - - - false - - - true - - - false - - - - Stream # - - - - - Connections - - - - - - - 20 - 30 - 401 - 16 - - - - Total connections: - - - - - - 320 - 110 - 331 - 20 - - - - Since startup: - - - - - - 350 - 130 - 361 - 16 - - - - Processed 0 person-to-person messages. - - - - - - 350 - 170 - 331 - 16 - - - - Processed 0 public keys. - - - - - - 350 - 150 - 351 - 16 - - - - Processed 0 broadcasts. - - - - - - 320 - 250 - 291 - 16 - - - - Inventory lookups per second: 0 - - - - - - 350 - 210 - 251 - 16 - - - - Down: 0 KB/s - - - - - - 350 - 230 - 251 - 16 - - - - Up: 0 KB/s - - - - - - - - - - - 0 - 0 - 885 - 27 - - - - - File - - - - - - - - - Settings - - - - - - Help - - - - - - - - - - - - 16777215 - 22 - - - - - - Import keys - - - - - false - - - true - - - - - - - - Manage keys - - - - - - - - - - Quit - - - Ctrl+Q - - - - - - - - - - Help - - - F1 - - - - - - - - - - About - - - - - - - - - - Settings - - - - - - - - - - Regenerate deterministic addresses - - - - - - - - - - Delete all trashed messages - - - - - - - - - - Join / Create chan - - - - - tableWidgetInbox - textEditInboxMessage - comboBoxSendFrom - lineEditTo - lineEditSubject - textEditMessage - pushButtonSend - pushButtonAddSubscription - radioButtonBlacklist - radioButtonWhitelist - pushButtonAddBlacklist - tableWidgetBlacklist - tableWidgetConnectionCount - pushButtonStatusIcon - - - - - - diff --git a/src/bitmessageqt/blacklist.py b/src/bitmessageqt/blacklist.py deleted file mode 100644 index 093f23d8..00000000 --- a/src/bitmessageqt/blacklist.py +++ /dev/null @@ -1,252 +0,0 @@ -from PyQt4 import QtCore, QtGui - -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 - - -class Blacklist(QtGui.QWidget, RetranslateMixin): - def __init__(self, parent=None): - super(Blacklist, self).__init__(parent) - widgets.load('blacklist.ui', self) - - QtCore.QObject.connect(self.radioButtonBlacklist, QtCore.SIGNAL( - "clicked()"), self.click_radioButtonBlacklist) - QtCore.QObject.connect(self.radioButtonWhitelist, QtCore.SIGNAL( - "clicked()"), self.click_radioButtonWhitelist) - QtCore.QObject.connect(self.pushButtonAddBlacklist, QtCore.SIGNAL( - "clicked()"), self.click_pushButtonAddBlacklist) - - self.init_blacklist_popup_menu() - - # Initialize blacklist - QtCore.QObject.connect(self.tableWidgetBlacklist, QtCore.SIGNAL( - "itemChanged(QTableWidgetItem *)"), self.tableWidgetBlacklistItemChanged) - - # Set the icon sizes for the identicons - identicon_size = 3*7 - self.tableWidgetBlacklist.setIconSize(QtCore.QSize(identicon_size, identicon_size)) - - self.UISignalThread = UISignaler.get() - QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( - "rerenderBlackWhiteList()"), self.rerenderBlackWhiteList) - - def click_radioButtonBlacklist(self): - if config.get('bitmessagesettings', 'blackwhitelist') == 'white': - config.set('bitmessagesettings', 'blackwhitelist', 'black') - config.save() - # self.tableWidgetBlacklist.clearContents() - self.tableWidgetBlacklist.setRowCount(0) - self.rerenderBlackWhiteList() - - def click_radioButtonWhitelist(self): - if config.get('bitmessagesettings', 'blackwhitelist') == 'black': - config.set('bitmessagesettings', 'blackwhitelist', 'white') - config.save() - # self.tableWidgetBlacklist.clearContents() - self.tableWidgetBlacklist.setRowCount(0) - self.rerenderBlackWhiteList() - - def click_pushButtonAddBlacklist(self): - self.NewBlacklistDialogInstance = AddAddressDialog(self) - if self.NewBlacklistDialogInstance.exec_(): - if self.NewBlacklistDialogInstance.labelAddressCheck.text() == \ - _translate("MainWindow", "Address is valid."): - address = addBMIfNotPresent(str( - self.NewBlacklistDialogInstance.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': - sql = '''select * from blacklist where address=?''' - else: - sql = '''select * from whitelist where address=?''' - queryreturn = sqlQuery(sql,*t) - if queryreturn == []: - self.tableWidgetBlacklist.setSortingEnabled(False) - self.tableWidgetBlacklist.insertRow(0) - newItem = QtGui.QTableWidgetItem(unicode( - self.NewBlacklistDialogInstance.lineEditLabel.text().toUtf8(), 'utf-8')) - newItem.setIcon(avatarize(address)) - self.tableWidgetBlacklist.setItem(0, 0, newItem) - newItem = QtGui.QTableWidgetItem(address) - newItem.setFlags( - 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': - 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.") - )) - else: - UISignalQueue.put(( - 'updateStatusBar', - _translate( - "MainWindow", - "The address you entered was invalid. Ignoring it.") - )) - - def tableWidgetBlacklistItemChanged(self, item): - if item.column() == 0: - addressitem = self.tableWidgetBlacklist.item(item.row(), 1) - if isinstance(addressitem, QtGui.QTableWidgetItem): - if self.radioButtonBlacklist.isChecked(): - sqlExecute('''UPDATE blacklist SET label=? WHERE address=?''', - str(item.text()), str(addressitem.text())) - else: - sqlExecute('''UPDATE whitelist SET label=? WHERE address=?''', - str(item.text()), str(addressitem.text())) - - def init_blacklist_popup_menu(self, connectSignal=True): - # Popup menu for the Blacklist page - self.blacklistContextMenuToolbar = QtGui.QToolBar() - # Actions - self.actionBlacklistNew = self.blacklistContextMenuToolbar.addAction( - _translate( - "MainWindow", "Add new entry"), self.on_action_BlacklistNew) - self.actionBlacklistDelete = self.blacklistContextMenuToolbar.addAction( - _translate( - "MainWindow", "Delete"), self.on_action_BlacklistDelete) - self.actionBlacklistClipboard = self.blacklistContextMenuToolbar.addAction( - _translate( - "MainWindow", "Copy address to clipboard"), - self.on_action_BlacklistClipboard) - self.actionBlacklistEnable = self.blacklistContextMenuToolbar.addAction( - _translate( - "MainWindow", "Enable"), self.on_action_BlacklistEnable) - self.actionBlacklistDisable = self.blacklistContextMenuToolbar.addAction( - _translate( - "MainWindow", "Disable"), self.on_action_BlacklistDisable) - self.actionBlacklistSetAvatar = self.blacklistContextMenuToolbar.addAction( - _translate( - "MainWindow", "Set avatar..."), - self.on_action_BlacklistSetAvatar) - self.tableWidgetBlacklist.setContextMenuPolicy( - QtCore.Qt.CustomContextMenu) - if connectSignal: - self.connect(self.tableWidgetBlacklist, QtCore.SIGNAL( - 'customContextMenuRequested(const QPoint&)'), - self.on_context_menuBlacklist) - self.popMenuBlacklist = QtGui.QMenu(self) - # self.popMenuBlacklist.addAction( self.actionBlacklistNew ) - self.popMenuBlacklist.addAction(self.actionBlacklistDelete) - self.popMenuBlacklist.addSeparator() - self.popMenuBlacklist.addAction(self.actionBlacklistClipboard) - self.popMenuBlacklist.addSeparator() - self.popMenuBlacklist.addAction(self.actionBlacklistEnable) - self.popMenuBlacklist.addAction(self.actionBlacklistDisable) - self.popMenuBlacklist.addAction(self.actionBlacklistSetAvatar) - - def rerenderBlackWhiteList(self): - tabs = self.parent().parent() - if 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') - if listType == 'black': - queryreturn = sqlQuery('''SELECT label, address, enabled FROM blacklist''') - else: - queryreturn = sqlQuery('''SELECT label, address, enabled FROM whitelist''') - self.tableWidgetBlacklist.setSortingEnabled(False) - for row in queryreturn: - label, address, enabled = row - self.tableWidgetBlacklist.insertRow(0) - newItem = QtGui.QTableWidgetItem(unicode(label, 'utf-8')) - if not enabled: - newItem.setTextColor(QtGui.QColor(128, 128, 128)) - newItem.setIcon(avatarize(address)) - self.tableWidgetBlacklist.setItem(0, 0, newItem) - newItem = QtGui.QTableWidgetItem(address) - newItem.setFlags( - QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) - if not enabled: - newItem.setTextColor(QtGui.QColor(128, 128, 128)) - self.tableWidgetBlacklist.setItem(0, 1, newItem) - self.tableWidgetBlacklist.setSortingEnabled(True) - - # Group of functions for the Blacklist dialog box - def on_action_BlacklistNew(self): - self.click_pushButtonAddBlacklist() - - def on_action_BlacklistDelete(self): - currentRow = self.tableWidgetBlacklist.currentRow() - labelAtCurrentRow = self.tableWidgetBlacklist.item( - currentRow, 0).text().toUtf8() - addressAtCurrentRow = self.tableWidgetBlacklist.item( - currentRow, 1).text() - if config.get('bitmessagesettings', 'blackwhitelist') == 'black': - sqlExecute( - '''DELETE FROM blacklist WHERE label=? AND address=?''', - str(labelAtCurrentRow), str(addressAtCurrentRow)) - else: - sqlExecute( - '''DELETE FROM whitelist WHERE label=? AND address=?''', - str(labelAtCurrentRow), str(addressAtCurrentRow)) - self.tableWidgetBlacklist.removeRow(currentRow) - - def on_action_BlacklistClipboard(self): - currentRow = self.tableWidgetBlacklist.currentRow() - addressAtCurrentRow = self.tableWidgetBlacklist.item( - currentRow, 1).text() - clipboard = QtGui.QApplication.clipboard() - clipboard.setText(str(addressAtCurrentRow)) - - def on_context_menuBlacklist(self, point): - self.popMenuBlacklist.exec_( - self.tableWidgetBlacklist.mapToGlobal(point)) - - def on_action_BlacklistEnable(self): - currentRow = self.tableWidgetBlacklist.currentRow() - addressAtCurrentRow = self.tableWidgetBlacklist.item( - currentRow, 1).text() - self.tableWidgetBlacklist.item( - 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': - sqlExecute( - '''UPDATE blacklist SET enabled=1 WHERE address=?''', - str(addressAtCurrentRow)) - else: - sqlExecute( - '''UPDATE whitelist SET enabled=1 WHERE address=?''', - str(addressAtCurrentRow)) - - def on_action_BlacklistDisable(self): - currentRow = self.tableWidgetBlacklist.currentRow() - addressAtCurrentRow = self.tableWidgetBlacklist.item( - currentRow, 1).text() - self.tableWidgetBlacklist.item( - 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': - sqlExecute( - '''UPDATE blacklist SET enabled=0 WHERE address=?''', str(addressAtCurrentRow)) - else: - sqlExecute( - '''UPDATE whitelist SET enabled=0 WHERE address=?''', str(addressAtCurrentRow)) - - def on_action_BlacklistSetAvatar(self): - self.window().on_action_SetAvatar(self.tableWidgetBlacklist) diff --git a/src/bitmessageqt/blacklist.ui b/src/bitmessageqt/blacklist.ui deleted file mode 100644 index 80993fac..00000000 --- a/src/bitmessageqt/blacklist.ui +++ /dev/null @@ -1,108 +0,0 @@ - - - blacklist - - - - 0 - 0 - 819 - 295 - - - - - - - Use a Blacklist (Allow all incoming messages except those on the Blacklist) - - - true - - - - - - - Use a Whitelist (Block all incoming messages except those on the Whitelist) - - - - - - - Add new entry - - - - - - - Qt::Horizontal - - - - 689 - 20 - - - - - - - - true - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectRows - - - true - - - true - - - 400 - - - false - - - false - - - true - - - false - - - - Name or Label - - - - - Address - - - - - - - - - STableWidget - QTableWidget -
bitmessageqt/settingsmixin.h
-
-
- - - - -
diff --git a/src/bitmessageqt/connect.ui b/src/bitmessageqt/connect.ui deleted file mode 100644 index 8b76f5ac..00000000 --- a/src/bitmessageqt/connect.ui +++ /dev/null @@ -1,108 +0,0 @@ - - - connectDialog - - - - 0 - 0 - 400 - 124 - - - - Bitmessage - - - - - - Bitmessage won't connect to anyone until you let it. - - - - - - - Connect now - - - true - - - - - - - Let me configure special network settings first - - - - - - - Work offline - - - - - - - Qt::Horizontal - - - - 185 - 24 - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Ok - - - - - - - - - buttonBox - accepted() - connectDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - connectDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff --git a/src/bitmessageqt/dialogs.py b/src/bitmessageqt/dialogs.py deleted file mode 100644 index dc31e266..00000000 --- a/src/bitmessageqt/dialogs.py +++ /dev/null @@ -1,85 +0,0 @@ -""" -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 tr import _translate -from version import softwareVersion - - -__all__ = [ - "NewChanDialog", "AddAddressDialog", "NewAddressDialog", - "NewSubscriptionDialog", "RegenerateAddressesDialog", - "SpecialAddressBehaviorDialog", "EmailGatewayDialog", - "SettingsDialog" -] - - -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)) diff --git a/src/bitmessageqt/emailgateway.ui b/src/bitmessageqt/emailgateway.ui deleted file mode 100644 index 77a66dec..00000000 --- a/src/bitmessageqt/emailgateway.ui +++ /dev/null @@ -1,224 +0,0 @@ - - - EmailGatewayDialog - - - - 0 - 0 - 386 - 240 - - - - Email gateway - - - - - - true - - - Desired email address (including @mailchuck.com): - - - - - - - Register on email gateway - - - true - - - - - - - - 368 - 0 - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - true - - - @mailchuck.com - - - 0 - - - - - - - Email gateway allows you to communicate with email users. Currently, only the Mailchuck email gateway (@mailchuck.com) is available. - - - true - - - - - - - false - - - Account status at email gateway - - - false - - - - - - - false - - - Change account settings at email gateway - - - false - - - - - - - false - - - Unregister from email gateway - - - false - - - - - - - radioButtonRegister - lineEditEmail - radioButtonStatus - radioButtonSettings - radioButtonUnregister - buttonBox - - - - - buttonBox - accepted() - EmailGatewayDialog - accept() - - - 227 - 152 - - - 157 - 171 - - - - - buttonBox - rejected() - EmailGatewayDialog - reject() - - - 295 - 158 - - - 286 - 171 - - - - - radioButtonRegister - clicked(bool) - lineEditEmail - setEnabled(bool) - - - 95 - 40 - - - 94 - 123 - - - - - radioButtonUnregister - clicked(bool) - lineEditEmail - setDisabled(bool) - - - 139 - 19 - - - 187 - 123 - - - - - 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 deleted file mode 100644 index c50b7d3d..00000000 --- a/src/bitmessageqt/foldertree.py +++ /dev/null @@ -1,627 +0,0 @@ -""" -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 bmconfigparser import config -from helper_sql import sqlExecute, sqlQuery -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""" - ALL = 0 - NORMAL = 1 - CHAN = 2 - MAILINGLIST = 3 - SUBSCRIPTION = 4 - BROADCAST = 5 - - def accountColor(self): - """QT UI color for an account""" - 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""" - if not self.parent().isEnabled: - return QtGui.QColor(128, 128, 128) - 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 - self.unreadCount = int(cnt) - if isinstance(self, QtGui.QTreeWidgetItem): - self.emitDataChanged() - - def setEnabled(self, enabled): - """Set account enabled (QT UI)""" - self.isEnabled = enabled - try: - self.setExpanded(enabled) - except AttributeError: - pass - if isinstance(self, Ui_AddressWidget): - for i in range(self.childCount()): - if isinstance(self.child(i), Ui_FolderWidget): - self.child(i).setEnabled(enabled) - if isinstance(self, QtGui.QTreeWidgetItem): - 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'): - self.type = self.CHAN - elif config.safeGetBoolean(self.address, 'mailinglist'): - self.type = self.MAILINGLIST - elif sqlQuery( - '''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): - try: - retval = unicode( - config.get(self.address, 'label'), 'utf-8') - except Exception: - 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: - 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') - - -class BMTreeWidgetItem(QtGui.QTreeWidgetItem, AccountMixin): - """A common abstract class for Tree widget item""" - - def __init__(self, parent, pos, address, unreadCount): - super(QtGui.QTreeWidgetItem, self).__init__() - self.setAddress(address) - self.setUnreadCount(unreadCount) - self._setup(parent, pos) - - def _getAddressBracket(self, unreadCount=False): - return " (" + str(self.unreadCount) + ")" if unreadCount else "" - - def data(self, column, role): - """Override internal QT method for returning object data""" - if column == 0: - if role == QtCore.Qt.DisplayRole: - return self._getLabel() + self._getAddressBracket( - self.unreadCount > 0) - elif role == QtCore.Qt.EditRole: - return self._getLabel() - elif role == QtCore.Qt.ToolTipRole: - return self._getLabel() + self._getAddressBracket(False) - 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() - return super(Ui_FolderWidget, self).data(column, role) - - # inbox, sent, thrash first, rest alphabetically - def __lt__(self, other): - if isinstance(other, Ui_FolderWidget): - if self.folderName in self.folderWeight: - x = self.folderWeight[self.folderName] - else: - x = 99 - if other.folderName in self.folderWeight: - y = self.folderWeight[other.folderName] - else: - y = 99 - reverse = QtCore.Qt.DescendingOrder == \ - self.treeWidget().header().sortIndicatorOrder() - if x == y: - return self.folderName < other.folderName - 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() - parent.insertTopLevelItem(pos, self) - - def _getLabel(self): - if self.address is None: - return unicode(_translate( - "MainWindow", "All accounts").toUtf8(), 'utf-8', 'ignore') - else: - try: - return unicode( - 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) - 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')) - 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() - 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 _getSortRank(self): - return self.type if self.isEnabled else (self.type + 100) - - # 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 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 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) - - def _getLabel(self): - queryreturn = sqlQuery( - '''select label from subscriptions where address=?''', self.address) - if queryreturn != []: - for row in queryreturn: - 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 - - def setData(self, column, role, value): - """Save subscription label to database""" - if role == QtCore.Qt.EditRole: - if isinstance(value, QtCore.QVariant): - label = str( - value.toString().toUtf8()).decode('utf-8', 'ignore') - else: - label = unicode(value, 'utf-8', 'ignore') - sqlExecute( - '''UPDATE subscriptions SET label=? WHERE address=?''', - label, self.address) - 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): - super(QtGui.QTableWidgetItem, self).__init__() - self.setLabel(label) - self.setUnread(unread) - self._setup() - - def _setup(self): - self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) - - def setLabel(self, label): - """Set object label""" - self.label = label - - 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 - ): - return self.label - 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: - 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): - 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): - self.setSubject(subject) - super(MessageList_SubjectWidget, self).__init__(label, unread) - - def setSubject(self, subject): - """Set subject""" - self.subject = subject - - def data(self, role): - """Return object data (QT UI)""" - if 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) - - # label (or address) alphabetically, disabled at the end - def __lt__(self, other): - 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) - - def data(self, role): - """Return object data""" - if 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): - try: - config.get(self.address, 'label') - config.set(self.address, 'label', self.label) - config.save() - except: - sqlExecute('''UPDATE addressbook set label=? WHERE address=?''', self.label, self.address) - elif self.type == AccountMixin.SUBSCRIPTION: - 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() - - if self.type == other.type: - return self.label.lower() < other.label.lower() - 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): - self.address = address - super(Ui_AddressBookWidgetItemLabel, self).__init__(label, acc_type) - - 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): - 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) - - -class AddressBookCompleter(QtGui.QCompleter): - """Addressbook completer""" - - def __init__(self): - super(AddressBookCompleter, self).__init__() - self.cursorPos = -1 - - def onCursorPositionChanged(self, oldPos, newPos): # pylint: disable=unused-argument - """Callback for cursor position change""" - 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()] - - 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') - - # If cursor position was saved, restore it, else save it - if self.cursorPos != -1: - self.widget().setCursorPosition(self.cursorPos) - else: - self.cursorPos = self.widget().cursorPosition() - - # Get current prosition - curIndex = self.widget().cursorPosition() - - # 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(";") - 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) - if nextDelimiterIndex == -1: - nextDelimiterIndex = len(text) - - # Get part of string that occurs before cursor - part1 = text[0:prevDelimiterIndex + 1] - - # Get string value from before auto finished string is selected - # pre = text[prevDelimiterIndex + 1:curIndex - 1] - - # Get part of string that occurs AFTER cursor - part2 = text[nextDelimiterIndex:] - - return part1 + autoString + part2 diff --git a/src/bitmessageqt/languagebox.py b/src/bitmessageqt/languagebox.py deleted file mode 100644 index 34f96b02..00000000 --- a/src/bitmessageqt/languagebox.py +++ /dev/null @@ -1,49 +0,0 @@ -"""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 - - -class LanguageBox(QtGui.QComboBox): - """LanguageBox class for Qt UI""" - 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.clear() - localesPath = os.path.join(paths.codePath(), 'translations') - 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] - if localeShort in LanguageBox.languageName: - self.addItem( - LanguageBox.languageName[localeShort], localeShort) - else: - locale = QtCore.QLocale(localeShort) - self.addItem( - locale.nativeLanguageName() or localeShort, localeShort) - - configuredLocale = config.safeGet( - 'bitmessagesettings', 'userlocale', "system") - for i in range(self.count()): - if self.itemData(i) == configuredLocale: - self.setCurrentIndex(i) - break diff --git a/src/bitmessageqt/messagecompose.py b/src/bitmessageqt/messagecompose.py deleted file mode 100644 index c51282f8..00000000 --- a/src/bitmessageqt/messagecompose.py +++ /dev/null @@ -1,37 +0,0 @@ -""" -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): - super(MessageCompose, self).__init__(parent) - self.setAcceptRichText(False) - 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 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) - ) - ) - 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 deleted file mode 100644 index 13ea16f9..00000000 --- a/src/bitmessageqt/messageview.py +++ /dev/null @@ -1,163 +0,0 @@ -""" -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 - - -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): - super(MessageView, self).__init__(parent) - self.mode = MessageView.MODE_PLAIN - self.html = None - self.setOpenExternalLinks(False) - self.setOpenLinks(False) - self.anchorClicked.connect(self.confirmURL) - self.out = "" - self.outpos = 0 - self.document().setUndoRedoEnabled(False) - 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: - if self.mode == MessageView.MODE_PLAIN: - self.showHTML() - else: - self.showPlain() - else: - 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: - 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) - - 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()) - if link.hasQueryItem("subject"): - window.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() - 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) - 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 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(): - 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 - 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 - 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 - 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() - self.html.allow_picture = True - self.html.feed(data) - self.html.close() - self.showPlain() diff --git a/src/bitmessageqt/migrationwizard.py b/src/bitmessageqt/migrationwizard.py deleted file mode 100644 index 6e80f1dc..00000000 --- a/src/bitmessageqt/migrationwizard.py +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env python2.7 -from PyQt4 import QtCore, QtGui - -class MigrationWizardIntroPage(QtGui.QWizardPage): - def __init__(self): - super(QtGui.QWizardPage, self).__init__() - self.setTitle("Migrating configuration") - - label = QtGui.QLabel("This wizard will help you to migrate your configuration. " - "You can still keep using PyBitMessage once you migrate, the changes are backwards compatible.") - label.setWordWrap(True) - - layout = QtGui.QVBoxLayout() - layout.addWidget(label) - self.setLayout(layout) - - def nextId(self): - return 1 - - -class MigrationWizardAddressesPage(QtGui.QWizardPage): - def __init__(self, addresses): - super(QtGui.QWizardPage, self).__init__() - self.setTitle("Addresses") - - label = QtGui.QLabel("Please select addresses that you are already using with mailchuck. ") - label.setWordWrap(True) - - layout = QtGui.QVBoxLayout() - layout.addWidget(label) - self.setLayout(layout) - - def nextId(self): - return 10 - - -class MigrationWizardGPUPage(QtGui.QWizardPage): - def __init__(self): - super(QtGui.QWizardPage, self).__init__() - self.setTitle("GPU") - - label = QtGui.QLabel("Are you using a GPU? ") - label.setWordWrap(True) - - layout = QtGui.QVBoxLayout() - layout.addWidget(label) - self.setLayout(layout) - - def nextId(self): - return 10 - - -class MigrationWizardConclusionPage(QtGui.QWizardPage): - def __init__(self): - super(QtGui.QWizardPage, self).__init__() - self.setTitle("All done!") - - label = QtGui.QLabel("You successfully migrated.") - label.setWordWrap(True) - - layout = QtGui.QVBoxLayout() - layout.addWidget(label) - self.setLayout(layout) - - -class Ui_MigrationWizard(QtGui.QWizard): - def __init__(self, addresses): - super(QtGui.QWizard, self).__init__() - - self.pages = {} - - page = MigrationWizardIntroPage() - self.setPage(0, page) - self.setStartId(0) - page = MigrationWizardAddressesPage(addresses) - self.setPage(1, page) - page = MigrationWizardGPUPage() - self.setPage(2, page) - page = MigrationWizardConclusionPage() - self.setPage(10, page) - - self.setWindowTitle("Migration from PyBitMessage wizard") - self.adjustSize() - self.show() \ No newline at end of file diff --git a/src/bitmessageqt/networkstatus.py b/src/bitmessageqt/networkstatus.py deleted file mode 100644 index e7fd9e94..00000000 --- a/src/bitmessageqt/networkstatus.py +++ /dev/null @@ -1,249 +0,0 @@ -""" -Network status tab widget definition. -""" - -import time - -from PyQt4 import QtCore, QtGui - -import l10n -import network.stats -import state -import widgets -from inventory import Inventory -from network import BMConnectionPool, knownnodes -from retranslateui import RetranslateMixin -from tr import _translate -from uisignaler import UISignaler - - -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.UISignalThread = UISignaler.get() - # pylint: disable=no-member - QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( - "updateNumberOfMessagesProcessed()"), self.updateNumberOfMessagesProcessed) - QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( - "updateNumberOfPubkeysProcessed()"), self.updateNumberOfPubkeysProcessed) - 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) - - self.timer = QtCore.QTimer() - - 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""" - 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", - ]: - 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())) - - 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)) - - 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)) - - 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)) - - 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()))) - - def updateNetworkStatusTab(self, outbound, add, destination): - """Add or remove an entry to the list of connected peers""" - # pylint: disable=too-many-branches,undefined-variable - if outbound: - try: - c = BMConnectionPool().outboundConnections[destination] - except KeyError: - if add: - return - else: - try: - c = BMConnectionPool().inboundConnections[destination] - except KeyError: - try: - c = BMConnectionPool().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) - 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 BMConnectionPool().inboundConnections: - self.window().setStatusIcon('yellow') - for i in range(self.tableWidgetConnectionCount.rowCount()): - if self.tableWidgetConnectionCount.item(i, 0).data(QtCore.Qt.UserRole).toPyObject() != destination: - continue - if self.tableWidgetConnectionCount.item(i, 1).data(QtCore.Qt.UserRole).toPyObject() == outbound: - self.tableWidgetConnectionCount.removeRow(i) - break - - self.tableWidgetConnectionCount.setUpdatesEnabled(True) - self.tableWidgetConnectionCount.setSortingEnabled(True) - self.labelTotalConnections.setText( - _translate( - "networkstatus", "Total Connections: %1").arg( - str(self.tableWidgetConnectionCount.rowCount()))) - # FYI: The 'singlelistener' thread sets the icon color to green when it - # receives an incoming connection, meaning that the user's firewall is - # configured correctly. - if self.tableWidgetConnectionCount.rowCount() and state.statusIconColor == 'red': - self.window().setStatusIcon('yellow') - elif self.tableWidgetConnectionCount.rowCount() == 0 and state.statusIconColor != "red": - self.window().setStatusIcon('red') - - # timer driven - def runEveryTwoSeconds(self): - """Updates counters, runs every 2 seconds if the timer is running""" - self.labelLookupsPerSecond.setText(_translate("networkstatus", "Inventory lookups per second: %1").arg( - str(Inventory().numberOfInventoryLookupsPerformed / 2))) - Inventory().numberOfInventoryLookupsPerformed = 0 - self.updateNumberOfBytes() - self.updateNumberOfObjectsToBeSynced() - - def retranslateUi(self): - """Conventional Qt Designer method for dynamic l10n""" - super(NetworkStatus, self).retranslateUi() - self.labelTotalConnections.setText( - _translate( - "networkstatus", "Total Connections: %1").arg( - str(self.tableWidgetConnectionCount.rowCount()))) - self.labelStartupTime.setText(_translate( - "networkstatus", "Since startup on %1" - ).arg(l10n.formatTimestamp(self.startup))) - self.updateNumberOfMessagesProcessed() - self.updateNumberOfBroadcastsProcessed() - self.updateNumberOfPubkeysProcessed() diff --git a/src/bitmessageqt/networkstatus.ui b/src/bitmessageqt/networkstatus.ui deleted file mode 100644 index e0c01b57..00000000 --- a/src/bitmessageqt/networkstatus.ui +++ /dev/null @@ -1,306 +0,0 @@ - - - networkstatus - - - - 0 - 0 - 602 - 254 - - - - - 0 - 0 - - - - - - - 20 - - - QLayout::SetNoConstraint - - - - - 20 - - - QLayout::SetMinimumSize - - - - - Total connections: - - - - - - - - - - - - 212 - 208 - 200 - - - - - - - - - 212 - 208 - 200 - - - - - - - - - 212 - 208 - 200 - - - - - - - - QFrame::Box - - - QFrame::Plain - - - QAbstractItemView::NoEditTriggers - - - false - - - false - - - QAbstractItemView::NoSelection - - - true - - - true - - - 80 - - - false - - - true - - - 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 - - - - - - - - - - 4 - - - QLayout::SetNoConstraint - - - - - - 0 - 9 - - - - - 0 - 50 - - - - Since startup: - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - - - - 0 - 0 - - - - Processed 0 person-to-person messages. - - - 0 - - - - - - - - 0 - 0 - - - - Processed 0 broadcasts. - - - - - - - - 0 - 0 - - - - Processed 0 public keys. - - - - - - - - 0 - 0 - - - - Objects to be synced: - - - - - - - - 0 - 0 - - - - Up: 0 kB/s - - - - - - - - 0 - 0 - - - - Down: 0 kB/s - - - - - - - - 0 - 0 - - - - Inventory lookups per second: 0 - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - - - - - - - - STableWidget - QTableWidget -
bitmessageqt/settingsmixin.h
-
-
- - - - -
diff --git a/src/bitmessageqt/newchandialog.py b/src/bitmessageqt/newchandialog.py deleted file mode 100644 index c0629cd7..00000000 --- a/src/bitmessageqt/newchandialog.py +++ /dev/null @@ -1,83 +0,0 @@ -""" -src/bitmessageqt/newchandialog.py -================================= - -""" - -from PyQt4 import QtCore, QtGui - -import widgets -from addresses import addBMIfNotPresent -from addressvalidator import AddressValidator, PassPhraseValidator -from queues import ( - addressGeneratorQueue, apiAddressGeneratorReturnQueue, UISignalQueue) -from tr import _translate -from utils import str_chan - - -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)) - - 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() - - def delayedUpdateStatus(self): - """Related to updating the UI for the chan passphrase validity""" - self.chanPassPhrase.validator().checkQueue() - - def accept(self): - """Proceed in joining the chan""" - self.timer.stop() - self.hide() - apiAddressGeneratorReturnQueue.queue.clear() - if self.chanAddress.text().toUtf8() == "": - addressGeneratorQueue.put( - ('createChan', 4, 1, str_chan + ' ' + str(self.chanPassPhrase.text().toUtf8()), - self.chanPassPhrase.text().toUtf8(), - True)) - 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 deleted file mode 100644 index 59dbb2bb..00000000 --- a/src/bitmessageqt/newchandialog.ui +++ /dev/null @@ -1,145 +0,0 @@ - - - newChanDialog - - - - 0 - 0 - 473 - 444 - - - - - 0 - 0 - - - - - 0 - 0 - - - - Create or join a chan - - - - - - - - - - 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> - - - true - - - - - - - - - - - - Chan passphrase/name: - - - - - - - Optional, for advanced usage - - - - - - - - 0 - 0 - - - - Chan address - - - - - - - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - false - - - - - - - - 0 - 0 - - - - Please input chan name/passphrase: - - - true - - - - - - - - - buttonBox - accepted() - newChanDialog - accept() - - - 240 - 372 - - - 236 - 221 - - - - - buttonBox - rejected() - newChanDialog - reject() - - - 240 - 372 - - - 236 - 221 - - - - - diff --git a/src/bitmessageqt/newsubscriptiondialog.ui b/src/bitmessageqt/newsubscriptiondialog.ui deleted file mode 100644 index ec67efa3..00000000 --- a/src/bitmessageqt/newsubscriptiondialog.ui +++ /dev/null @@ -1,123 +0,0 @@ - - - NewSubscriptionDialog - - - - 0 - 0 - 368 - 254 - - - - - 368 - 200 - - - - Add new entry - - - - - - Label - - - - - - - - - - Address - - - - - - - - - - - - - true - - - - - - - false - - - Enter an address above. - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - buttonBox - accepted() - NewSubscriptionDialog - accept() - - - 360 - 234 - - - 157 - 256 - - - - - buttonBox - rejected() - NewSubscriptionDialog - reject() - - - 360 - 240 - - - 286 - 256 - - - - - diff --git a/src/bitmessageqt/retranslateui.py b/src/bitmessageqt/retranslateui.py deleted file mode 100644 index c7676f77..00000000 --- a/src/bitmessageqt/retranslateui.py +++ /dev/null @@ -1,20 +0,0 @@ -from os import path -from PyQt4 import QtGui -from debug import logger -import widgets - -class RetranslateMixin(object): - def retranslateUi(self): - defaults = QtGui.QWidget() - widgets.load(self.__class__.__name__.lower() + '.ui', defaults) - for attr, value in defaults.__dict__.iteritems(): - setTextMethod = getattr(value, "setText", None) - if callable(setTextMethod): - 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()) - for i in range (value.rowCount()): - getattr(self, attr).verticalHeaderItem(i).setText( - getattr(defaults, attr).verticalHeaderItem(i).text()) diff --git a/src/bitmessageqt/safehtmlparser.py b/src/bitmessageqt/safehtmlparser.py deleted file mode 100644 index d408d2c7..00000000 --- a/src/bitmessageqt/safehtmlparser.py +++ /dev/null @@ -1,146 +0,0 @@ -"""Subclass of HTMLParser.HTMLParser for MessageView widget""" - -import inspect -import re -from HTMLParser import HTMLParser - -from urllib import 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]))+)' - ) - 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"" - self.has_html = False - 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: - return - self.sanitised += "<" - if inspect.stack()[1][3] == "handle_endtag": - self.sanitised += "/" - self.sanitised += tag - if attrs is not None: - for attr, val in attrs: - if tag == "img" and attr == "src" and not self.allow_picture: - val = "" - elif attr == "src" and not self.allow_external_src: - url = urlparse(val) - if url.scheme not in self.src_schemes: - val = "" - self.sanitised += " " + quote_plus(attr) - if val is not 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: - 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: - self.has_html = True - self.add_if_acceptable(tag, attrs) - - def handle_data(self, data): - self.sanitised += data - - def handle_charref(self, name): - self.sanitised += "&#" + name + ";" - - def handle_entityref(self, name): - self.sanitised += "&" + name + ";" - - 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) - self.raw += tmp - - def is_html(self, text=None, allow_picture=False): - """Detect if string contains HTML tags""" - if text: - self.reset() - self.reset_safe() - self.allow_picture = allow_picture - self.feed(text) - self.close() - return self.has_html diff --git a/src/bitmessageqt/settings.py b/src/bitmessageqt/settings.py deleted file mode 100644 index 6e0d4792..00000000 --- a/src/bitmessageqt/settings.py +++ /dev/null @@ -1,636 +0,0 @@ -""" -This module setting file is for settings -""" -import ConfigParser -import os -import sys -import tempfile - -import six -from PyQt4 import QtCore, QtGui - -import debug -import defaults -import namecoin -import openclpow -import paths -import queues -import state -import widgets -from bmconfigparser import config as config_obj -from helper_sql import sqlExecute, sqlStoredProcedure -from helper_startup import start_proxyconfig -from network import knownnodes, AnnounceThread -from network.asyncore_pollchoose import set_rates -from tr import _translate - - -def getSOCKSProxyType(config): - """Get user socksproxytype setting from *config*""" - try: - result = ConfigParser.SafeConfigParser.get( - config, 'bitmessagesettings', 'socksproxytype') - except (ConfigParser.NoSectionError, ConfigParser.NoOptionError): - return None - else: - if result.lower() in ('', 'none', 'false'): - result = None - return result - - -class SettingsDialog(QtGui.QDialog): - """The "Settings" dialog""" - def __init__(self, parent=None, firstrun=False): - super(SettingsDialog, self).__init__(parent) - widgets.load('settings.ui', self) - - self.parent = parent - self.firstrun = firstrun - self.config = config_obj - self.net_restart_needed = False - self.timer = QtCore.QTimer() - - if self.config.safeGetBoolean('bitmessagesettings', 'dontconnect'): - self.firstrun = False - try: - import pkg_resources - except ImportError: - pass - 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 - if not self.parent.tray.isSystemTrayAvailable(): - self.groupBoxTray.setEnabled(False) - self.groupBoxTray.setTitle(_translate( - "MainWindow", "Tray (not available in your system)")) - for setting in ( - 'minimizetotray', 'trayonclose', 'startintray'): - config.set('bitmessagesettings', setting, 'false') - else: - self.checkBoxMinimizeToTray.setChecked( - config.getboolean('bitmessagesettings', 'minimizetotray')) - self.checkBoxTrayOnClose.setChecked( - config.safeGetBoolean('bitmessagesettings', 'trayonclose')) - self.checkBoxStartInTray.setChecked( - config.getboolean('bitmessagesettings', 'startintray')) - - self.checkBoxHideTrayConnectionNotifications.setChecked( - config.getboolean( - 'bitmessagesettings', 'hidetrayconnectionnotifications')) - self.checkBoxShowTrayNotifications.setChecked( - config.getboolean('bitmessagesettings', 'showtraynotifications')) - - self.checkBoxStartOnLogon.setChecked( - config.getboolean('bitmessagesettings', 'startonlogon')) - - self.checkBoxWillinglySendToMobile.setChecked( - config.safeGetBoolean( - 'bitmessagesettings', 'willinglysendtomobile')) - self.checkBoxUseIdenticons.setChecked( - config.safeGetBoolean('bitmessagesettings', 'useidenticons')) - self.checkBoxReplyBelow.setChecked( - config.safeGetBoolean('bitmessagesettings', 'replybelow')) - - if state.appdata == paths.lookupExeFolder(): - self.checkBoxPortableMode.setChecked(True) - else: - try: - tempfile.NamedTemporaryFile( - dir=paths.lookupExeFolder(), delete=True - ).close() # should autodelete - except Exception: - self.checkBoxPortableMode.setDisabled(True) - - if 'darwin' in sys.platform: - self.checkBoxMinimizeToTray.setDisabled(True) - self.checkBoxMinimizeToTray.setText(_translate( - "MainWindow", - "Minimize-to-tray not yet supported on your OS.")) - self.checkBoxShowTrayNotifications.setDisabled(True) - self.checkBoxShowTrayNotifications.setText(_translate( - "MainWindow", - "Tray notifications not yet supported on your OS.")) - - if 'win' not in sys.platform and not self.parent.desktop: - self.checkBoxStartOnLogon.setDisabled(True) - self.checkBoxStartOnLogon.setText(_translate( - "MainWindow", "Start-on-login not yet supported on your OS.")) - - # On the Network settings tab: - self.lineEditTCPPort.setText(str( - config.get('bitmessagesettings', 'port'))) - self.checkBoxUPnP.setChecked( - config.safeGetBoolean('bitmessagesettings', 'upnp')) - self.checkBoxUDP.setChecked( - config.safeGetBoolean('bitmessagesettings', 'udp')) - self.checkBoxAuthentication.setChecked( - config.getboolean('bitmessagesettings', 'socksauthentication')) - self.checkBoxSocksListen.setChecked( - config.getboolean('bitmessagesettings', 'sockslisten')) - self.checkBoxOnionOnly.setChecked( - config.safeGetBoolean('bitmessagesettings', 'onionservicesonly')) - - self._proxy_type = getSOCKSProxyType(config) - self.comboBoxProxyType.setCurrentIndex( - 0 if not self._proxy_type - else self.comboBoxProxyType.findText(self._proxy_type)) - self.comboBoxProxyTypeChanged(self.comboBoxProxyType.currentIndex()) - - if self._proxy_type: - for node, info in six.iteritems( - knownnodes.knownNodes.get( - min(state.streamsInWhichIAmParticipating), []) - ): - if ( - node.host.endswith('.onion') and len(node.host) > 22 - and not info.get('self') - ): - break - else: - if self.checkBoxOnionOnly.isChecked(): - self.checkBoxOnionOnly.setText( - self.checkBoxOnionOnly.text() + ", " + _translate( - "MainWindow", "may cause connection problems!")) - self.checkBoxOnionOnly.setStyleSheet( - "QCheckBox { color : red; }") - else: - self.checkBoxOnionOnly.setEnabled(False) - - self.lineEditSocksHostname.setText( - config.get('bitmessagesettings', 'sockshostname')) - self.lineEditSocksPort.setText(str( - config.get('bitmessagesettings', 'socksport'))) - self.lineEditSocksUsername.setText( - config.get('bitmessagesettings', 'socksusername')) - self.lineEditSocksPassword.setText( - config.get('bitmessagesettings', 'sockspassword')) - - self.lineEditMaxDownloadRate.setText(str( - config.get('bitmessagesettings', 'maxdownloadrate'))) - self.lineEditMaxUploadRate.setText(str( - config.get('bitmessagesettings', 'maxuploadrate'))) - self.lineEditMaxOutboundConnections.setText(str( - config.get('bitmessagesettings', 'maxoutboundconnections'))) - - # Demanded difficulty tab - self.lineEditTotalDifficulty.setText(str((float( - config.getint( - 'bitmessagesettings', 'defaultnoncetrialsperbyte') - ) / defaults.networkDefaultProofOfWorkNonceTrialsPerByte))) - self.lineEditSmallMessageDifficulty.setText(str((float( - config.getint( - 'bitmessagesettings', 'defaultpayloadlengthextrabytes') - ) / defaults.networkDefaultPayloadLengthExtraBytes))) - - # Max acceptable difficulty tab - self.lineEditMaxAcceptableTotalDifficulty.setText(str((float( - config.getint( - 'bitmessagesettings', 'maxacceptablenoncetrialsperbyte') - ) / defaults.networkDefaultProofOfWorkNonceTrialsPerByte))) - self.lineEditMaxAcceptableSmallMessageDifficulty.setText(str((float( - config.getint( - 'bitmessagesettings', 'maxacceptablepayloadlengthextrabytes') - ) / defaults.networkDefaultPayloadLengthExtraBytes))) - - # OpenCL - self.comboBoxOpenCL.setEnabled(openclpow.openclAvailable()) - self.comboBoxOpenCL.clear() - self.comboBoxOpenCL.addItem("None") - self.comboBoxOpenCL.addItems(openclpow.vendors) - self.comboBoxOpenCL.setCurrentIndex(0) - for i in range(self.comboBoxOpenCL.count()): - if self.comboBoxOpenCL.itemText(i) == config.safeGet( - 'bitmessagesettings', 'opencl'): - self.comboBoxOpenCL.setCurrentIndex(i) - break - - # Namecoin integration tab - nmctype = config.get('bitmessagesettings', 'namecoinrpctype') - self.lineEditNamecoinHost.setText( - config.get('bitmessagesettings', 'namecoinrpchost')) - self.lineEditNamecoinPort.setText(str( - config.get('bitmessagesettings', 'namecoinrpcport'))) - self.lineEditNamecoinUser.setText( - config.get('bitmessagesettings', 'namecoinrpcuser')) - self.lineEditNamecoinPassword.setText( - config.get('bitmessagesettings', 'namecoinrpcpassword')) - - if nmctype == "namecoind": - self.radioButtonNamecoinNamecoind.setChecked(True) - elif nmctype == "nmcontrol": - self.radioButtonNamecoinNmcontrol.setChecked(True) - self.lineEditNamecoinUser.setEnabled(False) - self.labelNamecoinUser.setEnabled(False) - self.lineEditNamecoinPassword.setEnabled(False) - self.labelNamecoinPassword.setEnabled(False) - else: - assert False - - # Message Resend tab - self.lineEditDays.setText(str( - config.get('bitmessagesettings', 'stopresendingafterxdays'))) - self.lineEditMonths.setText(str( - config.get('bitmessagesettings', 'stopresendingafterxmonths'))) - - def comboBoxProxyTypeChanged(self, comboBoxIndex): - """A callback for currentIndexChanged event of comboBoxProxyType""" - if comboBoxIndex == 0: - self.lineEditSocksHostname.setEnabled(False) - self.lineEditSocksPort.setEnabled(False) - self.lineEditSocksUsername.setEnabled(False) - self.lineEditSocksPassword.setEnabled(False) - self.checkBoxAuthentication.setEnabled(False) - self.checkBoxSocksListen.setEnabled(False) - self.checkBoxOnionOnly.setEnabled(False) - else: - self.lineEditSocksHostname.setEnabled(True) - self.lineEditSocksPort.setEnabled(True) - self.checkBoxAuthentication.setEnabled(True) - self.checkBoxSocksListen.setEnabled(True) - self.checkBoxOnionOnly.setEnabled(True) - if self.checkBoxAuthentication.isChecked(): - self.lineEditSocksUsername.setEnabled(True) - self.lineEditSocksPassword.setEnabled(True) - - def getNamecoinType(self): - """ - Check status of namecoin integration radio buttons - and translate it to a string as in the options. - """ - if self.radioButtonNamecoinNamecoind.isChecked(): - return "namecoind" - if self.radioButtonNamecoinNmcontrol.isChecked(): - return "nmcontrol" - assert False - - # Namecoin connection type was changed. - def namecoinTypeChanged(self, checked): # pylint: disable=unused-argument - """A callback for toggled event of radioButtonNamecoinNamecoind""" - nmctype = self.getNamecoinType() - assert nmctype == "namecoind" or nmctype == "nmcontrol" - - isNamecoind = (nmctype == "namecoind") - self.lineEditNamecoinUser.setEnabled(isNamecoind) - self.labelNamecoinUser.setEnabled(isNamecoind) - self.lineEditNamecoinPassword.setEnabled(isNamecoind) - self.labelNamecoinPassword.setEnabled(isNamecoind) - - if isNamecoind: - self.lineEditNamecoinPort.setText(defaults.namecoinDefaultRpcPort) - else: - self.lineEditNamecoinPort.setText("9000") - - def click_pushButtonNamecoinTest(self): - """Test the namecoin settings specified in the settings dialog.""" - self.labelNamecoinTestResult.setText( - _translate("MainWindow", "Testing...")) - nc = namecoin.namecoinConnection({ - 'type': self.getNamecoinType(), - 'host': str(self.lineEditNamecoinHost.text().toUtf8()), - 'port': str(self.lineEditNamecoinPort.text().toUtf8()), - 'user': str(self.lineEditNamecoinUser.text().toUtf8()), - 'password': str(self.lineEditNamecoinPassword.text().toUtf8()) - }) - status, text = nc.test() - self.labelNamecoinTestResult.setText(text) - if status == 'success': - self.parent.namecoin = nc - - def accept(self): - """A callback for accepted event of buttonBox (OK button pressed)""" - # pylint: disable=too-many-branches,too-many-statements - super(SettingsDialog, self).accept() - if self.firstrun: - self.config.remove_option('bitmessagesettings', 'dontconnect') - self.config.set('bitmessagesettings', 'startonlogon', str( - self.checkBoxStartOnLogon.isChecked())) - self.config.set('bitmessagesettings', 'minimizetotray', str( - self.checkBoxMinimizeToTray.isChecked())) - self.config.set('bitmessagesettings', 'trayonclose', str( - self.checkBoxTrayOnClose.isChecked())) - self.config.set( - 'bitmessagesettings', 'hidetrayconnectionnotifications', - str(self.checkBoxHideTrayConnectionNotifications.isChecked())) - self.config.set('bitmessagesettings', 'showtraynotifications', str( - self.checkBoxShowTrayNotifications.isChecked())) - self.config.set('bitmessagesettings', 'startintray', str( - self.checkBoxStartInTray.isChecked())) - self.config.set('bitmessagesettings', 'willinglysendtomobile', str( - self.checkBoxWillinglySendToMobile.isChecked())) - self.config.set('bitmessagesettings', 'useidenticons', str( - self.checkBoxUseIdenticons.isChecked())) - self.config.set('bitmessagesettings', 'replybelow', str( - self.checkBoxReplyBelow.isChecked())) - - lang = str(self.languageComboBox.itemData( - self.languageComboBox.currentIndex()).toString()) - self.config.set('bitmessagesettings', 'userlocale', lang) - self.parent.change_translation() - - if int(self.config.get('bitmessagesettings', 'port')) != int( - self.lineEditTCPPort.text()): - self.config.set( - 'bitmessagesettings', 'port', str(self.lineEditTCPPort.text())) - if not self.config.safeGetBoolean( - 'bitmessagesettings', 'dontconnect'): - self.net_restart_needed = True - - if self.checkBoxUPnP.isChecked() != self.config.safeGetBoolean( - 'bitmessagesettings', 'upnp'): - self.config.set( - 'bitmessagesettings', 'upnp', - str(self.checkBoxUPnP.isChecked())) - if self.checkBoxUPnP.isChecked(): - import upnp - upnpThread = upnp.uPnPThread() - upnpThread.start() - - udp_enabled = self.checkBoxUDP.isChecked() - if udp_enabled != self.config.safeGetBoolean( - 'bitmessagesettings', 'udp'): - self.config.set('bitmessagesettings', 'udp', str(udp_enabled)) - if udp_enabled: - announceThread = AnnounceThread() - announceThread.daemon = True - announceThread.start() - else: - try: - state.announceThread.stopThread() - except AttributeError: - pass - - proxytype_index = self.comboBoxProxyType.currentIndex() - if proxytype_index == 0: - if self._proxy_type and state.statusIconColor != 'red': - self.net_restart_needed = True - elif state.statusIconColor == 'red' and self.config.safeGetBoolean( - 'bitmessagesettings', 'dontconnect'): - self.net_restart_needed = False - elif self.comboBoxProxyType.currentText() != self._proxy_type: - self.net_restart_needed = True - self.parent.statusbar.clearMessage() - - self.config.set( - 'bitmessagesettings', 'socksproxytype', - 'none' if self.comboBoxProxyType.currentIndex() == 0 - else str(self.comboBoxProxyType.currentText()) - ) - if proxytype_index > 2: # last literal proxytype in ui - start_proxyconfig() - - self.config.set('bitmessagesettings', 'socksauthentication', str( - self.checkBoxAuthentication.isChecked())) - self.config.set('bitmessagesettings', 'sockshostname', str( - self.lineEditSocksHostname.text())) - self.config.set('bitmessagesettings', 'socksport', str( - self.lineEditSocksPort.text())) - self.config.set('bitmessagesettings', 'socksusername', str( - self.lineEditSocksUsername.text())) - self.config.set('bitmessagesettings', 'sockspassword', str( - self.lineEditSocksPassword.text())) - self.config.set('bitmessagesettings', 'sockslisten', str( - self.checkBoxSocksListen.isChecked())) - if ( - self.checkBoxOnionOnly.isChecked() - and not self.config.safeGetBoolean( - 'bitmessagesettings', 'onionservicesonly') - ): - self.net_restart_needed = True - self.config.set('bitmessagesettings', 'onionservicesonly', str( - self.checkBoxOnionOnly.isChecked())) - try: - # Rounding to integers just for aesthetics - self.config.set('bitmessagesettings', 'maxdownloadrate', str( - int(float(self.lineEditMaxDownloadRate.text())))) - self.config.set('bitmessagesettings', 'maxuploadrate', str( - int(float(self.lineEditMaxUploadRate.text())))) - except ValueError: - QtGui.QMessageBox.about( - self, _translate("MainWindow", "Number needed"), - _translate( - "MainWindow", - "Your maximum download and upload rate must be numbers." - " Ignoring what you typed.") - ) - else: - set_rates( - self.config.safeGetInt('bitmessagesettings', 'maxdownloadrate'), - self.config.safeGetInt('bitmessagesettings', 'maxuploadrate')) - - self.config.set('bitmessagesettings', 'maxoutboundconnections', str( - int(float(self.lineEditMaxOutboundConnections.text())))) - - self.config.set( - 'bitmessagesettings', 'namecoinrpctype', self.getNamecoinType()) - self.config.set('bitmessagesettings', 'namecoinrpchost', str( - self.lineEditNamecoinHost.text())) - self.config.set('bitmessagesettings', 'namecoinrpcport', str( - self.lineEditNamecoinPort.text())) - self.config.set('bitmessagesettings', 'namecoinrpcuser', str( - self.lineEditNamecoinUser.text())) - self.config.set('bitmessagesettings', 'namecoinrpcpassword', str( - self.lineEditNamecoinPassword.text())) - self.parent.resetNamecoinConnection() - - # Demanded difficulty tab - if float(self.lineEditTotalDifficulty.text()) >= 1: - self.config.set( - 'bitmessagesettings', 'defaultnoncetrialsperbyte', - str(int( - float(self.lineEditTotalDifficulty.text()) - * defaults.networkDefaultProofOfWorkNonceTrialsPerByte))) - if float(self.lineEditSmallMessageDifficulty.text()) >= 1: - self.config.set( - 'bitmessagesettings', 'defaultpayloadlengthextrabytes', - str(int( - float(self.lineEditSmallMessageDifficulty.text()) - * defaults.networkDefaultPayloadLengthExtraBytes))) - - if self.comboBoxOpenCL.currentText().toUtf8() != self.config.safeGet( - 'bitmessagesettings', 'opencl'): - self.config.set( - 'bitmessagesettings', 'opencl', - str(self.comboBoxOpenCL.currentText())) - queues.workerQueue.put(('resetPoW', '')) - - acceptableDifficultyChanged = False - - if ( - float(self.lineEditMaxAcceptableTotalDifficulty.text()) >= 1 - or float(self.lineEditMaxAcceptableTotalDifficulty.text()) == 0 - ): - if self.config.get( - 'bitmessagesettings', 'maxacceptablenoncetrialsperbyte' - ) != str(int( - float(self.lineEditMaxAcceptableTotalDifficulty.text()) - * defaults.networkDefaultProofOfWorkNonceTrialsPerByte)): - # the user changed the max acceptable total difficulty - acceptableDifficultyChanged = True - self.config.set( - 'bitmessagesettings', 'maxacceptablenoncetrialsperbyte', - str(int( - float(self.lineEditMaxAcceptableTotalDifficulty.text()) - * defaults.networkDefaultProofOfWorkNonceTrialsPerByte)) - ) - if ( - float(self.lineEditMaxAcceptableSmallMessageDifficulty.text()) >= 1 - or float(self.lineEditMaxAcceptableSmallMessageDifficulty.text()) == 0 - ): - if self.config.get( - 'bitmessagesettings', 'maxacceptablepayloadlengthextrabytes' - ) != str(int( - float(self.lineEditMaxAcceptableSmallMessageDifficulty.text()) - * defaults.networkDefaultPayloadLengthExtraBytes)): - # the user changed the max acceptable small message difficulty - acceptableDifficultyChanged = True - self.config.set( - 'bitmessagesettings', 'maxacceptablepayloadlengthextrabytes', - str(int( - float(self.lineEditMaxAcceptableSmallMessageDifficulty.text()) - * defaults.networkDefaultPayloadLengthExtraBytes)) - ) - if acceptableDifficultyChanged: - # It might now be possible to send msgs which were previously - # marked as toodifficult. Let us change them to 'msgqueued'. - # The singleWorker will try to send them and will again mark - # them as toodifficult if the receiver's required difficulty - # is still higher than we are willing to do. - sqlExecute( - "UPDATE sent SET status='msgqueued'" - " WHERE status='toodifficult'") - queues.workerQueue.put(('sendmessage', '')) - - stopResendingDefaults = False - - # UI setting to stop trying to send messages after X days/months - # I'm open to changing this UI to something else if someone has a better idea. - if self.lineEditDays.text() == '' and self.lineEditMonths.text() == '': - # We need to handle this special case. Bitmessage has its - # default behavior. The input is blank/blank - self.config.set('bitmessagesettings', 'stopresendingafterxdays', '') - self.config.set('bitmessagesettings', 'stopresendingafterxmonths', '') - state.maximumLengthOfTimeToBotherResendingMessages = float('inf') - stopResendingDefaults = True - - try: - days = float(self.lineEditDays.text()) - except ValueError: - self.lineEditDays.setText("0") - days = 0.0 - try: - months = float(self.lineEditMonths.text()) - except ValueError: - self.lineEditMonths.setText("0") - months = 0.0 - - if days >= 0 and months >= 0 and not stopResendingDefaults: - state.maximumLengthOfTimeToBotherResendingMessages = \ - days * 24 * 60 * 60 + months * 60 * 60 * 24 * 365 / 12 - if state.maximumLengthOfTimeToBotherResendingMessages < 432000: - # If the time period is less than 5 hours, we give - # zero values to all fields. No message will be sent again. - QtGui.QMessageBox.about( - self, - _translate("MainWindow", "Will not resend ever"), - _translate( - "MainWindow", - "Note that the time limit you entered is less" - " than the amount of time Bitmessage waits for" - " the first resend attempt therefore your" - " messages will never be resent.") - ) - self.config.set( - 'bitmessagesettings', 'stopresendingafterxdays', '0') - self.config.set( - 'bitmessagesettings', 'stopresendingafterxmonths', '0') - state.maximumLengthOfTimeToBotherResendingMessages = 0.0 - else: - self.config.set( - 'bitmessagesettings', 'stopresendingafterxdays', str(days)) - self.config.set( - 'bitmessagesettings', 'stopresendingafterxmonths', - str(months)) - - self.config.save() - - if self.net_restart_needed: - self.net_restart_needed = False - self.config.setTemp('bitmessagesettings', 'dontconnect', 'true') - self.timer.singleShot( - 5000, lambda: - self.config.setTemp( - 'bitmessagesettings', 'dontconnect', 'false') - ) - - self.parent.updateStartOnLogon() - - if ( - state.appdata != paths.lookupExeFolder() - and self.checkBoxPortableMode.isChecked() - ): - # If we are NOT using portable mode now but the user selected - # that we should... - # Write the keys.dat file to disk in the new location - sqlStoredProcedure('movemessagstoprog') - with open(paths.lookupExeFolder() + 'keys.dat', 'wb') as configfile: - self.config.write(configfile) - # Write the knownnodes.dat file to disk in the new location - knownnodes.saveKnownNodes(paths.lookupExeFolder()) - os.remove(state.appdata + 'keys.dat') - os.remove(state.appdata + 'knownnodes.dat') - previousAppdataLocation = state.appdata - state.appdata = paths.lookupExeFolder() - debug.resetLogging() - try: - os.remove(previousAppdataLocation + 'debug.log') - os.remove(previousAppdataLocation + 'debug.log.1') - except Exception: - pass - - if ( - state.appdata == paths.lookupExeFolder() - and not self.checkBoxPortableMode.isChecked() - ): - # If we ARE using portable mode now but the user selected - # that we shouldn't... - state.appdata = paths.lookupAppdataFolder() - if not os.path.exists(state.appdata): - os.makedirs(state.appdata) - sqlStoredProcedure('movemessagstoappdata') - # Write the keys.dat file to disk in the new location - self.config.save() - # Write the knownnodes.dat file to disk in the new location - knownnodes.saveKnownNodes(state.appdata) - os.remove(paths.lookupExeFolder() + 'keys.dat') - os.remove(paths.lookupExeFolder() + 'knownnodes.dat') - debug.resetLogging() - try: - os.remove(paths.lookupExeFolder() + 'debug.log') - os.remove(paths.lookupExeFolder() + 'debug.log.1') - except Exception: - pass diff --git a/src/bitmessageqt/settings.ui b/src/bitmessageqt/settings.ui deleted file mode 100644 index 1e9a6f09..00000000 --- a/src/bitmessageqt/settings.ui +++ /dev/null @@ -1,1206 +0,0 @@ - - - settingsDialog - - - - 0 - 0 - 521 - 413 - - - - Settings - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - 0 - - - - true - - - User Interface - - - - 8 - - - 8 - - - 8 - - - 8 - - - - - Start Bitmessage on user login - - - - - - - Tray - - - - - - Start Bitmessage in the tray (don't show main window) - - - - - - - Minimize to tray - - - false - - - - - - - Close to tray - - - - - - - - - - Hide connection notifications - - - - - - - Show notification when message received - - - - - - - Run in Portable Mode - - - - - - - - 0 - 0 - - - - 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. - - - true - - - - - - - Willingly include unencrypted destination address when sending to a mobile device - - - - - - - Use Identicons - - - - - - - Reply below Quote - - - - - - - Interface Language - - - - - - - 100 - 0 - - - - - - - - - - - - Network Settings - - - - 8 - - - 8 - - - 8 - - - 8 - - - - - Listening port - - - - - - Listen for connections on port: - - - - - - - - 70 - 16777215 - - - - - - - - UPnP - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - Bandwidth limit - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Maximum download rate (kB/s): [0: unlimited] - - - - - - - - 0 - 0 - - - - - 60 - 16777215 - - - - - - - - Maximum upload rate (kB/s): [0: unlimited] - - - - - - - - 0 - 0 - - - - - 60 - 16777215 - - - - - - - - Maximum outbound connections: [0: none] - - - - - - - - 0 - 0 - - - - - 60 - 16777215 - - - - - - - - - - - Proxy server / Tor - - - - - - Type: - - - - - - - Server hostname: - - - - - - - - - - Port: - - - - - - - - - - Authentication - - - - - - - Username: - - - - - - - false - - - - - - - Pass: - - - - - - - false - - - Qt::ImhHiddenText|Qt::ImhNoAutoUppercase|Qt::ImhNoPredictiveText - - - QLineEdit::Password - - - - - - - Listen for incoming connections when using proxy - - - - - - - Only connect to onion services (*.onion) - - - - - - - - none - - - - - SOCKS4a - - - - - SOCKS5 - - - - - - - - - - - Announce self by UDP - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - Demanded difficulty - - - - 8 - - - 8 - - - 8 - - - 8 - - - - - Total difficulty: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - The 'Total difficulty' affects the absolute amount of work the sender must complete. Doubling this value doubles the amount of work. - - - true - - - - - - - Small message difficulty: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - 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. - - - true - - - - - - - Qt::Horizontal - - - - 203 - 20 - - - - - - - - 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. - - - true - - - - - - - - 0 - 0 - - - - - 70 - 16777215 - - - - - - - - - 0 - 0 - - - - - 70 - 16777215 - - - - - - - - Qt::Horizontal - - - - 203 - 20 - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - Max acceptable difficulty - - - - 8 - - - 8 - - - 8 - - - 8 - - - - - 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. - - - true - - - - - - - Qt::Horizontal - - - - 102 - 20 - - - - - - - - Qt::LeftToRight - - - Maximum acceptable total difficulty: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - 70 - 16777215 - - - - - - - - Qt::Horizontal - - - - 102 - 20 - - - - - - - - Maximum acceptable small message difficulty: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - 70 - 16777215 - - - - - - - - Qt::Vertical - - - - 20 - 147 - - - - - - - - - - Hardware GPU acceleration (OpenCL): - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - Namecoin integration - - - - 8 - - - 8 - - - 8 - - - 8 - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - <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> - - - true - - - - - - - Host: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - true - - - Port: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Username: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Password: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Qt::ImhHiddenText|Qt::ImhNoAutoUppercase|Qt::ImhNoPredictiveText - - - QLineEdit::Password - - - - - - - - - - - - - - Test - - - - - - - - - Connect to: - - - - - - - Namecoind - - - - - - - NMControl - - - - - - - - - - Resends Expire - - - - 8 - - - 8 - - - 8 - - - 8 - - - - - <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> - - - true - - - - - - - Qt::Horizontal - - - - 212 - 20 - - - - - - - - - 231 - 75 - - - - - - - Give up after - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - and - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 55 - 100 - - - - - - - - - 55 - 100 - - - - - - - - days - - - - - - - months. - - - - - - - - - - Qt::Vertical - - - - 20 - 129 - - - - - - - - - - - - - LanguageBox - QComboBox -
bitmessageqt.languagebox
-
-
- - tabWidgetSettings - checkBoxStartOnLogon - checkBoxStartInTray - checkBoxMinimizeToTray - lineEditTCPPort - comboBoxProxyType - lineEditSocksHostname - lineEditSocksPort - checkBoxAuthentication - lineEditSocksUsername - lineEditSocksPassword - checkBoxSocksListen - buttonBox - - - - - - - buttonBox - accepted() - settingsDialog - accept() - - - 257 - 330 - - - 157 - 274 - - - - - buttonBox - rejected() - settingsDialog - reject() - - - 325 - 330 - - - 286 - 274 - - - - - checkBoxAuthentication - toggled(bool) - lineEditSocksUsername - setEnabled(bool) - - - 125 - 190 - - - 233 - 189 - - - - - checkBoxAuthentication - toggled(bool) - lineEditSocksPassword - setEnabled(bool) - - - 79 - 190 - - - 370 - 192 - - - - - comboBoxProxyType - currentIndexChanged(int) - settingsDialog - comboBoxProxyTypeChanged - - - 20 - 20 - - - 20 - 20 - - - - - radioButtonNamecoinNamecoind - toggled(bool) - settingsDialog - namecoinTypeChanged - - - 20 - 20 - - - 20 - 20 - - - - - pushButtonNamecoinTest - clicked() - settingsDialog - click_pushButtonNamecoinTest - - - 20 - 20 - - - 20 - 20 - - - - -
diff --git a/src/bitmessageqt/settingsmixin.py b/src/bitmessageqt/settingsmixin.py deleted file mode 100644 index 3d5999e2..00000000 --- a/src/bitmessageqt/settingsmixin.py +++ /dev/null @@ -1,108 +0,0 @@ -#!/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 - 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: - 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: - 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) - pass - - def saveSettings(self): - """Save tree settings""" - # 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/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 deleted file mode 100644 index a84affa4..00000000 --- a/src/bitmessageqt/support.py +++ /dev/null @@ -1,163 +0,0 @@ -"""Composing support request message functions.""" -# pylint: disable=no-member - -import ctypes -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 foldertree import AccountMixin -from helper_sql import sqlExecute, sqlQuery -from l10n import getTranslationLanguage -from openclpow import openclEnabled -from pyelliptic.openssl import OpenSSL -from settings import getSOCKSProxyType -from version import softwareVersion -from tr import _translate - - -# this is BM support address going to Peter Surda -OLD_SUPPORT_ADDRESS = 'BM-2cTkCtMYkrSPwFTpgcBrMrf5d8oZwvMZWK' -SUPPORT_ADDRESS = 'BM-2cUdgkDDAahwPAU6oD2A7DnjqZz3hgY832' -SUPPORT_LABEL = _translate("Support", "PyBitmessage support") -SUPPORT_MY_LABEL = _translate("Support", "My new address") -SUPPORT_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. - -Please describe what you are trying to do: - -Please describe what you expect to happen: - -Please describe what happens instead: - - -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Please write above this line and if possible, keep the information about your \ -environment below intact. - -PyBitmessage version: {} -Operating system: {} -Architecture: {}bit -Python Version: {} -OpenSSL Version: {} -Frozen: {} -Portable mode: {} -C PoW: {} -OpenCL PoW: {} -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) - if queryreturn == []: - sqlExecute( - 'INSERT INTO addressbook VALUES (?,?)', - SUPPORT_LABEL.toUtf8(), SUPPORT_ADDRESS) - myapp.rerenderAddressBook() - - -def checkHasNormalAddress(): - for address in config.addresses(): - acct = account.accountClass(address) - if acct.type == AccountMixin.NORMAL and config.safeGetBoolean(address, 'enabled'): - return address - return False - - -def createAddressIfNeeded(myapp): - if not checkHasNormalAddress(): - queues.addressGeneratorQueue.put(( - 'createRandomAddress', 4, 1, - str(SUPPORT_MY_LABEL.toUtf8()), - 1, "", False, - defaults.networkDefaultProofOfWorkNonceTrialsPerByte, - defaults.networkDefaultPayloadLengthExtraBytes - )) - while state.shutdown == 0 and not checkHasNormalAddress(): - time.sleep(.2) - myapp.rerenderComboBoxSendFrom() - return checkHasNormalAddress() - - -def createSupportMessage(myapp): - checkAddressBook(myapp) - address = createAddressIfNeeded(myapp) - if state.shutdown: - return - - myapp.ui.lineEditSubject.setText(SUPPORT_SUBJECT) - addrIndex = myapp.ui.comboBoxSendFrom.findData( - address, QtCore.Qt.UserRole, - QtCore.Qt.MatchFixedString | QtCore.Qt.MatchCaseSensitive) - if addrIndex == -1: # something is very wrong - return - myapp.ui.comboBoxSendFrom.setCurrentIndex(addrIndex) - myapp.ui.lineEditTo.setText(SUPPORT_ADDRESS) - - version = softwareVersion - commit = paths.lastCommit().get('commit') - if commit: - version += " GIT " + commit - - os = sys.platform - if os == "win32": - windowsversion = sys.getwindowsversion() - os = "Windows " + str(windowsversion[0]) + "." + str(windowsversion[1]) - else: - try: - from os import uname - unixversion = uname() - os = unixversion[0] + " " + unixversion[2] - except: - 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) - - 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" - locale = getTranslationLanguage() - socks = getSOCKSProxyType(config) or "N/A" - upnp = config.safeGet('bitmessagesettings', 'upnp', "N/A") - connectedhosts = len(network.stats.connectedHostsList()) - - myapp.ui.textEditMessage.setText(unicode(SUPPORT_MESSAGE, 'utf-8').format( - version, os, architecture, pythonversion, opensslversion, frozen, - portablemode, cpow, openclpow, locale, socks, upnp, connectedhosts)) - - # single msg tab - myapp.ui.tabWidgetSend.setCurrentIndex( - myapp.ui.tabWidgetSend.indexOf(myapp.ui.sendDirect) - ) - # send tab - myapp.ui.tabWidget.setCurrentIndex( - myapp.ui.tabWidget.indexOf(myapp.ui.send) - ) diff --git a/src/bitmessageqt/tests/__init__.py b/src/bitmessageqt/tests/__init__.py deleted file mode 100644 index a542abdc..00000000 --- a/src/bitmessageqt/tests/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -"""bitmessageqt tests""" - -from addressbook import TestAddressbook -from main import TestMain, TestUISignaler -from settings import TestSettings -from support import TestSupport - -__all__ = [ - "TestAddressbook", "TestMain", "TestSettings", "TestSupport", - "TestUISignaler" -] diff --git a/src/bitmessageqt/tests/addressbook.py b/src/bitmessageqt/tests/addressbook.py deleted file mode 100644 index cd86c5d6..00000000 --- a/src/bitmessageqt/tests/addressbook.py +++ /dev/null @@ -1,17 +0,0 @@ -import helper_addressbook -from bitmessageqt.support import createAddressIfNeeded - -from main import TestBase - - -class TestAddressbook(TestBase): - """Test case for addressbook""" - - def test_add_own_address_to_addressbook(self): - """Checking own address adding in addressbook""" - try: - address = createAddressIfNeeded(self.window) - self.assertFalse( - helper_addressbook.insert(label='test', address=address)) - except IndexError: - self.fail("Can't generate addresses") diff --git a/src/bitmessageqt/tests/main.py b/src/bitmessageqt/tests/main.py deleted file mode 100644 index b3aa67fa..00000000 --- a/src/bitmessageqt/tests/main.py +++ /dev/null @@ -1,60 +0,0 @@ -"""Common definitions for bitmessageqt tests""" - -import Queue -import sys -import unittest - -from PyQt4 import QtCore, QtGui - -import bitmessageqt -import queues -from tr import _translate - - -class TestBase(unittest.TestCase): - """Base class for bitmessageqt test case""" - - def setUp(self): - self.app = ( - QtGui.QApplication.instance() - or bitmessageqt.BitmessageQtApplication(sys.argv)) - self.window = self.app.activeWindow() - if not self.window: - self.window = bitmessageqt.MyForm() - self.window.appIndicatorInit(self.app) - - def tearDown(self): - # self.app.deleteLater() - while True: - try: - thread, exc = queues.excQueue.get(block=False) - except Queue.Empty: - return - if thread == 'tests': - self.fail('Exception in the main thread: %s' % exc) - - -class TestMain(unittest.TestCase): - """Test case for main window - basic features""" - - def test_translate(self): - """Check the results of _translate() with various args""" - self.assertIsInstance( - _translate("MainWindow", "Test"), - QtCore.QString - ) - - -class TestUISignaler(TestBase): - """Test case for UISignalQueue""" - - def test_updateStatusBar(self): - """Check arguments order of updateStatusBar command""" - queues.UISignalQueue.put(( - 'updateStatusBar', ( - _translate("test", "Testing updateStatusBar..."), 1) - )) - - QtCore.QTimer.singleShot(60, self.app.quit) - self.app.exec_() - # self.app.processEvents(QtCore.QEventLoop.AllEvents, 60) diff --git a/src/bitmessageqt/tests/settings.py b/src/bitmessageqt/tests/settings.py deleted file mode 100644 index 0dcf8cf3..00000000 --- a/src/bitmessageqt/tests/settings.py +++ /dev/null @@ -1,34 +0,0 @@ -import threading -import time - -from main import TestBase -from bmconfigparser import config -from bitmessageqt import settings - - -class TestSettings(TestBase): - """A test case for the "Settings" dialog""" - def setUp(self): - super(TestSettings, self).setUp() - self.dialog = settings.SettingsDialog(self.window) - - def test_udp(self): - """Test the effect of checkBoxUDP""" - udp_setting = config.safeGetBoolean( - 'bitmessagesettings', 'udp') - self.assertEqual(udp_setting, self.dialog.checkBoxUDP.isChecked()) - self.dialog.checkBoxUDP.setChecked(not udp_setting) - self.dialog.accept() - self.assertEqual( - not udp_setting, - config.safeGetBoolean('bitmessagesettings', 'udp')) - time.sleep(5) - for thread in threading.enumerate(): - if thread.name == 'Announcer': # find Announcer thread - if udp_setting: - self.fail( - 'Announcer thread is running while udp set to False') - break - else: - if not udp_setting: - self.fail('No Announcer thread found while udp set to True') diff --git a/src/bitmessageqt/tests/support.py b/src/bitmessageqt/tests/support.py deleted file mode 100644 index ba28b73a..00000000 --- a/src/bitmessageqt/tests/support.py +++ /dev/null @@ -1,33 +0,0 @@ -# from PyQt4 import QtTest - -import sys - -from shared import isAddressInMyAddressBook - -from main import TestBase - - -class TestSupport(TestBase): - """A test case for support module""" - SUPPORT_ADDRESS = 'BM-2cUdgkDDAahwPAU6oD2A7DnjqZz3hgY832' - SUPPORT_SUBJECT = 'Support request' - - def test(self): - """trigger menu action "Contact Support" and check the result""" - ui = self.window.ui - self.assertEqual(ui.lineEditTo.text(), '') - self.assertEqual(ui.lineEditSubject.text(), '') - - ui.actionSupport.trigger() - - self.assertTrue( - isAddressInMyAddressBook(self.SUPPORT_ADDRESS)) - - self.assertEqual( - ui.tabWidget.currentIndex(), ui.tabWidget.indexOf(ui.send)) - self.assertEqual( - ui.lineEditTo.text(), self.SUPPORT_ADDRESS) - self.assertEqual( - ui.lineEditSubject.text(), self.SUPPORT_SUBJECT) - self.assertIn( - sys.version, ui.textEditMessage.toPlainText()) diff --git a/src/bitmessageqt/uisignaler.py b/src/bitmessageqt/uisignaler.py deleted file mode 100644 index c23ec3bc..00000000 --- a/src/bitmessageqt/uisignaler.py +++ /dev/null @@ -1,90 +0,0 @@ - -from PyQt4.QtCore import QThread, SIGNAL -import sys - -import queues - - -class UISignaler(QThread): - _instance = None - - def __init__(self, parent=None): - QThread.__init__(self, parent) - - @classmethod - def get(cls): - if not cls._instance: - cls._instance = UISignaler() - return cls._instance - - def run(self): - while True: - command, data = queues.UISignalQueue.get() - if command == 'writeNewAddressToTable': - label, address, streamNumber = data - self.emit( - SIGNAL("writeNewAddressToTable(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), - label, - address, - str(streamNumber)) - elif command == 'updateStatusBar': - self.emit(SIGNAL("updateStatusBar(PyQt_PyObject)"), data) - elif command == 'updateSentItemStatusByToAddress': - toAddress, message = data - self.emit(SIGNAL( - "updateSentItemStatusByToAddress(PyQt_PyObject,PyQt_PyObject)"), toAddress, message) - elif command == 'updateSentItemStatusByAckdata': - ackData, message = data - self.emit(SIGNAL( - "updateSentItemStatusByAckdata(PyQt_PyObject,PyQt_PyObject)"), ackData, message) - elif command == 'displayNewInboxMessage': - inventoryHash, toAddress, fromAddress, subject, body = data - self.emit(SIGNAL( - "displayNewInboxMessage(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), - inventoryHash, toAddress, fromAddress, subject, body) - elif command == 'displayNewSentMessage': - toAddress, fromLabel, fromAddress, subject, message, ackdata = data - self.emit(SIGNAL( - "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) - elif command == 'updateNumberOfMessagesProcessed': - self.emit(SIGNAL("updateNumberOfMessagesProcessed()")) - elif command == 'updateNumberOfPubkeysProcessed': - self.emit(SIGNAL("updateNumberOfPubkeysProcessed()")) - elif command == 'updateNumberOfBroadcastsProcessed': - self.emit(SIGNAL("updateNumberOfBroadcastsProcessed()")) - elif command == 'setStatusIcon': - self.emit(SIGNAL("setStatusIcon(PyQt_PyObject)"), data) - elif command == 'changedInboxUnread': - self.emit(SIGNAL("changedInboxUnread(PyQt_PyObject)"), data) - elif command == 'rerenderMessagelistFromLabels': - self.emit(SIGNAL("rerenderMessagelistFromLabels()")) - elif command == 'rerenderMessagelistToLabels': - self.emit(SIGNAL("rerenderMessagelistToLabels()")) - elif command == 'rerenderAddressBook': - self.emit(SIGNAL("rerenderAddressBook()")) - elif command == 'rerenderSubscriptions': - self.emit(SIGNAL("rerenderSubscriptions()")) - elif command == 'rerenderBlackWhiteList': - self.emit(SIGNAL("rerenderBlackWhiteList()")) - elif command == 'removeInboxRowByMsgid': - self.emit(SIGNAL("removeInboxRowByMsgid(PyQt_PyObject)"), data) - elif command == 'newVersionAvailable': - 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) - 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 deleted file mode 100644 index 9f849b3b..00000000 --- a/src/bitmessageqt/utils.py +++ /dev/null @@ -1,116 +0,0 @@ -import hashlib -import os - -from PyQt4 import QtGui - -import state -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 - # example identicon with the following md5 hash: - # 3fd4bf901b9d4ea1394f0fb358725b28 - - 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': - # 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 - penwidth = 0 - image = qidenticon.render_identicon( - int(icon_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) - 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) - 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) - 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) - """ - idcon = QtGui.QIcon() - icon_hash = hashlib.md5(addBMIfNotPresent(address)).hexdigest() - if address == str_broadcast_subscribers: - # don't hash [Broadcast subscribers] - icon_hash = address - # https://www.riverbankcomputing.com/static/Docs/PyQt4/qimagereader.html#supportedImageFormats - # QImageReader.supportedImageFormats () - 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() - if os.path.isfile(lower_hash): - idcon.addFile(lower_hash) - return idcon - elif os.path.isfile(upper_hash): - 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() - if os.path.isfile(lower_default): - default = lower_default - idcon.addFile(lower_default) - return idcon - elif os.path.isfile(upper_default): - default = upper_default - idcon.addFile(upper_default) - return idcon - # If no avatar is found - return identiconize(address) diff --git a/src/bitmessageqt/wanlan.ui b/src/bitmessageqt/wanlan.ui deleted file mode 100644 index 156a111f..00000000 --- a/src/bitmessageqt/wanlan.ui +++ /dev/null @@ -1,217 +0,0 @@ - - - Dialog - - - true - - - - 0 - 0 - 338 - 225 - - - - Dialog - - - - - - Connect on the Internet with everyone else - - - true - - - - - - - Ignore Internet nodes. Only try to connect to these hosts: - - - - - - - false - - - example.domain.local:8444 - - - - - - - false - - - 10.244.30.70:8080 - - - - - - - false - - - Note that you can still receive incoming connections from anyone unless they are blocked by a firewall. - - - true - - - - - - - false - - - Store network objects for two months rather than two days - - - - - - - false - - - Forgo the proof of work - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - buttonBox - accepted() - Dialog - accept() - - - 227 - 201 - - - 157 - 218 - - - - - buttonBox - rejected() - Dialog - reject() - - - 295 - 207 - - - 286 - 218 - - - - - radioButton_2 - toggled(bool) - checkBox - setEnabled(bool) - - - 127 - 40 - - - 93 - 146 - - - - - radioButton_2 - toggled(bool) - checkBox_2 - setEnabled(bool) - - - 160 - 38 - - - 135 - 169 - - - - - radioButton_2 - toggled(bool) - lineEdit - setEnabled(bool) - - - 63 - 41 - - - 68 - 74 - - - - - radioButton_2 - toggled(bool) - lineEdit_2 - setEnabled(bool) - - - 43 - 38 - - - 50 - 98 - - - - - radioButton_2 - toggled(bool) - label - setEnabled(bool) - - - 161 - 44 - - - 160 - 120 - - - - - diff --git a/src/bitmessageqt/widgets.py b/src/bitmessageqt/widgets.py deleted file mode 100644 index 8ef807f2..00000000 --- a/src/bitmessageqt/widgets.py +++ /dev/null @@ -1,13 +0,0 @@ -from PyQt4 import uic -import os.path -import paths -import sys - -def resource_path(resFile): - baseDir = paths.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) - -def load(resFile, widget): - uic.loadUi(resource_path(resFile), widget) diff --git a/src/bitmessageui.py b/src/bitmessageui.py new file mode 100644 index 00000000..ac4ae3b6 --- /dev/null +++ b/src/bitmessageui.py @@ -0,0 +1,552 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'bitmessageui.ui' +# +# Created: Tue May 28 16:22:12 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_MainWindow(object): + def setupUi(self, MainWindow): + MainWindow.setObjectName(_fromUtf8("MainWindow")) + MainWindow.resize(795, 561) + icon = QtGui.QIcon() + 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) + self.centralwidget.setObjectName(_fromUtf8("centralwidget")) + self.gridLayout = QtGui.QGridLayout(self.centralwidget) + self.gridLayout.setMargin(0) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.tabWidget = QtGui.QTabWidget(self.centralwidget) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.tabWidget.sizePolicy().hasHeightForWidth()) + self.tabWidget.setSizePolicy(sizePolicy) + self.tabWidget.setMinimumSize(QtCore.QSize(0, 0)) + self.tabWidget.setBaseSize(QtCore.QSize(0, 0)) + font = QtGui.QFont() + font.setPointSize(9) + self.tabWidget.setFont(font) + self.tabWidget.setTabPosition(QtGui.QTabWidget.North) + self.tabWidget.setTabShape(QtGui.QTabWidget.Rounded) + self.tabWidget.setObjectName(_fromUtf8("tabWidget")) + self.inbox = QtGui.QWidget() + self.inbox.setObjectName(_fromUtf8("inbox")) + self.verticalLayout_2 = QtGui.QVBoxLayout(self.inbox) + self.verticalLayout_2.setObjectName(_fromUtf8("verticalLayout_2")) + self.tableWidgetInbox = QtGui.QTableWidget(self.inbox) + self.tableWidgetInbox.setAlternatingRowColors(True) + self.tableWidgetInbox.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) + self.tableWidgetInbox.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) + self.tableWidgetInbox.setWordWrap(False) + self.tableWidgetInbox.setObjectName(_fromUtf8("tableWidgetInbox")) + self.tableWidgetInbox.setColumnCount(4) + self.tableWidgetInbox.setRowCount(0) + item = QtGui.QTableWidgetItem() + self.tableWidgetInbox.setHorizontalHeaderItem(0, item) + item = QtGui.QTableWidgetItem() + self.tableWidgetInbox.setHorizontalHeaderItem(1, item) + item = QtGui.QTableWidgetItem() + self.tableWidgetInbox.setHorizontalHeaderItem(2, item) + item = QtGui.QTableWidgetItem() + self.tableWidgetInbox.setHorizontalHeaderItem(3, item) + self.tableWidgetInbox.horizontalHeader().setCascadingSectionResizes(True) + self.tableWidgetInbox.horizontalHeader().setDefaultSectionSize(200) + self.tableWidgetInbox.horizontalHeader().setHighlightSections(False) + self.tableWidgetInbox.horizontalHeader().setMinimumSectionSize(27) + self.tableWidgetInbox.horizontalHeader().setSortIndicatorShown(False) + self.tableWidgetInbox.horizontalHeader().setStretchLastSection(True) + self.tableWidgetInbox.verticalHeader().setVisible(False) + self.tableWidgetInbox.verticalHeader().setDefaultSectionSize(26) + self.verticalLayout_2.addWidget(self.tableWidgetInbox) + self.textEditInboxMessage = QtGui.QTextEdit(self.inbox) + self.textEditInboxMessage.setBaseSize(QtCore.QSize(0, 500)) + self.textEditInboxMessage.setObjectName(_fromUtf8("textEditInboxMessage")) + self.verticalLayout_2.addWidget(self.textEditInboxMessage) + icon1 = QtGui.QIcon() + icon1.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/inbox.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.tabWidget.addTab(self.inbox, icon1, _fromUtf8("")) + self.send = QtGui.QWidget() + self.send.setObjectName(_fromUtf8("send")) + self.gridLayout_2 = QtGui.QGridLayout(self.send) + self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2")) + self.pushButtonLoadFromAddressBook = QtGui.QPushButton(self.send) + font = QtGui.QFont() + font.setPointSize(7) + self.pushButtonLoadFromAddressBook.setFont(font) + self.pushButtonLoadFromAddressBook.setObjectName(_fromUtf8("pushButtonLoadFromAddressBook")) + self.gridLayout_2.addWidget(self.pushButtonLoadFromAddressBook, 3, 2, 1, 2) + self.label_4 = QtGui.QLabel(self.send) + self.label_4.setObjectName(_fromUtf8("label_4")) + self.gridLayout_2.addWidget(self.label_4, 5, 0, 1, 1) + self.comboBoxSendFrom = QtGui.QComboBox(self.send) + self.comboBoxSendFrom.setMinimumSize(QtCore.QSize(300, 0)) + self.comboBoxSendFrom.setObjectName(_fromUtf8("comboBoxSendFrom")) + self.gridLayout_2.addWidget(self.comboBoxSendFrom, 2, 1, 1, 1) + self.label_3 = QtGui.QLabel(self.send) + self.label_3.setObjectName(_fromUtf8("label_3")) + self.gridLayout_2.addWidget(self.label_3, 4, 0, 1, 1) + self.labelFrom = QtGui.QLabel(self.send) + self.labelFrom.setText(_fromUtf8("")) + self.labelFrom.setObjectName(_fromUtf8("labelFrom")) + self.gridLayout_2.addWidget(self.labelFrom, 2, 2, 1, 3) + self.radioButtonSpecific = QtGui.QRadioButton(self.send) + self.radioButtonSpecific.setChecked(True) + self.radioButtonSpecific.setObjectName(_fromUtf8("radioButtonSpecific")) + self.gridLayout_2.addWidget(self.radioButtonSpecific, 0, 1, 1, 1) + self.lineEditTo = QtGui.QLineEdit(self.send) + self.lineEditTo.setObjectName(_fromUtf8("lineEditTo")) + self.gridLayout_2.addWidget(self.lineEditTo, 3, 1, 1, 1) + self.textEditMessage = QtGui.QTextEdit(self.send) + self.textEditMessage.setObjectName(_fromUtf8("textEditMessage")) + self.gridLayout_2.addWidget(self.textEditMessage, 5, 1, 2, 5) + self.label = QtGui.QLabel(self.send) + self.label.setObjectName(_fromUtf8("label")) + self.gridLayout_2.addWidget(self.label, 3, 0, 1, 1) + self.label_2 = QtGui.QLabel(self.send) + self.label_2.setObjectName(_fromUtf8("label_2")) + self.gridLayout_2.addWidget(self.label_2, 2, 0, 1, 1) + spacerItem = QtGui.QSpacerItem(20, 297, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) + self.gridLayout_2.addItem(spacerItem, 6, 0, 1, 1) + self.radioButtonBroadcast = QtGui.QRadioButton(self.send) + self.radioButtonBroadcast.setObjectName(_fromUtf8("radioButtonBroadcast")) + self.gridLayout_2.addWidget(self.radioButtonBroadcast, 1, 1, 1, 3) + self.lineEditSubject = QtGui.QLineEdit(self.send) + self.lineEditSubject.setText(_fromUtf8("")) + self.lineEditSubject.setObjectName(_fromUtf8("lineEditSubject")) + self.gridLayout_2.addWidget(self.lineEditSubject, 4, 1, 1, 5) + spacerItem1 = QtGui.QSpacerItem(20, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_2.addItem(spacerItem1, 3, 4, 1, 1) + self.pushButtonSend = QtGui.QPushButton(self.send) + self.pushButtonSend.setObjectName(_fromUtf8("pushButtonSend")) + self.gridLayout_2.addWidget(self.pushButtonSend, 7, 5, 1, 1) + self.labelSendBroadcastWarning = QtGui.QLabel(self.send) + self.labelSendBroadcastWarning.setEnabled(True) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Ignored, QtGui.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.labelSendBroadcastWarning.sizePolicy().hasHeightForWidth()) + self.labelSendBroadcastWarning.setSizePolicy(sizePolicy) + self.labelSendBroadcastWarning.setIndent(-1) + self.labelSendBroadcastWarning.setObjectName(_fromUtf8("labelSendBroadcastWarning")) + self.gridLayout_2.addWidget(self.labelSendBroadcastWarning, 7, 1, 1, 4) + icon2 = QtGui.QIcon() + icon2.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/send.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.tabWidget.addTab(self.send, icon2, _fromUtf8("")) + self.sent = QtGui.QWidget() + self.sent.setObjectName(_fromUtf8("sent")) + self.verticalLayout = QtGui.QVBoxLayout(self.sent) + self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) + self.tableWidgetSent = QtGui.QTableWidget(self.sent) + self.tableWidgetSent.setDragDropMode(QtGui.QAbstractItemView.DragDrop) + self.tableWidgetSent.setAlternatingRowColors(True) + self.tableWidgetSent.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) + self.tableWidgetSent.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) + self.tableWidgetSent.setWordWrap(False) + self.tableWidgetSent.setObjectName(_fromUtf8("tableWidgetSent")) + self.tableWidgetSent.setColumnCount(4) + self.tableWidgetSent.setRowCount(0) + item = QtGui.QTableWidgetItem() + self.tableWidgetSent.setHorizontalHeaderItem(0, item) + item = QtGui.QTableWidgetItem() + self.tableWidgetSent.setHorizontalHeaderItem(1, item) + item = QtGui.QTableWidgetItem() + self.tableWidgetSent.setHorizontalHeaderItem(2, item) + item = QtGui.QTableWidgetItem() + self.tableWidgetSent.setHorizontalHeaderItem(3, item) + self.tableWidgetSent.horizontalHeader().setCascadingSectionResizes(True) + self.tableWidgetSent.horizontalHeader().setDefaultSectionSize(130) + self.tableWidgetSent.horizontalHeader().setHighlightSections(False) + self.tableWidgetSent.horizontalHeader().setSortIndicatorShown(False) + self.tableWidgetSent.horizontalHeader().setStretchLastSection(True) + self.tableWidgetSent.verticalHeader().setVisible(False) + self.tableWidgetSent.verticalHeader().setStretchLastSection(False) + self.verticalLayout.addWidget(self.tableWidgetSent) + self.textEditSentMessage = QtGui.QTextEdit(self.sent) + self.textEditSentMessage.setObjectName(_fromUtf8("textEditSentMessage")) + self.verticalLayout.addWidget(self.textEditSentMessage) + icon3 = QtGui.QIcon() + icon3.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/sent.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.tabWidget.addTab(self.sent, icon3, _fromUtf8("")) + self.youridentities = QtGui.QWidget() + self.youridentities.setObjectName(_fromUtf8("youridentities")) + self.gridLayout_3 = QtGui.QGridLayout(self.youridentities) + self.gridLayout_3.setObjectName(_fromUtf8("gridLayout_3")) + self.pushButtonNewAddress = QtGui.QPushButton(self.youridentities) + self.pushButtonNewAddress.setObjectName(_fromUtf8("pushButtonNewAddress")) + self.gridLayout_3.addWidget(self.pushButtonNewAddress, 0, 0, 1, 1) + spacerItem2 = QtGui.QSpacerItem(689, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_3.addItem(spacerItem2, 0, 1, 1, 1) + self.tableWidgetYourIdentities = QtGui.QTableWidget(self.youridentities) + self.tableWidgetYourIdentities.setFrameShadow(QtGui.QFrame.Sunken) + self.tableWidgetYourIdentities.setLineWidth(1) + self.tableWidgetYourIdentities.setAlternatingRowColors(True) + self.tableWidgetYourIdentities.setSelectionMode(QtGui.QAbstractItemView.SingleSelection) + self.tableWidgetYourIdentities.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) + self.tableWidgetYourIdentities.setObjectName(_fromUtf8("tableWidgetYourIdentities")) + self.tableWidgetYourIdentities.setColumnCount(3) + self.tableWidgetYourIdentities.setRowCount(0) + item = QtGui.QTableWidgetItem() + font = QtGui.QFont() + font.setKerning(True) + item.setFont(font) + self.tableWidgetYourIdentities.setHorizontalHeaderItem(0, item) + item = QtGui.QTableWidgetItem() + self.tableWidgetYourIdentities.setHorizontalHeaderItem(1, item) + item = QtGui.QTableWidgetItem() + self.tableWidgetYourIdentities.setHorizontalHeaderItem(2, item) + self.tableWidgetYourIdentities.horizontalHeader().setCascadingSectionResizes(True) + self.tableWidgetYourIdentities.horizontalHeader().setDefaultSectionSize(346) + self.tableWidgetYourIdentities.horizontalHeader().setMinimumSectionSize(52) + self.tableWidgetYourIdentities.horizontalHeader().setSortIndicatorShown(True) + self.tableWidgetYourIdentities.horizontalHeader().setStretchLastSection(True) + self.tableWidgetYourIdentities.verticalHeader().setVisible(False) + self.tableWidgetYourIdentities.verticalHeader().setDefaultSectionSize(26) + self.tableWidgetYourIdentities.verticalHeader().setSortIndicatorShown(False) + self.tableWidgetYourIdentities.verticalHeader().setStretchLastSection(False) + self.gridLayout_3.addWidget(self.tableWidgetYourIdentities, 1, 0, 1, 2) + icon4 = QtGui.QIcon() + icon4.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/identities.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.tabWidget.addTab(self.youridentities, icon4, _fromUtf8("")) + self.subscriptions = QtGui.QWidget() + self.subscriptions.setObjectName(_fromUtf8("subscriptions")) + self.gridLayout_4 = QtGui.QGridLayout(self.subscriptions) + self.gridLayout_4.setObjectName(_fromUtf8("gridLayout_4")) + self.label_5 = QtGui.QLabel(self.subscriptions) + self.label_5.setWordWrap(True) + self.label_5.setObjectName(_fromUtf8("label_5")) + self.gridLayout_4.addWidget(self.label_5, 0, 0, 1, 2) + self.pushButtonAddSubscription = QtGui.QPushButton(self.subscriptions) + self.pushButtonAddSubscription.setObjectName(_fromUtf8("pushButtonAddSubscription")) + self.gridLayout_4.addWidget(self.pushButtonAddSubscription, 1, 0, 1, 1) + spacerItem3 = QtGui.QSpacerItem(689, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_4.addItem(spacerItem3, 1, 1, 1, 1) + self.tableWidgetSubscriptions = QtGui.QTableWidget(self.subscriptions) + self.tableWidgetSubscriptions.setAlternatingRowColors(True) + self.tableWidgetSubscriptions.setSelectionMode(QtGui.QAbstractItemView.SingleSelection) + self.tableWidgetSubscriptions.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) + self.tableWidgetSubscriptions.setObjectName(_fromUtf8("tableWidgetSubscriptions")) + self.tableWidgetSubscriptions.setColumnCount(2) + self.tableWidgetSubscriptions.setRowCount(0) + item = QtGui.QTableWidgetItem() + self.tableWidgetSubscriptions.setHorizontalHeaderItem(0, item) + item = QtGui.QTableWidgetItem() + self.tableWidgetSubscriptions.setHorizontalHeaderItem(1, item) + self.tableWidgetSubscriptions.horizontalHeader().setCascadingSectionResizes(True) + self.tableWidgetSubscriptions.horizontalHeader().setDefaultSectionSize(400) + self.tableWidgetSubscriptions.horizontalHeader().setHighlightSections(False) + self.tableWidgetSubscriptions.horizontalHeader().setSortIndicatorShown(False) + self.tableWidgetSubscriptions.horizontalHeader().setStretchLastSection(True) + self.tableWidgetSubscriptions.verticalHeader().setVisible(False) + self.gridLayout_4.addWidget(self.tableWidgetSubscriptions, 2, 0, 1, 2) + icon5 = QtGui.QIcon() + icon5.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/subscriptions.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.tabWidget.addTab(self.subscriptions, icon5, _fromUtf8("")) + self.addressbook = QtGui.QWidget() + self.addressbook.setObjectName(_fromUtf8("addressbook")) + self.gridLayout_5 = QtGui.QGridLayout(self.addressbook) + self.gridLayout_5.setObjectName(_fromUtf8("gridLayout_5")) + self.label_6 = QtGui.QLabel(self.addressbook) + self.label_6.setWordWrap(True) + self.label_6.setObjectName(_fromUtf8("label_6")) + self.gridLayout_5.addWidget(self.label_6, 0, 0, 1, 2) + self.pushButtonAddAddressBook = QtGui.QPushButton(self.addressbook) + self.pushButtonAddAddressBook.setObjectName(_fromUtf8("pushButtonAddAddressBook")) + self.gridLayout_5.addWidget(self.pushButtonAddAddressBook, 1, 0, 1, 1) + spacerItem4 = QtGui.QSpacerItem(689, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_5.addItem(spacerItem4, 1, 1, 1, 1) + self.tableWidgetAddressBook = QtGui.QTableWidget(self.addressbook) + self.tableWidgetAddressBook.setAlternatingRowColors(True) + self.tableWidgetAddressBook.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) + self.tableWidgetAddressBook.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) + self.tableWidgetAddressBook.setObjectName(_fromUtf8("tableWidgetAddressBook")) + self.tableWidgetAddressBook.setColumnCount(2) + self.tableWidgetAddressBook.setRowCount(0) + item = QtGui.QTableWidgetItem() + self.tableWidgetAddressBook.setHorizontalHeaderItem(0, item) + item = QtGui.QTableWidgetItem() + self.tableWidgetAddressBook.setHorizontalHeaderItem(1, item) + self.tableWidgetAddressBook.horizontalHeader().setCascadingSectionResizes(True) + self.tableWidgetAddressBook.horizontalHeader().setDefaultSectionSize(400) + self.tableWidgetAddressBook.horizontalHeader().setHighlightSections(False) + self.tableWidgetAddressBook.horizontalHeader().setStretchLastSection(True) + self.tableWidgetAddressBook.verticalHeader().setVisible(False) + self.gridLayout_5.addWidget(self.tableWidgetAddressBook, 2, 0, 1, 2) + icon6 = QtGui.QIcon() + icon6.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/addressbook.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.tabWidget.addTab(self.addressbook, icon6, _fromUtf8("")) + self.blackwhitelist = QtGui.QWidget() + self.blackwhitelist.setObjectName(_fromUtf8("blackwhitelist")) + self.gridLayout_6 = QtGui.QGridLayout(self.blackwhitelist) + self.gridLayout_6.setObjectName(_fromUtf8("gridLayout_6")) + self.radioButtonBlacklist = QtGui.QRadioButton(self.blackwhitelist) + self.radioButtonBlacklist.setChecked(True) + self.radioButtonBlacklist.setObjectName(_fromUtf8("radioButtonBlacklist")) + self.gridLayout_6.addWidget(self.radioButtonBlacklist, 0, 0, 1, 2) + self.radioButtonWhitelist = QtGui.QRadioButton(self.blackwhitelist) + self.radioButtonWhitelist.setObjectName(_fromUtf8("radioButtonWhitelist")) + self.gridLayout_6.addWidget(self.radioButtonWhitelist, 1, 0, 1, 2) + self.pushButtonAddBlacklist = QtGui.QPushButton(self.blackwhitelist) + self.pushButtonAddBlacklist.setObjectName(_fromUtf8("pushButtonAddBlacklist")) + self.gridLayout_6.addWidget(self.pushButtonAddBlacklist, 2, 0, 1, 1) + spacerItem5 = QtGui.QSpacerItem(689, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_6.addItem(spacerItem5, 2, 1, 1, 1) + self.tableWidgetBlacklist = QtGui.QTableWidget(self.blackwhitelist) + self.tableWidgetBlacklist.setAlternatingRowColors(True) + self.tableWidgetBlacklist.setSelectionMode(QtGui.QAbstractItemView.SingleSelection) + self.tableWidgetBlacklist.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) + self.tableWidgetBlacklist.setObjectName(_fromUtf8("tableWidgetBlacklist")) + self.tableWidgetBlacklist.setColumnCount(2) + self.tableWidgetBlacklist.setRowCount(0) + item = QtGui.QTableWidgetItem() + self.tableWidgetBlacklist.setHorizontalHeaderItem(0, item) + item = QtGui.QTableWidgetItem() + self.tableWidgetBlacklist.setHorizontalHeaderItem(1, item) + self.tableWidgetBlacklist.horizontalHeader().setCascadingSectionResizes(True) + self.tableWidgetBlacklist.horizontalHeader().setDefaultSectionSize(400) + self.tableWidgetBlacklist.horizontalHeader().setHighlightSections(False) + self.tableWidgetBlacklist.horizontalHeader().setSortIndicatorShown(False) + self.tableWidgetBlacklist.horizontalHeader().setStretchLastSection(True) + self.tableWidgetBlacklist.verticalHeader().setVisible(False) + self.gridLayout_6.addWidget(self.tableWidgetBlacklist, 3, 0, 1, 2) + icon7 = QtGui.QIcon() + icon7.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/blacklist.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.tabWidget.addTab(self.blackwhitelist, icon7, _fromUtf8("")) + self.networkstatus = QtGui.QWidget() + self.networkstatus.setObjectName(_fromUtf8("networkstatus")) + self.pushButtonStatusIcon = QtGui.QPushButton(self.networkstatus) + self.pushButtonStatusIcon.setGeometry(QtCore.QRect(680, 440, 21, 23)) + self.pushButtonStatusIcon.setText(_fromUtf8("")) + icon8 = QtGui.QIcon() + icon8.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/redicon.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.pushButtonStatusIcon.setIcon(icon8) + self.pushButtonStatusIcon.setFlat(True) + self.pushButtonStatusIcon.setObjectName(_fromUtf8("pushButtonStatusIcon")) + self.tableWidgetConnectionCount = QtGui.QTableWidget(self.networkstatus) + self.tableWidgetConnectionCount.setGeometry(QtCore.QRect(20, 70, 241, 241)) + palette = QtGui.QPalette() + brush = QtGui.QBrush(QtGui.QColor(212, 208, 200)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) + brush = QtGui.QBrush(QtGui.QColor(212, 208, 200)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) + brush = QtGui.QBrush(QtGui.QColor(212, 208, 200)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) + self.tableWidgetConnectionCount.setPalette(palette) + self.tableWidgetConnectionCount.setFrameShape(QtGui.QFrame.Box) + self.tableWidgetConnectionCount.setFrameShadow(QtGui.QFrame.Plain) + self.tableWidgetConnectionCount.setProperty("showDropIndicator", False) + self.tableWidgetConnectionCount.setAlternatingRowColors(True) + self.tableWidgetConnectionCount.setSelectionMode(QtGui.QAbstractItemView.NoSelection) + self.tableWidgetConnectionCount.setObjectName(_fromUtf8("tableWidgetConnectionCount")) + self.tableWidgetConnectionCount.setColumnCount(2) + self.tableWidgetConnectionCount.setRowCount(0) + item = QtGui.QTableWidgetItem() + self.tableWidgetConnectionCount.setHorizontalHeaderItem(0, item) + item = QtGui.QTableWidgetItem() + self.tableWidgetConnectionCount.setHorizontalHeaderItem(1, item) + self.tableWidgetConnectionCount.horizontalHeader().setCascadingSectionResizes(True) + self.tableWidgetConnectionCount.horizontalHeader().setHighlightSections(False) + self.tableWidgetConnectionCount.horizontalHeader().setStretchLastSection(True) + self.tableWidgetConnectionCount.verticalHeader().setVisible(False) + self.labelTotalConnections = QtGui.QLabel(self.networkstatus) + self.labelTotalConnections.setGeometry(QtCore.QRect(20, 30, 401, 16)) + self.labelTotalConnections.setObjectName(_fromUtf8("labelTotalConnections")) + self.labelStartupTime = QtGui.QLabel(self.networkstatus) + self.labelStartupTime.setGeometry(QtCore.QRect(320, 110, 331, 20)) + self.labelStartupTime.setObjectName(_fromUtf8("labelStartupTime")) + self.labelMessageCount = QtGui.QLabel(self.networkstatus) + self.labelMessageCount.setGeometry(QtCore.QRect(350, 130, 361, 16)) + self.labelMessageCount.setObjectName(_fromUtf8("labelMessageCount")) + self.labelPubkeyCount = QtGui.QLabel(self.networkstatus) + self.labelPubkeyCount.setGeometry(QtCore.QRect(350, 170, 331, 16)) + self.labelPubkeyCount.setObjectName(_fromUtf8("labelPubkeyCount")) + self.labelBroadcastCount = QtGui.QLabel(self.networkstatus) + self.labelBroadcastCount.setGeometry(QtCore.QRect(350, 150, 351, 16)) + self.labelBroadcastCount.setObjectName(_fromUtf8("labelBroadcastCount")) + icon9 = QtGui.QIcon() + icon9.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/networkstatus.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.tabWidget.addTab(self.networkstatus, icon9, _fromUtf8("")) + self.gridLayout.addWidget(self.tabWidget, 0, 0, 1, 1) + MainWindow.setCentralWidget(self.centralwidget) + self.menubar = QtGui.QMenuBar(MainWindow) + self.menubar.setGeometry(QtCore.QRect(0, 0, 795, 18)) + self.menubar.setObjectName(_fromUtf8("menubar")) + self.menuFile = QtGui.QMenu(self.menubar) + self.menuFile.setObjectName(_fromUtf8("menuFile")) + self.menuSettings = QtGui.QMenu(self.menubar) + self.menuSettings.setObjectName(_fromUtf8("menuSettings")) + self.menuHelp = QtGui.QMenu(self.menubar) + self.menuHelp.setObjectName(_fromUtf8("menuHelp")) + MainWindow.setMenuBar(self.menubar) + self.statusbar = QtGui.QStatusBar(MainWindow) + self.statusbar.setMaximumSize(QtCore.QSize(16777215, 22)) + self.statusbar.setObjectName(_fromUtf8("statusbar")) + MainWindow.setStatusBar(self.statusbar) + self.actionImport_keys = QtGui.QAction(MainWindow) + self.actionImport_keys.setObjectName(_fromUtf8("actionImport_keys")) + self.actionManageKeys = QtGui.QAction(MainWindow) + self.actionManageKeys.setCheckable(False) + self.actionManageKeys.setEnabled(True) + self.actionManageKeys.setObjectName(_fromUtf8("actionManageKeys")) + self.actionExit = QtGui.QAction(MainWindow) + self.actionExit.setObjectName(_fromUtf8("actionExit")) + self.actionHelp = QtGui.QAction(MainWindow) + self.actionHelp.setObjectName(_fromUtf8("actionHelp")) + self.actionAbout = QtGui.QAction(MainWindow) + self.actionAbout.setObjectName(_fromUtf8("actionAbout")) + self.actionSettings = QtGui.QAction(MainWindow) + self.actionSettings.setObjectName(_fromUtf8("actionSettings")) + self.actionRegenerateDeterministicAddresses = QtGui.QAction(MainWindow) + self.actionRegenerateDeterministicAddresses.setObjectName(_fromUtf8("actionRegenerateDeterministicAddresses")) + self.actionDeleteAllTrashedMessages = QtGui.QAction(MainWindow) + self.actionDeleteAllTrashedMessages.setObjectName(_fromUtf8("actionDeleteAllTrashedMessages")) + self.menuFile.addAction(self.actionManageKeys) + self.menuFile.addAction(self.actionDeleteAllTrashedMessages) + self.menuFile.addAction(self.actionRegenerateDeterministicAddresses) + self.menuFile.addAction(self.actionExit) + self.menuSettings.addAction(self.actionSettings) + self.menuHelp.addAction(self.actionHelp) + self.menuHelp.addAction(self.actionAbout) + self.menubar.addAction(self.menuFile.menuAction()) + self.menubar.addAction(self.menuSettings.menuAction()) + self.menubar.addAction(self.menuHelp.menuAction()) + + self.retranslateUi(MainWindow) + self.tabWidget.setCurrentIndex(0) + QtCore.QObject.connect(self.radioButtonSpecific, QtCore.SIGNAL(_fromUtf8("toggled(bool)")), self.lineEditTo.setEnabled) + QtCore.QObject.connect(self.radioButtonSpecific, QtCore.SIGNAL(_fromUtf8("clicked(bool)")), self.labelSendBroadcastWarning.hide) + QtCore.QObject.connect(self.radioButtonBroadcast, QtCore.SIGNAL(_fromUtf8("clicked()")), self.labelSendBroadcastWarning.show) + QtCore.QMetaObject.connectSlotsByName(MainWindow) + MainWindow.setTabOrder(self.tabWidget, self.tableWidgetInbox) + MainWindow.setTabOrder(self.tableWidgetInbox, self.textEditInboxMessage) + MainWindow.setTabOrder(self.textEditInboxMessage, self.radioButtonSpecific) + MainWindow.setTabOrder(self.radioButtonSpecific, self.radioButtonBroadcast) + MainWindow.setTabOrder(self.radioButtonBroadcast, self.comboBoxSendFrom) + MainWindow.setTabOrder(self.comboBoxSendFrom, self.lineEditTo) + MainWindow.setTabOrder(self.lineEditTo, self.pushButtonLoadFromAddressBook) + MainWindow.setTabOrder(self.pushButtonLoadFromAddressBook, self.lineEditSubject) + MainWindow.setTabOrder(self.lineEditSubject, self.textEditMessage) + MainWindow.setTabOrder(self.textEditMessage, self.pushButtonSend) + MainWindow.setTabOrder(self.pushButtonSend, self.tableWidgetSent) + MainWindow.setTabOrder(self.tableWidgetSent, self.textEditSentMessage) + MainWindow.setTabOrder(self.textEditSentMessage, self.pushButtonNewAddress) + MainWindow.setTabOrder(self.pushButtonNewAddress, self.tableWidgetYourIdentities) + MainWindow.setTabOrder(self.tableWidgetYourIdentities, self.pushButtonAddSubscription) + MainWindow.setTabOrder(self.pushButtonAddSubscription, self.tableWidgetSubscriptions) + MainWindow.setTabOrder(self.tableWidgetSubscriptions, self.pushButtonAddAddressBook) + MainWindow.setTabOrder(self.pushButtonAddAddressBook, self.tableWidgetAddressBook) + MainWindow.setTabOrder(self.tableWidgetAddressBook, self.radioButtonBlacklist) + MainWindow.setTabOrder(self.radioButtonBlacklist, self.radioButtonWhitelist) + MainWindow.setTabOrder(self.radioButtonWhitelist, self.pushButtonAddBlacklist) + MainWindow.setTabOrder(self.pushButtonAddBlacklist, self.tableWidgetBlacklist) + MainWindow.setTabOrder(self.tableWidgetBlacklist, self.tableWidgetConnectionCount) + MainWindow.setTabOrder(self.tableWidgetConnectionCount, self.pushButtonStatusIcon) + + def retranslateUi(self, MainWindow): + MainWindow.setWindowTitle(QtGui.QApplication.translate("MainWindow", "Bitmessage", None, QtGui.QApplication.UnicodeUTF8)) + self.tableWidgetInbox.setSortingEnabled(True) + item = self.tableWidgetInbox.horizontalHeaderItem(0) + item.setText(QtGui.QApplication.translate("MainWindow", "To", None, QtGui.QApplication.UnicodeUTF8)) + item = self.tableWidgetInbox.horizontalHeaderItem(1) + item.setText(QtGui.QApplication.translate("MainWindow", "From", None, QtGui.QApplication.UnicodeUTF8)) + item = self.tableWidgetInbox.horizontalHeaderItem(2) + item.setText(QtGui.QApplication.translate("MainWindow", "Subject", None, QtGui.QApplication.UnicodeUTF8)) + item = self.tableWidgetInbox.horizontalHeaderItem(3) + item.setText(QtGui.QApplication.translate("MainWindow", "Received", None, QtGui.QApplication.UnicodeUTF8)) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.inbox), QtGui.QApplication.translate("MainWindow", "Inbox", None, QtGui.QApplication.UnicodeUTF8)) + self.pushButtonLoadFromAddressBook.setText(QtGui.QApplication.translate("MainWindow", "Load from Address book", None, QtGui.QApplication.UnicodeUTF8)) + self.label_4.setText(QtGui.QApplication.translate("MainWindow", "Message:", None, QtGui.QApplication.UnicodeUTF8)) + self.label_3.setText(QtGui.QApplication.translate("MainWindow", "Subject:", None, QtGui.QApplication.UnicodeUTF8)) + self.radioButtonSpecific.setText(QtGui.QApplication.translate("MainWindow", "Send to one or more specific people", None, QtGui.QApplication.UnicodeUTF8)) + self.textEditMessage.setHtml(QtGui.QApplication.translate("MainWindow", "\n" +"\n" +"


", None, QtGui.QApplication.UnicodeUTF8)) + self.label.setText(QtGui.QApplication.translate("MainWindow", "To:", None, QtGui.QApplication.UnicodeUTF8)) + self.label_2.setText(QtGui.QApplication.translate("MainWindow", "From:", None, QtGui.QApplication.UnicodeUTF8)) + self.radioButtonBroadcast.setText(QtGui.QApplication.translate("MainWindow", "Broadcast to everyone who is subscribed to your address", None, QtGui.QApplication.UnicodeUTF8)) + self.pushButtonSend.setText(QtGui.QApplication.translate("MainWindow", "Send", None, QtGui.QApplication.UnicodeUTF8)) + self.labelSendBroadcastWarning.setText(QtGui.QApplication.translate("MainWindow", "Be aware that broadcasts are only encrypted with your address. Anyone who knows your address can read them.", None, QtGui.QApplication.UnicodeUTF8)) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.send), QtGui.QApplication.translate("MainWindow", "Send", None, QtGui.QApplication.UnicodeUTF8)) + self.tableWidgetSent.setSortingEnabled(True) + item = self.tableWidgetSent.horizontalHeaderItem(0) + item.setText(QtGui.QApplication.translate("MainWindow", "To", None, QtGui.QApplication.UnicodeUTF8)) + item = self.tableWidgetSent.horizontalHeaderItem(1) + item.setText(QtGui.QApplication.translate("MainWindow", "From", None, QtGui.QApplication.UnicodeUTF8)) + item = self.tableWidgetSent.horizontalHeaderItem(2) + item.setText(QtGui.QApplication.translate("MainWindow", "Subject", None, QtGui.QApplication.UnicodeUTF8)) + item = self.tableWidgetSent.horizontalHeaderItem(3) + item.setText(QtGui.QApplication.translate("MainWindow", "Status", None, QtGui.QApplication.UnicodeUTF8)) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.sent), QtGui.QApplication.translate("MainWindow", "Sent", None, QtGui.QApplication.UnicodeUTF8)) + self.pushButtonNewAddress.setText(QtGui.QApplication.translate("MainWindow", "New", None, QtGui.QApplication.UnicodeUTF8)) + self.tableWidgetYourIdentities.setSortingEnabled(True) + item = self.tableWidgetYourIdentities.horizontalHeaderItem(0) + item.setText(QtGui.QApplication.translate("MainWindow", "Label (not shown to anyone)", None, QtGui.QApplication.UnicodeUTF8)) + item = self.tableWidgetYourIdentities.horizontalHeaderItem(1) + item.setText(QtGui.QApplication.translate("MainWindow", "Address", None, QtGui.QApplication.UnicodeUTF8)) + item = self.tableWidgetYourIdentities.horizontalHeaderItem(2) + item.setText(QtGui.QApplication.translate("MainWindow", "Stream", None, QtGui.QApplication.UnicodeUTF8)) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.youridentities), QtGui.QApplication.translate("MainWindow", "Your Identities", None, QtGui.QApplication.UnicodeUTF8)) + self.label_5.setText(QtGui.QApplication.translate("MainWindow", "Here you can subscribe to \'broadcast messages\' that are sent by other users. Messages will appear in your Inbox. Addresses here override those on the Blacklist tab.", None, QtGui.QApplication.UnicodeUTF8)) + self.pushButtonAddSubscription.setText(QtGui.QApplication.translate("MainWindow", "Add new Subscription", None, QtGui.QApplication.UnicodeUTF8)) + self.tableWidgetSubscriptions.setSortingEnabled(True) + item = self.tableWidgetSubscriptions.horizontalHeaderItem(0) + item.setText(QtGui.QApplication.translate("MainWindow", "Label", None, QtGui.QApplication.UnicodeUTF8)) + item = self.tableWidgetSubscriptions.horizontalHeaderItem(1) + item.setText(QtGui.QApplication.translate("MainWindow", "Address", None, QtGui.QApplication.UnicodeUTF8)) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.subscriptions), QtGui.QApplication.translate("MainWindow", "Subscriptions", None, QtGui.QApplication.UnicodeUTF8)) + self.label_6.setText(QtGui.QApplication.translate("MainWindow", "The Address book is useful for adding names or labels to other people\'s Bitmessage addresses so that you can recognize them more easily in your inbox. You can add entries here using the \'Add\' button, or from your inbox by right-clicking on a message.", None, QtGui.QApplication.UnicodeUTF8)) + self.pushButtonAddAddressBook.setText(QtGui.QApplication.translate("MainWindow", "Add new entry", None, QtGui.QApplication.UnicodeUTF8)) + self.tableWidgetAddressBook.setSortingEnabled(True) + item = self.tableWidgetAddressBook.horizontalHeaderItem(0) + item.setText(QtGui.QApplication.translate("MainWindow", "Name or Label", None, QtGui.QApplication.UnicodeUTF8)) + item = self.tableWidgetAddressBook.horizontalHeaderItem(1) + item.setText(QtGui.QApplication.translate("MainWindow", "Address", None, QtGui.QApplication.UnicodeUTF8)) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.addressbook), QtGui.QApplication.translate("MainWindow", "Address Book", None, QtGui.QApplication.UnicodeUTF8)) + self.radioButtonBlacklist.setText(QtGui.QApplication.translate("MainWindow", "Use a Blacklist (Allow all incoming messages except those on the Blacklist)", None, QtGui.QApplication.UnicodeUTF8)) + self.radioButtonWhitelist.setText(QtGui.QApplication.translate("MainWindow", "Use a Whitelist (Block all incoming messages except those on the Whitelist)", None, QtGui.QApplication.UnicodeUTF8)) + self.pushButtonAddBlacklist.setText(QtGui.QApplication.translate("MainWindow", "Add new entry", None, QtGui.QApplication.UnicodeUTF8)) + self.tableWidgetBlacklist.setSortingEnabled(True) + item = self.tableWidgetBlacklist.horizontalHeaderItem(0) + item.setText(QtGui.QApplication.translate("MainWindow", "Name or Label", None, QtGui.QApplication.UnicodeUTF8)) + item = self.tableWidgetBlacklist.horizontalHeaderItem(1) + item.setText(QtGui.QApplication.translate("MainWindow", "Address", None, QtGui.QApplication.UnicodeUTF8)) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.blackwhitelist), QtGui.QApplication.translate("MainWindow", "Blacklist", None, QtGui.QApplication.UnicodeUTF8)) + item = self.tableWidgetConnectionCount.horizontalHeaderItem(0) + item.setText(QtGui.QApplication.translate("MainWindow", "Stream Number", None, QtGui.QApplication.UnicodeUTF8)) + item = self.tableWidgetConnectionCount.horizontalHeaderItem(1) + item.setText(QtGui.QApplication.translate("MainWindow", "Number of Connections", None, QtGui.QApplication.UnicodeUTF8)) + self.labelTotalConnections.setText(QtGui.QApplication.translate("MainWindow", "Total connections: 0", None, QtGui.QApplication.UnicodeUTF8)) + self.labelStartupTime.setText(QtGui.QApplication.translate("MainWindow", "Since startup at asdf:", None, QtGui.QApplication.UnicodeUTF8)) + self.labelMessageCount.setText(QtGui.QApplication.translate("MainWindow", "Processed 0 person-to-person messages.", None, QtGui.QApplication.UnicodeUTF8)) + self.labelPubkeyCount.setText(QtGui.QApplication.translate("MainWindow", "Processed 0 public keys.", None, QtGui.QApplication.UnicodeUTF8)) + self.labelBroadcastCount.setText(QtGui.QApplication.translate("MainWindow", "Processed 0 broadcasts.", None, QtGui.QApplication.UnicodeUTF8)) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.networkstatus), QtGui.QApplication.translate("MainWindow", "Network Status", None, QtGui.QApplication.UnicodeUTF8)) + self.menuFile.setTitle(QtGui.QApplication.translate("MainWindow", "File", None, QtGui.QApplication.UnicodeUTF8)) + self.menuSettings.setTitle(QtGui.QApplication.translate("MainWindow", "Settings", None, QtGui.QApplication.UnicodeUTF8)) + self.menuHelp.setTitle(QtGui.QApplication.translate("MainWindow", "Help", None, QtGui.QApplication.UnicodeUTF8)) + self.actionImport_keys.setText(QtGui.QApplication.translate("MainWindow", "Import keys", None, QtGui.QApplication.UnicodeUTF8)) + self.actionManageKeys.setText(QtGui.QApplication.translate("MainWindow", "Manage keys", None, QtGui.QApplication.UnicodeUTF8)) + self.actionExit.setText(QtGui.QApplication.translate("MainWindow", "Quit", None, QtGui.QApplication.UnicodeUTF8)) + self.actionHelp.setText(QtGui.QApplication.translate("MainWindow", "Help", None, QtGui.QApplication.UnicodeUTF8)) + self.actionAbout.setText(QtGui.QApplication.translate("MainWindow", "About", None, QtGui.QApplication.UnicodeUTF8)) + self.actionSettings.setText(QtGui.QApplication.translate("MainWindow", "Settings", None, QtGui.QApplication.UnicodeUTF8)) + self.actionRegenerateDeterministicAddresses.setText(QtGui.QApplication.translate("MainWindow", "Regenerate deterministic addresses", None, QtGui.QApplication.UnicodeUTF8)) + self.actionDeleteAllTrashedMessages.setText(QtGui.QApplication.translate("MainWindow", "Delete all trashed messages", None, QtGui.QApplication.UnicodeUTF8)) + +import bitmessage_icons_rc diff --git a/src/bitmessageui.ui b/src/bitmessageui.ui new file mode 100644 index 00000000..bba6d3a5 --- /dev/null +++ b/src/bitmessageui.ui @@ -0,0 +1,1092 @@ + + + MainWindow + + + + 0 + 0 + 795 + 561 + + + + Bitmessage + + + + :/newPrefix/images/can-icon-24px.png:/newPrefix/images/can-icon-24px.png + + + QTabWidget::Rounded + + + + + 0 + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 9 + + + + QTabWidget::North + + + QTabWidget::Rounded + + + 0 + + + + + :/newPrefix/images/inbox.png:/newPrefix/images/inbox.png + + + Inbox + + + + + + true + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + true + + + false + + + true + + + 200 + + + false + + + 27 + + + false + + + true + + + false + + + 26 + + + + To + + + + + From + + + + + Subject + + + + + Received + + + + + + + + + 0 + 500 + + + + + + + + + + :/newPrefix/images/send.png:/newPrefix/images/send.png + + + Send + + + + + + + 7 + + + + Load from Address book + + + + + + + Message: + + + + + + + + 300 + 0 + + + + + + + + Subject: + + + + + + + + + + + + + + Send to one or more specific people + + + true + + + + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> + + + + + + + To: + + + + + + + From: + + + + + + + Qt::Vertical + + + + 20 + 297 + + + + + + + + Broadcast to everyone who is subscribed to your address + + + + + + + + + + + + + + Qt::Horizontal + + + + 20 + 20 + + + + + + + + Send + + + + + + + true + + + + 0 + 0 + + + + Be aware that broadcasts are only encrypted with your address. Anyone who knows your address can read them. + + + -1 + + + + + + + + + :/newPrefix/images/sent.png:/newPrefix/images/sent.png + + + Sent + + + + + + QAbstractItemView::DragDrop + + + true + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + true + + + false + + + true + + + 130 + + + false + + + false + + + true + + + false + + + false + + + + To + + + + + From + + + + + Subject + + + + + Status + + + + + + + + + + + + + :/newPrefix/images/identities.png:/newPrefix/images/identities.png + + + Your Identities + + + + + + New + + + + + + + Qt::Horizontal + + + + 689 + 20 + + + + + + + + QFrame::Sunken + + + 1 + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + true + + + true + + + 346 + + + 52 + + + true + + + true + + + false + + + 26 + + + false + + + false + + + + Label (not shown to anyone) + + + + true + + + + + + Address + + + + + Stream + + + + + + + + + + :/newPrefix/images/subscriptions.png:/newPrefix/images/subscriptions.png + + + Subscriptions + + + + + + Here you can subscribe to 'broadcast messages' that are sent by other users. Messages will appear in your Inbox. Addresses here override those on the Blacklist tab. + + + true + + + + + + + Add new Subscription + + + + + + + Qt::Horizontal + + + + 689 + 20 + + + + + + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + true + + + true + + + 400 + + + false + + + false + + + true + + + false + + + + Label + + + + + Address + + + + + + + + + + :/newPrefix/images/addressbook.png:/newPrefix/images/addressbook.png + + + Address Book + + + + + + The Address book is useful for adding names or labels to other people's Bitmessage addresses so that you can recognize them more easily in your inbox. You can add entries here using the 'Add' button, or from your inbox by right-clicking on a message. + + + true + + + + + + + Add new entry + + + + + + + Qt::Horizontal + + + + 689 + 20 + + + + + + + + true + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + true + + + true + + + 400 + + + false + + + true + + + false + + + + Name or Label + + + + + Address + + + + + + + + + + :/newPrefix/images/blacklist.png:/newPrefix/images/blacklist.png + + + Blacklist + + + + + + Use a Blacklist (Allow all incoming messages except those on the Blacklist) + + + true + + + + + + + Use a Whitelist (Block all incoming messages except those on the Whitelist) + + + + + + + Add new entry + + + + + + + Qt::Horizontal + + + + 689 + 20 + + + + + + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + true + + + true + + + 400 + + + false + + + false + + + true + + + false + + + + Name or Label + + + + + Address + + + + + + + + + + :/newPrefix/images/networkstatus.png:/newPrefix/images/networkstatus.png + + + Network Status + + + + + 680 + 440 + 21 + 23 + + + + + + + + :/newPrefix/images/redicon.png:/newPrefix/images/redicon.png + + + true + + + + + + 20 + 70 + 241 + 241 + + + + + + + + + 212 + 208 + 200 + + + + + + + + + 212 + 208 + 200 + + + + + + + + + 212 + 208 + 200 + + + + + + + + QFrame::Box + + + QFrame::Plain + + + false + + + true + + + QAbstractItemView::NoSelection + + + true + + + false + + + true + + + false + + + + Stream Number + + + + + Number of Connections + + + + + + + 20 + 30 + 401 + 16 + + + + Total connections: 0 + + + + + + 320 + 110 + 331 + 20 + + + + Since startup at asdf: + + + + + + 350 + 130 + 361 + 16 + + + + Processed 0 person-to-person messages. + + + + + + 350 + 170 + 331 + 16 + + + + Processed 0 public keys. + + + + + + 350 + 150 + 351 + 16 + + + + Processed 0 broadcasts. + + + + + + + + + + + 0 + 0 + 795 + 18 + + + + + File + + + + + + + + + Settings + + + + + + Help + + + + + + + + + + + + 16777215 + 22 + + + + + + Import keys + + + + + false + + + true + + + Manage keys + + + + + Quit + + + + + Help + + + + + About + + + + + Settings + + + + + Regenerate deterministic addresses + + + + + Delete all trashed messages + + + + + tabWidget + tableWidgetInbox + textEditInboxMessage + radioButtonSpecific + radioButtonBroadcast + comboBoxSendFrom + lineEditTo + pushButtonLoadFromAddressBook + lineEditSubject + textEditMessage + pushButtonSend + tableWidgetSent + textEditSentMessage + pushButtonNewAddress + tableWidgetYourIdentities + pushButtonAddSubscription + tableWidgetSubscriptions + pushButtonAddAddressBook + tableWidgetAddressBook + radioButtonBlacklist + radioButtonWhitelist + pushButtonAddBlacklist + tableWidgetBlacklist + tableWidgetConnectionCount + pushButtonStatusIcon + + + + + + + radioButtonSpecific + toggled(bool) + lineEditTo + setEnabled(bool) + + + 121 + 60 + + + 175 + 147 + + + + + radioButtonSpecific + clicked(bool) + labelSendBroadcastWarning + hide() + + + 95 + 59 + + + 129 + 528 + + + + + radioButtonBroadcast + clicked() + labelSendBroadcastWarning + show() + + + 108 + 84 + + + 177 + 519 + + + + + diff --git a/src/bitmsghash/Makefile b/src/bitmsghash/Makefile deleted file mode 100644 index 7a494c39..00000000 --- a/src/bitmsghash/Makefile +++ /dev/null @@ -1,24 +0,0 @@ -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 -endif - -all: bitmsghash.so - -powtest: - ./testpow.py - -bitmsghash.so: bitmsghash.o - ${CXX} bitmsghash.o -shared -fPIC -lcrypto $(LDFLAGS) - -bitmsghash.o: - ${CXX} -Wall -O3 -march=native -fPIC $(CCFLAGS) -c bitmsghash.cpp - -clean: - rm -f bitmsghash.o bitmsghash.so bitmsghash*.dll 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.cl b/src/bitmsghash/bitmsghash.cl deleted file mode 100644 index 3c8c21a5..00000000 --- a/src/bitmsghash/bitmsghash.cl +++ /dev/null @@ -1,276 +0,0 @@ -/* -* This is based on the John The Ripper SHA512 code, modified for double SHA512 and for use as a miner in Bitmessage. -* This software is originally Copyright (c) 2012 Myrice -* and it is hereby released to the general public under the following terms: -* Redistribution and use in source and binary forms, with or without modification, are permitted. -*/ - -#ifdef cl_khr_byte_addressable_store -#pragma OPENCL EXTENSION cl_khr_byte_addressable_store : disable -#endif - -#define uint8_t unsigned char -#define uint32_t unsigned int -#define uint64_t unsigned long -#define SALT_SIZE 0 - -#define BINARY_SIZE 8 -#define FULL_BINARY_SIZE 64 - - -#define PLAINTEXT_LENGTH 72 - -#define CIPHERTEXT_LENGTH 128 - - -/// Warning: This version of SWAP64(n) is slow and avoid bugs on AMD GPUs(7970) -// #define SWAP64(n) as_ulong(as_uchar8(n).s76543210) - -#define SWAP64(n) \ - (((n) << 56) \ - | (((n) & 0xff00) << 40) \ - | (((n) & 0xff0000) << 24) \ - | (((n) & 0xff000000) << 8) \ - | (((n) >> 8) & 0xff000000) \ - | (((n) >> 24) & 0xff0000) \ - | (((n) >> 40) & 0xff00) \ - | ((n) >> 56)) - - - -#define rol(x,n) ((x << n) | (x >> (64-n))) -#define ror(x,n) ((x >> n) | (x << (64-n))) -#define Ch(x,y,z) ((x & y) ^ ( (~x) & z)) -#define Maj(x,y,z) ((x & y) ^ (x & z) ^ (y & z)) -#define Sigma0(x) ((ror(x,28)) ^ (ror(x,34)) ^ (ror(x,39))) -#define Sigma1(x) ((ror(x,14)) ^ (ror(x,18)) ^ (ror(x,41))) -#define sigma0(x) ((ror(x,1)) ^ (ror(x,8)) ^(x>>7)) -#define sigma1(x) ((ror(x,19)) ^ (ror(x,61)) ^(x>>6)) - - - -typedef struct { // notice memory align problem - uint64_t H[8]; - uint32_t buffer[32]; //1024 bits - uint32_t buflen; -} sha512_ctx; - -typedef struct { - uint64_t target; - char v[PLAINTEXT_LENGTH+1]; -} sha512_key; - - -/* Macros for reading/writing chars from int32's */ -#define PUTCHAR(buf, index, val) (buf)[(index)>>2] = ((buf)[(index)>>2] & ~(0xffU << (((index) & 3) << 3))) + ((val) << (((index) & 3) << 3)) - - -__constant uint64_t k[] = { - 0x428a2f98d728ae22UL, 0x7137449123ef65cdUL, 0xb5c0fbcfec4d3b2fUL, - 0xe9b5dba58189dbbcUL, - 0x3956c25bf348b538UL, 0x59f111f1b605d019UL, 0x923f82a4af194f9bUL, - 0xab1c5ed5da6d8118UL, - 0xd807aa98a3030242UL, 0x12835b0145706fbeUL, 0x243185be4ee4b28cUL, - 0x550c7dc3d5ffb4e2UL, - 0x72be5d74f27b896fUL, 0x80deb1fe3b1696b1UL, 0x9bdc06a725c71235UL, - 0xc19bf174cf692694UL, - 0xe49b69c19ef14ad2UL, 0xefbe4786384f25e3UL, 0x0fc19dc68b8cd5b5UL, - 0x240ca1cc77ac9c65UL, - 0x2de92c6f592b0275UL, 0x4a7484aa6ea6e483UL, 0x5cb0a9dcbd41fbd4UL, - 0x76f988da831153b5UL, - 0x983e5152ee66dfabUL, 0xa831c66d2db43210UL, 0xb00327c898fb213fUL, - 0xbf597fc7beef0ee4UL, - 0xc6e00bf33da88fc2UL, 0xd5a79147930aa725UL, 0x06ca6351e003826fUL, - 0x142929670a0e6e70UL, - 0x27b70a8546d22ffcUL, 0x2e1b21385c26c926UL, 0x4d2c6dfc5ac42aedUL, - 0x53380d139d95b3dfUL, - 0x650a73548baf63deUL, 0x766a0abb3c77b2a8UL, 0x81c2c92e47edaee6UL, - 0x92722c851482353bUL, - 0xa2bfe8a14cf10364UL, 0xa81a664bbc423001UL, 0xc24b8b70d0f89791UL, - 0xc76c51a30654be30UL, - 0xd192e819d6ef5218UL, 0xd69906245565a910UL, 0xf40e35855771202aUL, - 0x106aa07032bbd1b8UL, - 0x19a4c116b8d2d0c8UL, 0x1e376c085141ab53UL, 0x2748774cdf8eeb99UL, - 0x34b0bcb5e19b48a8UL, - 0x391c0cb3c5c95a63UL, 0x4ed8aa4ae3418acbUL, 0x5b9cca4f7763e373UL, - 0x682e6ff3d6b2b8a3UL, - 0x748f82ee5defb2fcUL, 0x78a5636f43172f60UL, 0x84c87814a1f0ab72UL, - 0x8cc702081a6439ecUL, - 0x90befffa23631e28UL, 0xa4506cebde82bde9UL, 0xbef9a3f7b2c67915UL, - 0xc67178f2e372532bUL, - 0xca273eceea26619cUL, 0xd186b8c721c0c207UL, 0xeada7dd6cde0eb1eUL, - 0xf57d4f7fee6ed178UL, - 0x06f067aa72176fbaUL, 0x0a637dc5a2c898a6UL, 0x113f9804bef90daeUL, - 0x1b710b35131c471bUL, - 0x28db77f523047d84UL, 0x32caab7b40c72493UL, 0x3c9ebe0a15c9bebcUL, - 0x431d67c49c100d4cUL, - 0x4cc5d4becb3e42b6UL, 0x597f299cfc657e2aUL, 0x5fcb6fab3ad6faecUL, - 0x6c44198c4a475817UL, -}; - - - -static void setup_ctx(sha512_ctx* ctx, const char * password, uint8_t pass_len) -{ - uint32_t* b32 = ctx->buffer; - - //set password to buffer - for (uint32_t i = 0; i < pass_len; i++) { - PUTCHAR(b32,i,password[i]); - } - ctx->buflen = pass_len; - - //append 1 to ctx buffer - uint32_t length = ctx->buflen; - PUTCHAR(b32, length, 0x80); - while((++length & 3) != 0) { - PUTCHAR(b32, length, 0); - } - - uint32_t* buffer32 = b32+(length>>2); - for(uint32_t i = length; i < 128; i+=4) {// append 0 to 128 - *buffer32++=0; - } - - //append length to buffer - uint64_t *buffer64 = (uint64_t *)ctx->buffer; - buffer64[15] = SWAP64(((uint64_t) ctx->buflen) * 8); -} - -inline uint64_t sha512(char* password) -{ - __private sha512_ctx ctx; - setup_ctx(&ctx, password, 72); - // sha512 main` - int i; - - uint64_t a = 0x6a09e667f3bcc908UL; - uint64_t b = 0xbb67ae8584caa73bUL; - uint64_t c = 0x3c6ef372fe94f82bUL; - uint64_t d = 0xa54ff53a5f1d36f1UL; - uint64_t e = 0x510e527fade682d1UL; - uint64_t f = 0x9b05688c2b3e6c1fUL; - uint64_t g = 0x1f83d9abfb41bd6bUL; - uint64_t h = 0x5be0cd19137e2179UL; - - __private uint64_t w[16]; - - uint64_t *data = (uint64_t *) ctx.buffer; - - for (i = 0; i < 16; i++) - w[i] = SWAP64(data[i]); - - uint64_t t1, t2; - for (i = 0; i < 16; i++) { - t1 = k[i] + w[i] + h + Sigma1(e) + Ch(e, f, g); - t2 = Maj(a, b, c) + Sigma0(a); - - h = g; - g = f; - f = e; - e = d + t1; - d = c; - c = b; - b = a; - a = t1 + t2; - } - - for (i = 16; i < 80; i++) { - - w[i & 15] =sigma1(w[(i - 2) & 15]) + sigma0(w[(i - 15) & 15]) + w[(i -16) & 15] + w[(i - 7) & 15]; - t1 = k[i] + w[i & 15] + h + Sigma1(e) + Ch(e, f, g); - t2 = Maj(a, b, c) + Sigma0(a); - - h = g; - g = f; - f = e; - e = d + t1; - d = c; - c = b; - b = a; - a = t1 + t2; - } - - uint64_t finalhash[8]; - - finalhash[0] = SWAP64(a + 0x6a09e667f3bcc908UL); - finalhash[1] = SWAP64(b + 0xbb67ae8584caa73bUL); - finalhash[2] = SWAP64(c + 0x3c6ef372fe94f82bUL); - finalhash[3] = SWAP64(d + 0xa54ff53a5f1d36f1UL); - finalhash[4] = SWAP64(e + 0x510e527fade682d1UL); - finalhash[5] = SWAP64(f + 0x9b05688c2b3e6c1fUL); - finalhash[6] = SWAP64(g + 0x1f83d9abfb41bd6bUL); - finalhash[7] = SWAP64(h + 0x5be0cd19137e2179UL); - - setup_ctx(&ctx, (char*) finalhash, 64); - - a = 0x6a09e667f3bcc908UL; - b = 0xbb67ae8584caa73bUL; - c = 0x3c6ef372fe94f82bUL; - d = 0xa54ff53a5f1d36f1UL; - e = 0x510e527fade682d1UL; - f = 0x9b05688c2b3e6c1fUL; - g = 0x1f83d9abfb41bd6bUL; - h = 0x5be0cd19137e2179UL; - - data = (uint64_t *) ctx.buffer; - //((uint64_t*)ctx.buffer)[8] = SWAP64((uint64_t)0x80); - - for (i = 0; i < 16; i++) - w[i] = SWAP64(data[i]); - - for (i = 0; i < 16; i++) { - t1 = k[i] + w[i] + h + Sigma1(e) + Ch(e, f, g); - t2 = Maj(a, b, c) + Sigma0(a); - - h = g; - g = f; - f = e; - e = d + t1; - d = c; - c = b; - b = a; - a = t1 + t2; - } - - for (i = 16; i < 80; i++) { - - w[i & 15] =sigma1(w[(i - 2) & 15]) + sigma0(w[(i - 15) & 15]) + w[(i -16) & 15] + w[(i - 7) & 15]; - t1 = k[i] + w[i & 15] + h + Sigma1(e) + Ch(e, f, g); - t2 = Maj(a, b, c) + Sigma0(a); - - h = g; - g = f; - f = e; - e = d + t1; - d = c; - c = b; - b = a; - a = t1 + t2; - } - return SWAP64(a + 0x6a09e667f3bcc908UL); -} - -__kernel void kernel_sha512(__global const sha512_key *password,__global uint64_t *hash, uint64_t start) -{ - uint64_t idx = get_global_id(0); - if (idx == 0 && start == 0) { - *hash = 0; - } - uint64_t winval; - - uint64_t junk[9]; - - __global uint64_t * source = (__global uint64_t*) password->v; - for (int i = 1; i < 9; i++) { - junk[i] = source[i]; - } - - junk[0] = SWAP64(idx + (start)); - - winval = sha512((char*)junk); - if (SWAP64(winval) < password->target) { - *hash = SWAP64(junk[0]); - } -} - diff --git a/src/bitmsghash/bitmsghash.cpp b/src/bitmsghash/bitmsghash.cpp deleted file mode 100644 index 24775475..00000000 --- a/src/bitmsghash/bitmsghash.cpp +++ /dev/null @@ -1,165 +0,0 @@ -// 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" -#define uint64_t unsigned __int64 -#else -#include -#include -#include -#endif -#include -#include -#include -#if defined(__APPLE__) || defined(__FreeBSD__) || defined (__DragonFly__) || defined (__OpenBSD__) || defined (__NetBSD__) -#include -#include -#endif - -#include "openssl/sha.h" - -#define HASH_SIZE 64 -#define BUFLEN 16384 - -#if defined(__GNUC__) - #define EXPORT __attribute__ ((__visibility__("default"))) -#elif defined(_WIN32) - #define EXPORT __declspec(dllexport) -#endif - -#ifndef __APPLE__ -#define ntohll(x) ( ( (uint64_t)(ntohl( (unsigned int)((x << 32) >> 32) )) << 32) | ntohl( ((unsigned int)(x >> 32)) ) ) -#endif - -unsigned long long max_val; -unsigned char *initialHash; -unsigned long long successval = 0; -unsigned int numthreads = 0; - -#ifdef _WIN32 -DWORD WINAPI threadfunc(LPVOID param) { -#else -void * threadfunc(void* param) { -#endif - unsigned int incamt = *((unsigned int*)param); - SHA512_CTX sha; - unsigned char buf[HASH_SIZE + sizeof(uint64_t)] = { 0 }; - unsigned char output[HASH_SIZE] = { 0 }; - - memcpy(buf + sizeof(uint64_t), initialHash, HASH_SIZE); - - unsigned long long tmpnonce = incamt; - unsigned long long * nonce = (unsigned long long *)buf; - unsigned long long * hash = (unsigned long long *)output; - while (successval == 0) { - tmpnonce += numthreads; - - (*nonce) = ntohll(tmpnonce); /* increment nonce */ - SHA512_Init(&sha); - SHA512_Update(&sha, buf, HASH_SIZE + sizeof(uint64_t)); - SHA512_Final(output, &sha); - SHA512_Init(&sha); - SHA512_Update(&sha, output, HASH_SIZE); - SHA512_Final(output, &sha); - - if (ntohll(*hash) < max_val) { - successval = tmpnonce; - } - } -#ifdef _WIN32 - return 0; -#else - return NULL; -#endif -} - -void getnumthreads() -{ -#ifdef _WIN32 - 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; -#endif - size_t len = sizeof(dwProcessAffinity); - if (numthreads > 0) - return; -#ifdef _WIN32 - 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; - else if (sysctlbyname("hw.ncpu", &core_count, &len, 0, 0) == 0) - numthreads = core_count; -#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 - if (dwProcessAffinity & (1 << i)) -#endif - numthreads++; - if (numthreads == 0) // something failed - numthreads = 1; - printf("Number of threads: %i\n", (int)numthreads); -} - -extern "C" EXPORT unsigned long long BitmessagePOW(unsigned char * starthash, unsigned long long target) -{ - successval = 0; - max_val = target; - getnumthreads(); - initialHash = (unsigned char *)starthash; -# ifdef _WIN32 - HANDLE* threads = (HANDLE*)calloc(sizeof(HANDLE), numthreads); -# else - pthread_t* threads = (pthread_t*)calloc(sizeof(pthread_t), numthreads); - struct sched_param schparam; - schparam.sched_priority = 0; -# endif - unsigned int *threaddata = (unsigned int *)calloc(sizeof(unsigned int), numthreads); - for (unsigned int i = 0; i < numthreads; i++) { - threaddata[i] = i; -# ifdef _WIN32 - threads[i] = CreateThread(NULL, 0, threadfunc, (LPVOID)&threaddata[i], 0, NULL); - SetThreadPriority(threads[i], THREAD_PRIORITY_IDLE); -# else - pthread_create(&threads[i], NULL, threadfunc, (void*)&threaddata[i]); -# ifdef __linux__ - pthread_setschedparam(threads[i], SCHED_IDLE, &schparam); -# else - pthread_setschedparam(threads[i], SCHED_RR, &schparam); -# endif -# endif - } -# ifdef _WIN32 - WaitForMultipleObjects(numthreads, threads, TRUE, INFINITE); -# else - for (unsigned int i = 0; i < numthreads; i++) { - pthread_join(threads[i], NULL); - } -# endif - free(threads); - free(threaddata); - return successval; -} 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 deleted file mode 100644 index d83e9b9b..00000000 --- a/src/build_osx.py +++ /dev/null @@ -1,39 +0,0 @@ -"""Building osx.""" -import os -from glob import glob -from PyQt4 import QtCore -from setuptools import setup - -name = "Bitmessage" -version = os.getenv("PYBITMESSAGEVERSION", "custom") -mainscript = ["bitmessagemain.py"] - -DATA_FILES = [ - ('', ['sslkeys', 'images', 'default.ini']), - ('sql', glob('sql/*.sql')), - ('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'))), -] - -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" - ) - ) -) diff --git a/src/class_addressGenerator.py b/src/class_addressGenerator.py deleted file mode 100644 index 06a0521a..00000000 --- a/src/class_addressGenerator.py +++ /dev/null @@ -1,408 +0,0 @@ -""" -A thread for creating addresses -""" -import hashlib -import time -from binascii import hexlify - -from six.moves import configparser, queue - -import defaults -import highlevelcrypto -import queues -import shared -import state -from addresses import decodeAddress, encodeAddress, encodeVarint -from bmconfigparser import config -from fallback import RIPEMD160Hash -from network import StoppableThread -from pyelliptic import arithmetic -from pyelliptic.openssl import OpenSSL -from tr import _translate - - -class AddressGeneratorException(Exception): - '''Generic AddressGenerator exception''' - pass - - -class addressGenerator(StoppableThread): - """A thread for creating addresses""" - - name = "addressGenerator" - - 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') - - super(addressGenerator, self).stopThread() - - def run(self): - """ - Process the requests for addresses generation - from `.queues.addressGeneratorQueue` - """ - # pylint: disable=too-many-locals,too-many-branches,too-many-statements - # pylint: disable=too-many-nested-blocks - - while state.shutdown == 0: - queueValue = queues.addressGeneratorQueue.get() - nonceTrialsPerByte = 0 - payloadLengthExtraBytes = 0 - live = True - if queueValue[0] == 'createChan': - command, addressVersionNumber, streamNumber, label, \ - deterministicPassphrase, live = queueValue - eighteenByteRipe = False - numberOfAddressesToMake = 1 - numberOfNullBytesDemandedOnFrontOfRipeHash = 1 - elif queueValue[0] == 'joinChan': - command, chanAddress, label, deterministicPassphrase, \ - live = queueValue - eighteenByteRipe = False - addressVersionNumber = decodeAddress(chanAddress)[1] - streamNumber = decodeAddress(chanAddress)[2] - numberOfAddressesToMake = 1 - numberOfNullBytesDemandedOnFrontOfRipeHash = 1 - elif len(queueValue) == 7: - command, addressVersionNumber, streamNumber, label, \ - numberOfAddressesToMake, deterministicPassphrase, \ - eighteenByteRipe = queueValue - - numberOfNullBytesDemandedOnFrontOfRipeHash = \ - config.safeGetInt( - 'bitmessagesettings', - 'numberofnullbytesonaddress', - 2 if eighteenByteRipe else 1 - ) - elif len(queueValue) == 9: - command, addressVersionNumber, streamNumber, label, \ - numberOfAddressesToMake, deterministicPassphrase, \ - eighteenByteRipe, nonceTrialsPerByte, \ - payloadLengthExtraBytes = queueValue - - numberOfNullBytesDemandedOnFrontOfRipeHash = \ - config.safeGetInt( - 'bitmessagesettings', - 'numberofnullbytesonaddress', - 2 if eighteenByteRipe else 1 - ) - 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) - 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) - if nonceTrialsPerByte == 0: - nonceTrialsPerByte = config.getint( - 'bitmessagesettings', 'defaultnoncetrialsperbyte') - if nonceTrialsPerByte < \ - defaults.networkDefaultProofOfWorkNonceTrialsPerByte: - nonceTrialsPerByte = \ - defaults.networkDefaultProofOfWorkNonceTrialsPerByte - if payloadLengthExtraBytes == 0: - payloadLengthExtraBytes = config.getint( - 'bitmessagesettings', 'defaultpayloadlengthextrabytes') - if payloadLengthExtraBytes < \ - defaults.networkDefaultPayloadLengthExtraBytes: - payloadLengthExtraBytes = \ - defaults.networkDefaultPayloadLengthExtraBytes - if command == 'createRandomAddress': - queues.UISignalQueue.put(( - 'updateStatusBar', - _translate( - "MainWindow", "Generating one new address") - )) - # This next section is a little bit strange. We're going - # to generate keys over and over until we find one - # that starts with either \x00 or \x00\x00. Then when - # we pack them into a Bitmessage address, we won't store - # the \x00 or \x00\x00 bytes thus making the address shorter. - startTime = time.time() - numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix = 0 - potentialPrivSigningKey = OpenSSL.rand(32) - potentialPubSigningKey = highlevelcrypto.pointMult( - potentialPrivSigningKey) - while True: - numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix += 1 - potentialPrivEncryptionKey = OpenSSL.rand(32) - potentialPubEncryptionKey = highlevelcrypto.pointMult( - potentialPrivEncryptionKey) - sha = hashlib.new('sha512') - sha.update( - potentialPubSigningKey + potentialPubEncryptionKey) - ripe = RIPEMD160Hash(sha.digest()).digest() - if ( - ripe[:numberOfNullBytesDemandedOnFrontOfRipeHash] - == b'\x00' * numberOfNullBytesDemandedOnFrontOfRipeHash - ): - break - self.logger.info( - 'Generated address with ripe digest: %s', hexlify(ripe)) - 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)) - except ZeroDivisionError: - # The user must have a pretty fast computer. - # time.time() - startTime equaled zero. - pass - address = encodeAddress( - addressVersionNumber, streamNumber, ripe) - - # An excellent way for us to store our keys - # is in Wallet Import Format. Let us convert now. - # https://en.bitcoin.it/wiki/Wallet_import_format - privSigningKey = b'\x80' + potentialPrivSigningKey - checksum = hashlib.sha256(hashlib.sha256( - privSigningKey).digest()).digest()[0:4] - privSigningKeyWIF = arithmetic.changebase( - privSigningKey + checksum, 256, 58) - - privEncryptionKey = b'\x80' + potentialPrivEncryptionKey - checksum = hashlib.sha256(hashlib.sha256( - privEncryptionKey).digest()).digest()[0:4] - privEncryptionKeyWIF = arithmetic.changebase( - privEncryptionKey + checksum, 256, 58) - - config.add_section(address) - config.set(address, 'label', label) - config.set(address, 'enabled', 'true') - config.set(address, 'decoy', 'false') - config.set(address, 'noncetrialsperbyte', str( - nonceTrialsPerByte)) - config.set(address, 'payloadlengthextrabytes', str( - payloadLengthExtraBytes)) - config.set( - address, 'privsigningkey', privSigningKeyWIF.decode()) - config.set( - address, 'privencryptionkey', - privEncryptionKeyWIF.decode()) - config.save() - - # The API and the join and create Chan functionality - # both need information back from the address generator. - queues.apiAddressGeneratorReturnQueue.put(address) - - queues.UISignalQueue.put(( - 'updateStatusBar', - _translate( - "MainWindow", - "Done generating address. Doing work necessary" - " to broadcast it...") - )) - queues.UISignalQueue.put(('writeNewAddressToTable', ( - label, address, streamNumber))) - shared.reloadMyAddressHashes() - if addressVersionNumber == 3: - queues.workerQueue.put(( - 'sendOutOrStoreMyV3Pubkey', ripe)) - elif addressVersionNumber == 4: - queues.workerQueue.put(( - 'sendOutOrStoreMyV4Pubkey', address)) - - elif command in ( - 'createDeterministicAddresses', 'createChan', - 'getDeterministicAddress', 'joinChan' - ): - if not deterministicPassphrase: - self.logger.warning( - 'You are creating deterministic' - ' address(es) using a blank passphrase.' - ' Bitmessage will do it but it is rather stupid.') - if command == 'createDeterministicAddresses': - queues.UISignalQueue.put(( - 'updateStatusBar', - _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 = [] - - 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 - # \x00\x00 bytes thus making the address shorter. - startTime = time.time() - numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix = 0 - while True: - numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix += 1 - potentialPrivSigningKey = hashlib.sha512( - deterministicPassphrase - + encodeVarint(signingKeyNonce) - ).digest()[:32] - potentialPrivEncryptionKey = hashlib.sha512( - deterministicPassphrase - + encodeVarint(encryptionKeyNonce) - ).digest()[:32] - potentialPubSigningKey = highlevelcrypto.pointMult( - potentialPrivSigningKey) - potentialPubEncryptionKey = highlevelcrypto.pointMult( - potentialPrivEncryptionKey) - signingKeyNonce += 2 - encryptionKeyNonce += 2 - sha = hashlib.new('sha512') - sha.update( - potentialPubSigningKey + potentialPubEncryptionKey) - ripe = RIPEMD160Hash(sha.digest()).digest() - if ( - ripe[:numberOfNullBytesDemandedOnFrontOfRipeHash] - == b'\x00' * numberOfNullBytesDemandedOnFrontOfRipeHash - ): - break - - self.logger.info( - 'Generated address with ripe digest: %s', hexlify(ripe)) - 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) - ) - except ZeroDivisionError: - # The user must have a pretty fast computer. - # time.time() - startTime equaled zero. - pass - address = encodeAddress( - addressVersionNumber, streamNumber, ripe) - - saveAddressToDisk = True - # 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') - saveAddressToDisk = False - if command == 'getDeterministicAddress': - saveAddressToDisk = False - - if saveAddressToDisk and live: - # An excellent way for us to store our keys is - # in Wallet Import Format. Let us convert now. - # https://en.bitcoin.it/wiki/Wallet_import_format - privSigningKey = b'\x80' + potentialPrivSigningKey - checksum = hashlib.sha256(hashlib.sha256( - privSigningKey).digest()).digest()[0:4] - privSigningKeyWIF = arithmetic.changebase( - privSigningKey + checksum, 256, 58) - - privEncryptionKey = b'\x80' + \ - potentialPrivEncryptionKey - checksum = hashlib.sha256(hashlib.sha256( - privEncryptionKey).digest()).digest()[0:4] - privEncryptionKeyWIF = arithmetic.changebase( - privEncryptionKey + checksum, 256, 58) - - try: - config.add_section(address) - addressAlreadyExists = False - except configparser.DuplicateSectionError: - 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) - )) - 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() - - queues.UISignalQueue.put(( - 'writeNewAddressToTable', - (label, address, str(streamNumber)) - )) - listOfNewAddressesToSendOutThroughTheAPI.append( - address) - shared.myECCryptorObjects[ripe] = \ - highlevelcrypto.makeCryptor( - hexlify(potentialPrivEncryptionKey)) - shared.myAddressesByHash[ripe] = address - tag = hashlib.sha512(hashlib.sha512( - encodeVarint(addressVersionNumber) - + encodeVarint(streamNumber) + ripe - ).digest()).digest()[32:] - shared.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)) - elif addressVersionNumber == 4: - queues.workerQueue.put(( - 'sendOutOrStoreMyV4Pubkey', address)) - queues.UISignalQueue.put(( - 'updateStatusBar', - _translate( - "MainWindow", "Done generating address") - )) - elif saveAddressToDisk and not live \ - and not config.has_section(address): - listOfNewAddressesToSendOutThroughTheAPI.append( - address) - - # Done generating addresses. - if command in ( - 'createDeterministicAddresses', 'createChan', 'joinChan' - ): - queues.apiAddressGeneratorReturnQueue.put( - listOfNewAddressesToSendOutThroughTheAPI) - elif command == 'getDeterministicAddress': - queues.apiAddressGeneratorReturnQueue.put(address) - else: - raise AddressGeneratorException( - "Error in the addressGenerator thread. Thread was" - + " given a command it could not understand: " + command) - queues.addressGeneratorQueue.task_done() diff --git a/src/class_objectProcessor.py b/src/class_objectProcessor.py deleted file mode 100644 index 8d2e12a8..00000000 --- a/src/class_objectProcessor.py +++ /dev/null @@ -1,1075 +0,0 @@ -""" -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 -from binascii import hexlify - -import helper_bitcoin -import helper_inbox -import helper_msgcoding -import helper_sent -import highlevelcrypto -import l10n -import protocol -import queues -import shared -import state -from addresses import ( - calculateInventoryHash, decodeAddress, decodeVarint, - encodeAddress, encodeVarint, varintDecodeError -) -from bmconfigparser import config -from fallback import RIPEMD160Hash -from helper_sql import ( - sql_ready, sql_timeout, SqlBulkExecute, sqlExecute, sqlQuery) -from inventory import Inventory -from network import knownnodes -from network.node import Peer -from tr import _translate - -logger = logging.getLogger('default') - - -class objectProcessor(threading.Thread): - """ - The objectProcessor thread, of which there is only one, receives network - objects (msg, broadcast, pubkey, getpubkey) from the receiveDataThreads. - """ - 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. - 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 = [] - - def run(self): - """Process the objects from `.queues.objectProcessorQueue`""" - while True: - objectType, data = queues.objectProcessorQueue.get() - - self.checkackdata(data) - - try: - if objectType == protocol.OBJECT_GETPUBKEY: - self.processgetpubkey(data) - elif objectType == protocol.OBJECT_PUBKEY: - self.processpubkey(data) - elif objectType == protocol.OBJECT_MSG: - self.processmsg(data) - elif objectType == protocol.OBJECT_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': - 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')) - 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) - - if state.shutdown: - # Wait just a moment for most of the connections to close - time.sleep(.5) - numberOfObjectsThatWereInTheObjectProcessorQueue = 0 - with SqlBulkExecute() as sql: - while queues.objectProcessorQueue.curSize > 0: - objectType, data = queues.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 - 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.') - 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]) - 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.') - - myAddress = '' - 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: - 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)) - if requestedTag in shared.myAddressesByTag: - myAddress = shared.myAddressesByTag[requestedTag] - - if myAddress == '': - logger.info('This getpubkey request is not for any of my keys.') - 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.') - 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.') - if requestedAddressVersionNumber == 2: - queues.workerQueue.put(('doPOWForMyV2Pubkey', requestedHash)) - elif requestedAddressVersionNumber == 3: - queues.workerQueue.put(('sendOutOrStoreMyV3Pubkey', requestedHash)) - elif requestedAddressVersionNumber == 4: - queues.workerQueue.put(('sendOutOrStoreMyV4Pubkey', myAddress)) - - def processpubkey(self, data): - """Process a pubkey object""" - pubkeyProcessingStartTime = time.time() - state.numberOfPubkeysProcessed += 1 - queues.UISignalQueue.put(( - 'updateNumberOfPubkeysProcessed', 'no data')) - 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 addressVersion == 0: - return logger.debug( - '(Within processpubkey) addressVersion of 0 doesn\'t' - ' make sense.') - if addressVersion > 4 or addressVersion == 1: - return logger.info( - 'This version of Bitmessage cannot handle version %s' - ' addresses.', addressVersion) - 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.') - readPosition += 4 - 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 - publicEncryptionKey = data[readPosition:readPosition + 64] - if len(publicEncryptionKey) < 64: - return logger.debug( - 'publicEncryptionKey length less than 64. Sanity check' - ' failed.') - readPosition += 64 - # The data we'll store in the pubkeys table. - dataToStore = data[20:readPosition] - sha = hashlib.new('sha512') - sha.update( - '\x04' + publicSigningKey + '\x04' + publicEncryptionKey) - ripe = RIPEMD160Hash(sha.digest()).digest() - - if logger.isEnabledFor(logging.DEBUG): - logger.debug( - 'within recpubkey, addressVersion: %s, streamNumber: %s' - '\nripe %s\npublicSigningKey in hex: %s' - '\npublicEncryptionKey in hex: %s', - addressVersion, streamNumber, hexlify(ripe), - hexlify(publicSigningKey), hexlify(publicEncryptionKey) - ) - - 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') - else: - 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.') - return - readPosition += 4 - publicSigningKey = '\x04' + data[readPosition:readPosition + 64] - readPosition += 64 - publicEncryptionKey = '\x04' + data[readPosition:readPosition + 64] - readPosition += 64 - specifiedNonceTrialsPerByteLength = decodeVarint( - data[readPosition:readPosition + 10])[1] - readPosition += specifiedNonceTrialsPerByteLength - specifiedPayloadLengthExtraBytesLength = decodeVarint( - data[readPosition:readPosition + 10])[1] - readPosition += specifiedPayloadLengthExtraBytesLength - endOfSignedDataPosition = readPosition - # The data we'll store in the pubkeys table. - dataToStore = data[20:readPosition] - signatureLength, signatureLengthLength = decodeVarint( - data[readPosition:readPosition + 10]) - readPosition += signatureLengthLength - signature = data[readPosition:readPosition + signatureLength] - 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 - - sha = hashlib.new('sha512') - sha.update(publicSigningKey + publicEncryptionKey) - ripe = RIPEMD160Hash(sha.digest()).digest() - - if logger.isEnabledFor(logging.DEBUG): - logger.debug( - 'within recpubkey, addressVersion: %s, streamNumber: %s' - '\nripe %s\npublicSigningKey in hex: %s' - '\npublicEncryptionKey in hex: %s', - addressVersion, streamNumber, hexlify(ripe), - hexlify(publicSigningKey), hexlify(publicEncryptionKey) - ) - - 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') - else: - 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.') - - 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.') - - # 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. - self.possibleNewPubkey(toAddress) - - # Display timing data - logger.debug( - 'Time required to process this pubkey: %s', - time.time() - pubkeyProcessingStartTime) - - def processmsg(self, data): - """Process a message object""" - messageProcessingStartTime = time.time() - state.numberOfMessagesProcessed += 1 - queues.UISignalQueue.put(( - 'updateNumberOfMessagesProcessed', 'no data')) - 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.') - readPosition += msgVersionLength - - streamNumberAsClaimedByMsg, streamNumberAsClaimedByMsgLength = \ - decodeVarint(data[readPosition:readPosition + 9]) - readPosition += streamNumberAsClaimedByMsgLength - inventoryHash = calculateInventoryHash(data) - initialDecryptionSuccessful = False - - # This is not an acknowledgement bound for me. See if it is a message - # bound for me by trying to decrypt it with my private keys. - - for key, cryptorObject in sorted( - shared.myECCryptorObjects.items(), - key=lambda x: random.random()): # nosec B311 - try: - # continue decryption attempts to avoid timing attacks - if initialDecryptionSuccessful: - 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 - initialDecryptionSuccessful = True - logger.info( - 'EC decryption successful using key associated' - ' with ripe hash: %s.', hexlify(key)) - except Exception: # nosec B110 - 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) - - # This is a message bound for me. - # Look up my address based on the RIPE hash. - toAddress = shared.myAddressesByHash[toRipe] - readPosition = 0 - sendersAddressVersionNumber, sendersAddressVersionNumberLength = \ - decodeVarint(decryptedData[readPosition:readPosition + 10]) - readPosition += sendersAddressVersionNumberLength - if sendersAddressVersionNumber == 0: - return logger.info( - 'Cannot understand sendersAddressVersionNumber = 0.' - ' Ignoring message.') - if sendersAddressVersionNumber > 4: - return logger.info( - 'Sender\'s address version number %s not yet supported.' - ' Ignoring message.', sendersAddressVersionNumber) - if len(decryptedData) < 170: - return logger.info( - 'Length of the unencrypted data is unreasonably short.' - ' Sanity check failed. Ignoring message.') - sendersStreamNumber, sendersStreamNumberLength = decodeVarint( - decryptedData[readPosition:readPosition + 10]) - if sendersStreamNumber == 0: - logger.info('sender\'s stream number is 0. Ignoring message.') - return - readPosition += sendersStreamNumberLength - readPosition += 4 - pubSigningKey = '\x04' + decryptedData[readPosition:readPosition + 64] - readPosition += 64 - pubEncryptionKey = '\x04' + decryptedData[readPosition:readPosition + 64] - readPosition += 64 - if sendersAddressVersionNumber >= 3: - requiredAverageProofOfWorkNonceTrialsPerByte, varintLength = \ - decodeVarint(decryptedData[readPosition:readPosition + 10]) - readPosition += varintLength - 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 - 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]) - ) - readPosition += 20 - messageEncodingType, messageEncodingTypeLength = decodeVarint( - decryptedData[readPosition:readPosition + 10]) - readPosition += messageEncodingTypeLength - messageLength, messageLengthLength = decodeVarint( - decryptedData[readPosition:readPosition + 10]) - readPosition += messageLengthLength - message = decryptedData[readPosition:readPosition + messageLength] - 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 - 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') - logger.debug('ECDSA verify passed') - if logger.isEnabledFor(logging.DEBUG): - logger.debug( - 'As a matter of intellectual curiosity, here is the Bitcoin' - ' address associated with the keys owned by the other person:' - ' %s ..and here is the testnet address: %s. The other person' - ' must take their private signing key from Bitmessage and' - ' import it into Bitcoin (or a service like Blockchain.info)' - ' for it to be of any use. Do not use this unless you know' - ' what you are doing.', - helper_bitcoin.calculateBitcoinAddressFromPubkey(pubSigningKey), - helper_bitcoin.calculateTestnetAddressFromPubkey(pubSigningKey) - ) - # Used to detect and ignore duplicate messages in our inbox - sigHash = hashlib.sha512( - hashlib.sha512(signature).digest()).digest()[32:] - - # calculate the fromRipe. - sha = hashlib.new('sha512') - sha.update(pubSigningKey + pubEncryptionKey) - ripe = RIPEMD160Hash(sha.digest()).digest() - fromAddress = encodeAddress( - sendersAddressVersionNumber, sendersStreamNumber, ripe) - - # Let's store the public key in case we want to reply to this - # person. - sqlExecute( - '''INSERT INTO pubkeys VALUES (?,?,?,?,?)''', - fromAddress, - sendersAddressVersionNumber, - 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( - toAddress, 'noncetrialsperbyte') - requiredPayloadLengthExtraBytes = 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': - queryreturn = sqlQuery( - "SELECT label FROM blacklist where address=? and enabled='1'", - fromAddress) - if queryreturn != []: - logger.info('Message ignored because address is in blacklist.') - - blockMessage = True - else: # We're using a whitelist - queryreturn = sqlQuery( - "SELECT label FROM whitelist where address=? and enabled='1'", - fromAddress) - if queryreturn == []: - logger.info( - 'Message ignored because address not in whitelist.') - blockMessage = True - - # toLabel = config.safeGet(toAddress, 'label', toAddress) - try: - decodedMessage = helper_msgcoding.MsgDecode( - messageEncodingType, message) - except helper_msgcoding.MsgDecodeException: - return - subject = decodedMessage.subject - body = decodedMessage.body - - # 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) - helper_inbox.insert(t) - - queues.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 - - # 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', '') - # 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. - toAddress = '[Broadcast subscribers]' - - ackdata = helper_sent.insert( - fromAddress=fromAddress, - status='broadcastqueued', - subject=subject, - message=message, - encoding=messageEncodingType) - - queues.UISignalQueue.put(( - 'displayNewSentMessage', ( - toAddress, '[Broadcast subscribers]', fromAddress, - subject, message, ackdata) - )) - queues.workerQueue.put(('sendbroadcast', '')) - - # Don't send ACK if invalid, blacklisted senders, invisible - # messages, disabled or chan - if ( - self.ackDataHasAValidHeader(ackData) and not blockMessage - and messageEncodingType != 0 - and not config.safeGetBoolean(toAddress, 'dontsendack') - and not config.safeGetBoolean(toAddress, 'chan') - ): - ackPayload = ackData[24:] - objectType, toStreamNumber, expiresTime = \ - protocol.decodeObjectParameters(ackPayload) - inventoryHash = calculateInventoryHash(ackPayload) - Inventory()[inventoryHash] = ( - objectType, toStreamNumber, ackPayload, expiresTime, b'') - queues.invQueue.put((toStreamNumber, inventoryHash)) - - # Display timing data - timeRequiredToAttemptToDecryptMessage = time.time( - ) - messageProcessingStartTime - self.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) - ) - - def processbroadcast(self, data): - """Process a broadcast object""" - messageProcessingStartTime = time.time() - state.numberOfBroadcastsProcessed += 1 - queues.UISignalQueue.put(( - 'updateNumberOfBroadcastsProcessed', 'no 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.' - ) - 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. - signedData = data[8:readPosition] - initialDecryptionSuccessful = False - for key, cryptorObject in sorted( - shared.MyECSubscriptionCryptorObjects.items(), - key=lambda x: random.random()): # nosec B311 - try: - # continue decryption attempts to avoid timing attacks - if initialDecryptionSuccessful: - 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 - 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) - 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) - elif broadcastVersion == 5: - embeddedTag = data[readPosition:readPosition + 32] - readPosition += 32 - if embeddedTag not in shared.MyECSubscriptionCryptorObjects: - 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] - 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) - # At this point this is a broadcast I have decrypted and am - # interested in. - readPosition = 0 - sendersAddressVersion, sendersAddressVersionLength = decodeVarint( - 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.' - ) - 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.' - ) - 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.' - ) - readPosition += sendersStreamLength - readPosition += 4 - sendersPubSigningKey = '\x04' + \ - decryptedData[readPosition:readPosition + 64] - readPosition += 64 - sendersPubEncryptionKey = '\x04' + \ - decryptedData[readPosition:readPosition + 64] - readPosition += 64 - if sendersAddressVersion >= 3: - requiredAverageProofOfWorkNonceTrialsPerByte, varintLength = \ - decodeVarint(decryptedData[readPosition:readPosition + 10]) - readPosition += varintLength - 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) - endOfPubkeyPosition = readPosition - - sha = hashlib.new('sha512') - sha.update(sendersPubSigningKey + sendersPubEncryptionKey) - calculatedRipe = RIPEMD160Hash(sha.digest()).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.' - ) - elif broadcastVersion == 5: - 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.' - ) - messageEncodingType, messageEncodingTypeLength = decodeVarint( - decryptedData[readPosition:readPosition + 9]) - if messageEncodingType == 0: - return - readPosition += messageEncodingTypeLength - messageLength, messageLengthLength = decodeVarint( - decryptedData[readPosition:readPosition + 9]) - readPosition += messageLengthLength - message = decryptedData[readPosition:readPosition + messageLength] - readPosition += messageLength - readPositionAtBottomOfMessage = readPosition - signatureLength, signatureLengthLength = decodeVarint( - decryptedData[readPosition:readPosition + 9]) - readPosition += signatureLengthLength - signature = decryptedData[ - readPosition:readPosition + signatureLength] - signedData += decryptedData[:readPositionAtBottomOfMessage] - if not highlevelcrypto.verify( - signedData, signature, hexlify(sendersPubSigningKey)): - logger.debug('ECDSA verify failed') - return - logger.debug('ECDSA verify passed') - # Used to detect and ignore duplicate messages in our inbox - sigHash = hashlib.sha512( - hashlib.sha512(signature).digest()).digest()[32:] - - fromAddress = encodeAddress( - sendersAddressVersion, sendersStream, calculatedRipe) - 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 (?,?,?,?,?)''', - fromAddress, - sendersAddressVersion, - decryptedData[:endOfPubkeyPosition], - 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) - - try: - decodedMessage = helper_msgcoding.MsgDecode( - messageEncodingType, message) - except helper_msgcoding.MsgDecodeException: - return - subject = decodedMessage.subject - body = decodedMessage.body - - toAddress = '[Broadcast subscribers]' - if helper_inbox.isMessageAlreadyInInbox(sigHash): - logger.info('This broadcast is already in our inbox. Ignoring it.') - return - t = (inventoryHash, toAddress, fromAddress, subject, int( - time.time()), body, 'inbox', messageEncodingType, 0, sigHash) - helper_inbox.insert(t) - - queues.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 - - # Display timing data - 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. - """ - - # 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] - self.sendMessages(address) - else: - logger.debug( - 'We don\'t need this pub key. We didn\'t ask for it.' - ' For address: %s', address) - # For address versions >= 4, we wait on a pubkey with the correct tag. - # Let us create the tag from the address and see if we were waiting - # for it. - elif addressVersion >= 4: - tag = hashlib.sha512(hashlib.sha512( - encodeVarint(addressVersion) + encodeVarint(streamNumber) - + ripe - ).digest()).digest()[32:] - if tag in state.neededPubkeys: - del state.neededPubkeys[tag] - self.sendMessages(address) - - @staticmethod - def sendMessages(address): - """ - This method is called by the `possibleNewPubkey` when it 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', '')) - - @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.') - return False - - magic, command, payloadLength, checksum = protocol.Header.unpack( - ackData[:protocol.Header.size]) - if magic != protocol.magic: - logger.info('Ackdata magic bytes were wrong. Not sending ackData.') - return False - payload = ackData[protocol.Header.size:] - if len(payload) != payloadLength: - logger.info( - 'ackData payload length doesn\'t match the payload length' - ' specified in the header. Not sending ackdata.') - 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. - return False - # test the checksum in the message. - if checksum != hashlib.sha512(payload).digest()[0:4]: - logger.info('ackdata checksum wrong. Not sending ackdata.') - return False - command = command.rstrip('\x00') - if command != 'object': - return False - return True - - @staticmethod - def addMailingListNameToSubject(subject, mailingListName): - """Adding mailingListName to subject""" - subject = subject.strip() - if subject[:3] == 'Re:' or subject[:3] == 'RE:': - subject = subject[3:].strip() - if '[' + mailingListName + ']' in subject: - return subject - return '[' + mailingListName + '] ' + subject diff --git a/src/class_singleCleaner.py b/src/class_singleCleaner.py deleted file mode 100644 index 24812b9e..00000000 --- a/src/class_singleCleaner.py +++ /dev/null @@ -1,188 +0,0 @@ -""" -The `singleCleaner` class is a timer-driven thread that cleans data structures -to free memory, resends messages when a remote node doesn't respond, and -sends pong messages to keep connections alive if the network isn't busy. - -It cleans these data structures in memory: - - inventory (moves data to the on-disk sql database) - - inventorySets (clears then reloads data out of sql database) - -It cleans these tables on the disk: - - inventory (clears expired objects) - - pubkeys (clears pubkeys older than 4 weeks old which we have not used - personally) - - knownNodes (clears addresses which have not been online for over 3 days) - -It resends messages when there has been no response: - - resends getpubkey messages in 5 days (then 10 days, then 20 days, etc...) - - resends msg messages in 5 days (then 10 days, then 20 days, etc...) - -""" - -import gc -import os -import time - -import queues -import state -from bmconfigparser import config -from helper_sql import sqlExecute, sqlQuery -from inventory import Inventory -from network import BMConnectionPool, knownnodes, StoppableThread -from tr import _translate - - -#: 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() - 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...)' - )) - Inventory().flush() - queues.UISignalQueue.put(('updateStatusBar', '')) - - # If we are running as a daemon then we are going to fill up the UI - # queue which will never be handled by a UI. We should clear it to - # save memory. - # FIXME redundant? - if state.thisapp.daemon or not state.enableGUI: - queues.UISignalQueue.queue.clear() - - tick = int(time.time()) - if timeWeLastClearedInventoryAndPubkeysTables < tick - 7380: - timeWeLastClearedInventoryAndPubkeysTables = tick - Inventory().clean() - queues.workerQueue.put(('sendOnionPeerObj', '')) - # pubkeys - sqlExecute( - "DELETE FROM pubkeys WHERE time?)", - tick, - tick - state.maximumLengthOfTimeToBotherResendingMessages - ) - for toAddress, ackData, status in queryreturn: - if status == 'awaitingpubkey': - self.resendPubkeyRequest(toAddress) - elif status == 'msgsent': - self.resendMsg(ackData) - - try: - # Cleanup knownnodes and handle possible severe exception - # while writing it to disk - if state.enableNetwork: - knownnodes.cleanupKnownNodes() - except Exception as err: - if "Errno 28" in str(err): - self.logger.fatal( - '(while writing knownnodes to disk)' - ' Alert: Your disk or data storage volume is full.' - ) - queues.UISignalQueue.put(( - 'alert', - (_translate("MainWindow", "Disk full"), - _translate( - "MainWindow", - 'Alert: Your disk or data storage volume' - ' is full. Bitmessage will now exit.'), - True) - )) - # FIXME redundant? - if state.thisapp.daemon or not state.enableGUI: - os._exit(1) # pylint: disable=protected-access - - # inv/object tracking - for connection in BMConnectionPool().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: - try: - del state.discoveredPeers[k] - except KeyError: - pass - # ..todo:: cleanup pending upload / download - - gc.collect() - - def resendPubkeyRequest(self, address): - """Resend pubkey request for address""" - self.logger.debug( - 'It has been a long time and we haven\'t heard a response to our' - ' getpubkey request. Sending again.' - ) - try: - # We need to take this entry out of the neededPubkeys structure - # because the queues.workerQueue checks to see whether the entry - # is already present and will not do the POW and send the message - # because it assumes that it has already done it recently. - del state.neededPubkeys[address] - except KeyError: - pass - except RuntimeError: - self.logger.warning( - "Can't remove %s from neededPubkeys, requesting pubkey will be delayed", address, exc_info=True) - - queues.UISignalQueue.put(( - 'updateStatusBar', - 'Doing work necessary to again attempt to request a public key...' - )) - sqlExecute( - "UPDATE sent SET status = 'msgqueued'" - " WHERE toaddress = ? AND folder = 'sent'", address) - queues.workerQueue.put(('sendmessage', '')) - - def resendMsg(self, ackdata): - """Resend message by ackdata""" - self.logger.debug( - 'It has been a long time and we haven\'t heard an acknowledgement' - ' to our msg. Sending again.' - ) - sqlExecute( - "UPDATE sent SET status = 'msgqueued'" - " WHERE ackdata = ? AND folder = 'sent'", ackdata) - queues.workerQueue.put(('sendmessage', '')) - queues.UISignalQueue.put(( - 'updateStatusBar', - 'Doing work necessary to again attempt to deliver a message...' - )) diff --git a/src/class_singleWorker.py b/src/class_singleWorker.py deleted file mode 100644 index 60cfd9d7..00000000 --- a/src/class_singleWorker.py +++ /dev/null @@ -1,1524 +0,0 @@ -""" -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 shared -import state -import tr -from addresses import ( - calculateInventoryHash, decodeAddress, decodeVarint, encodeVarint -) -from bmconfigparser import config -from helper_sql import sqlExecute, sqlQuery -from inventory import Inventory -from network import knownnodes, StoppableThread -from six.moves import configparser, queue - - -def sizeof_fmt(num, suffix='h/s'): - """Format hashes per seconds nicely (SI prefix)""" - - 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""" - - def __init__(self): - super(singleWorker, self).__init__(name="singleWorker") - self.digestAlg = config.safeGet( - 'bitmessagesettings', 'digestalg', 'sha256') - proofofwork.init() - - def stopThread(self): - """Signal through the queue that the thread should be stopped""" - - try: - queues.workerQueue.put(("stopThread", "data")) - except queue.Full: - self.logger.error('workerQueue is Full') - 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 row in queryreturn: - toAddress, = row - # toStatus - _, toAddressVersionNumber, toStreamNumber, toRipe = \ - decodeAddress(toAddress) - if toAddressVersionNumber <= 3: - state.neededPubkeys[toAddress] = 0 - elif toAddressVersionNumber >= 4: - doubleHashOfAddressData = hashlib.sha512(hashlib.sha512( - encodeVarint(toAddressVersionNumber) - + encodeVarint(toStreamNumber) + toRipe - ).digest()).digest() - # Note that this is the first half of the sha512 hash. - privEncryptionKey = doubleHashOfAddressData[:32] - 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)) - ) - - # Initialize the state.ackdataForWhichImWatching data structure - queryreturn = sqlQuery( - '''SELECT ackdata FROM sent WHERE status = 'msgsent' AND folder = 'sent' ''') - for row in queryreturn: - ackdata, = row - self.logger.info('Watching for ackdata %s', hexlify(ackdata)) - state.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] - - # For the case if user deleted knownnodes - # but is still having onionpeer objects in inventory - if not knownnodes.knownNodesActual: - for item in Inventory().by_type_and_tag(protocol.OBJECT_ONIONPEER): - queues.objectProcessorQueue.put(( - protocol.OBJECT_ONIONPEER, item.payload - )) - # FIXME: should also delete from inventory - - # 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: - self.busy = 0 - command, data = queues.workerQueue.get() - self.busy = 1 - if command == 'sendmessage': - try: - self.sendMsg() - except: # noqa:E722 - self.logger.warning("sendMsg didn't work") - elif command == 'sendbroadcast': - try: - self.sendBroadcast() - except: # noqa:E722 - self.logger.warning("sendBroadcast didn't work") - elif command == 'doPOWForMyV2Pubkey': - try: - self.doPOWForMyV2Pubkey(data) - except: # noqa:E722 - self.logger.warning("doPOWForMyV2Pubkey didn't work") - elif command == 'sendOutOrStoreMyV3Pubkey': - try: - self.sendOutOrStoreMyV3Pubkey(data) - except: # noqa:E722 - self.logger.warning("sendOutOrStoreMyV3Pubkey didn't work") - 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") - 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 - ) - - queues.workerQueue.task_done() - self.logger.info("Quitting...") - - def _getKeysForAddress(self, address): - privSigningKeyBase58 = config.get( - address, 'privsigningkey') - privEncryptionKeyBase58 = config.get( - address, 'privencryptionkey') - - privSigningKeyHex = hexlify(shared.decodeWalletImportFormat( - privSigningKeyBase58)) - privEncryptionKeyHex = hexlify(shared.decodeWalletImportFormat( - privEncryptionKeyBase58)) - - # The \x04 on the beginning of the public keys are not sent. - # This way there is only one acceptable way to encode - # and send a public key. - pubSigningKey = unhexlify(highlevelcrypto.privToPub( - privSigningKeyHex))[1:] - 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""" - # Look up my stream number based on my address hash - myAddress = shared.myAddressesByHash[adressHash] - # status - _, addressVersionNumber, streamNumber, adressHash = ( - decodeAddress(myAddress)) - - # 28 days from now plus or minus five minutes - TTL = int(28 * 24 * 60 * 60 + helper_random.randomrandrange(-300, 300)) - embeddedTime = int(time.time() + TTL) - payload = pack('>Q', (embeddedTime)) - payload += '\x00\x00\x00\x01' # object type: pubkey - payload += encodeVarint(addressVersionNumber) # Address version number - payload += encodeVarint(streamNumber) - # bitfield of features supported by me (see the wiki). - payload += protocol.getBitfield(myAddress) - - try: - # privSigningKeyHex, privEncryptionKeyHex - _, _, pubSigningKey, pubEncryptionKey = \ - self._getKeysForAddress(myAddress) - except (configparser.NoSectionError, configparser.NoOptionError) as err: - self.logger.warning("Section or Option did not found: %s", err) - except Exception as err: - self.logger.error( - 'Error within doPOWForMyV2Pubkey. Could not read' - ' the keys from the keys.dat file for a requested' - ' address. %s\n', err - ) - return - - payload += pubSigningKey + pubEncryptionKey - - # Do the POW for this pubkey message - payload = self._doPOWDefaults( - payload, TTL, log_prefix='(For pubkey message)') - - inventoryHash = calculateInventoryHash(payload) - objectType = 1 - Inventory()[inventoryHash] = ( - objectType, streamNumber, payload, embeddedTime, '') - - self.logger.info( - 'broadcasting inv with hash: %s', hexlify(inventoryHash)) - - queues.invQueue.put((streamNumber, inventoryHash)) - queues.UISignalQueue.put(('updateStatusBar', '')) - try: - 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. - 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". - """ - try: - myAddress = shared.myAddressesByHash[adressHash] - except KeyError: - # The address has been deleted. - self.logger.warning("Can't find %s in myAddressByHash", hexlify(adressHash)) - return - if config.safeGetBoolean(myAddress, 'chan'): - self.logger.info('This is a chan address. Not sending pubkey.') - return - _, addressVersionNumber, streamNumber, adressHash = decodeAddress( - myAddress) - - # 28 days from now plus or minus five minutes - TTL = int(28 * 24 * 60 * 60 + helper_random.randomrandrange(-300, 300)) - embeddedTime = int(time.time() + TTL) - - # 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 += encodeVarint(addressVersionNumber) # Address version number - payload += encodeVarint(streamNumber) - # bitfield of features supported by me (see the wiki). - payload += protocol.getBitfield(myAddress) - - try: - # , privEncryptionKeyHex - privSigningKeyHex, _, pubSigningKey, pubEncryptionKey = \ - self._getKeysForAddress(myAddress) - except (configparser.NoSectionError, configparser.NoOptionError) as err: - self.logger.warning("Section or Option did not found: %s", err) - except Exception as err: - self.logger.error( - 'Error within sendOutOrStoreMyV3Pubkey. Could not read' - ' the keys from the keys.dat file for a requested' - ' address. %s\n', err - ) - return - - payload += pubSigningKey + pubEncryptionKey - - payload += encodeVarint(config.getint( - myAddress, 'noncetrialsperbyte')) - payload += encodeVarint(config.getint( - myAddress, 'payloadlengthextrabytes')) - - signature = highlevelcrypto.sign( - payload, privSigningKeyHex, self.digestAlg) - payload += encodeVarint(len(signature)) - payload += signature - - # Do the POW for this pubkey message - payload = self._doPOWDefaults( - payload, TTL, log_prefix='(For pubkey message)') - - inventoryHash = calculateInventoryHash(payload) - objectType = 1 - Inventory()[inventoryHash] = ( - objectType, streamNumber, payload, embeddedTime, '') - - self.logger.info( - 'broadcasting inv with hash: %s', hexlify(inventoryHash)) - - queues.invQueue.put((streamNumber, inventoryHash)) - queues.UISignalQueue.put(('updateStatusBar', '')) - try: - 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. - pass - except: # noqa:E722 - self.logger.warning("BMConfigParser().set didn't work") - - 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. - return - if config.safeGetBoolean(myAddress, 'chan'): - self.logger.info('This is a chan address. Not sending pubkey.') - return - _, addressVersionNumber, streamNumber, addressHash = decodeAddress( - myAddress) - - # 28 days from now plus or minus five minutes - TTL = int(28 * 24 * 60 * 60 + helper_random.randomrandrange(-300, 300)) - embeddedTime = int(time.time() + TTL) - payload = pack('>Q', (embeddedTime)) - payload += '\x00\x00\x00\x01' # object type: pubkey - payload += encodeVarint(addressVersionNumber) # Address version number - payload += encodeVarint(streamNumber) - dataToEncrypt = protocol.getBitfield(myAddress) - - try: - # , privEncryptionKeyHex - privSigningKeyHex, _, pubSigningKey, pubEncryptionKey = \ - self._getKeysForAddress(myAddress) - except (configparser.NoSectionError, configparser.NoOptionError) as err: - self.logger.warning("Section or Option did not found: %s", err) - except Exception as err: - self.logger.error( - 'Error within sendOutOrStoreMyV4Pubkey. Could not read' - ' the keys from the keys.dat file for a requested' - ' address. %s\n', err - ) - return - - dataToEncrypt += pubSigningKey + pubEncryptionKey - - dataToEncrypt += encodeVarint(config.getint( - myAddress, 'noncetrialsperbyte')) - dataToEncrypt += encodeVarint(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 = hashlib.sha512(hashlib.sha512( - encodeVarint(addressVersionNumber) - + encodeVarint(streamNumber) + addressHash - ).digest()).digest() - payload += doubleHashOfAddressData[32:] # the tag - signature = highlevelcrypto.sign( - payload + dataToEncrypt, privSigningKeyHex, self.digestAlg) - 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)') - - inventoryHash = calculateInventoryHash(payload) - objectType = 1 - Inventory()[inventoryHash] = ( - objectType, streamNumber, payload, embeddedTime, - doubleHashOfAddressData[32:] - ) - - self.logger.info( - 'broadcasting inv with hash: %s', hexlify(inventoryHash)) - - queues.invQueue.put((streamNumber, inventoryHash)) - queues.UISignalQueue.put(('updateStatusBar', '')) - try: - config.set( - myAddress, 'lastpubkeysendtime', str(int(time.time()))) - config.save() - except Exception as err: - self.logger.error( - 'Error: Couldn\'t add the lastpubkeysendtime' - ' to the keys.dat file. Error message: %s', err - ) - - def sendOnionPeerObj(self, peer=None): - """Send onionpeer object representing peer""" - if not peer: # find own onionhostname - for peer in state.ownAddresses: - if peer.host.endswith('.onion'): - break - else: - return - TTL = int(7 * 24 * 60 * 60 + helper_random.randomrandrange(-300, 300)) - embeddedTime = int(time.time() + TTL) - streamNumber = 1 # Don't know yet what should be here - objectType = protocol.OBJECT_ONIONPEER - # FIXME: ideally the objectPayload should be signed - objectPayload = encodeVarint(peer.port) + protocol.encodeHost(peer.host) - tag = calculateInventoryHash(objectPayload) - - if Inventory().by_type_and_tag(objectType, tag): - return # not expired - - payload = pack('>Q', embeddedTime) - payload += pack('>I', objectType) - payload += encodeVarint(2 if len(peer.host) == 22 else 3) - payload += encodeVarint(streamNumber) - payload += objectPayload - - payload = self._doPOWDefaults( - payload, TTL, log_prefix='(For onionpeer object)') - - inventoryHash = calculateInventoryHash(payload) - Inventory()[inventoryHash] = ( - objectType, streamNumber, buffer(payload), # noqa: F821 - embeddedTime, buffer(tag) # noqa: F821 - ) - self.logger.info( - 'sending inv (within sendOnionPeerObj function) for object: %s', - hexlify(inventoryHash)) - queues.invQueue.put((streamNumber, inventoryHash)) - - 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') - - for row in queryreturn: - fromaddress, subject, body, ackdata, TTL, encoding = 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') - return - # We need to convert our private keys to public keys in order - # to include them. - try: - # , privEncryptionKeyHex - privSigningKeyHex, _, pubSigningKey, pubEncryptionKey = \ - self._getKeysForAddress(fromaddress) - except (configparser.NoSectionError, configparser.NoOptionError) as err: - self.logger.warning("Section or Option did not found: %s", err) - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "Error! Could not find sender address" - " (your address) in the keys.dat file.")) - )) - except Exception as err: - self.logger.error( - 'Error within sendBroadcast. Could not read' - ' the keys from the keys.dat file for a requested' - ' address. %s\n', err - ) - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "Error, can't send.")) - )) - continue - - if not sqlExecute( - '''UPDATE sent SET status='doingbroadcastpow' ''' - ''' WHERE ackdata=? AND status='broadcastqueued' ''' - ''' AND folder='sent' ''', - ackdata): - continue - - # 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') - - 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)) - embeddedTime = int(time.time() + TTL) - payload = pack('>Q', embeddedTime) - payload += '\x00\x00\x00\x03' # object type: broadcast - - if addressVersionNumber <= 3: - payload += encodeVarint(4) # broadcast version - else: - payload += encodeVarint(5) # broadcast version - - payload += encodeVarint(streamNumber) - if addressVersionNumber >= 4: - doubleHashOfAddressData = hashlib.sha512(hashlib.sha512( - encodeVarint(addressVersionNumber) - + encodeVarint(streamNumber) + ripe - ).digest()).digest() - tag = doubleHashOfAddressData[32:] - payload += tag - else: - tag = '' - - dataToEncrypt = encodeVarint(addressVersionNumber) - dataToEncrypt += encodeVarint(streamNumber) - # behavior bitfield - dataToEncrypt += protocol.getBitfield(fromaddress) - dataToEncrypt += pubSigningKey + pubEncryptionKey - 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 - dataToSign = payload + dataToEncrypt - - signature = highlevelcrypto.sign( - dataToSign, privSigningKeyHex, self.digestAlg) - 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. - if addressVersionNumber <= 3: - privEncryptionKey = hashlib.sha512( - encodeVarint(addressVersionNumber) - + encodeVarint(streamNumber) + ripe - ).digest()[:32] - else: - privEncryptionKey = doubleHashOfAddressData[:32] - - pubEncryptionKey = highlevelcrypto.pointMult(privEncryptionKey) - 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)') - - # 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) - ) - continue - - inventoryHash = calculateInventoryHash(payload) - objectType = 3 - Inventory()[inventoryHash] = ( - objectType, streamNumber, payload, embeddedTime, tag) - self.logger.info( - 'sending inv (within sendBroadcast function)' - ' for object: %s', - hexlify(inventoryHash) - ) - queues.invQueue.put((streamNumber, inventoryHash)) - - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "Broadcast sent on %1" - ).arg(l10n.formatTimestamp())) - )) - - # Update the status of the message in the 'sent' table to have - # a 'broadcastsent' status - sqlExecute( - '''UPDATE sent SET msgid=?, status=?, lastactiontime=? ''' - ''' WHERE ackdata=? AND folder='sent' ''', - inventoryHash, 'broadcastsent', int(time.time()), ackdata - ) - - 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. - 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. - 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. - 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' - 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 != []: - # 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 - 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 - # 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: - if toAddressVersionNumber <= 3: - toTag = '' - else: - toTag = hashlib.sha512(hashlib.sha512( - encodeVarint(toAddressVersionNumber) - + encodeVarint(toStreamNumber) + toRipe - ).digest()).digest()[32:] - if toaddress in state.neededPubkeys or \ - toTag in state.neededPubkeys: - # 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 - else: - # We have not yet sent a request for the pubkey - needToRequestPubkey = True - # If we are trying to send to address - # version >= 4 then the needed pubkey might be - # encrypted in the inventory. - # If we have it we'll need to decrypt it - # and put it in the pubkeys table. - - # The decryptAndCheckPubkeyPayload function - # expects that the shared.neededPubkeys dictionary - # already contains the toAddress and cryptor - # object associated with the tag for this toAddress. - if toAddressVersionNumber >= 4: - doubleHashOfToAddressData = hashlib.sha512( - hashlib.sha512( - encodeVarint(toAddressVersionNumber) + encodeVarint(toStreamNumber) + toRipe - ).digest() - ).digest() - # The first half of the sha512 hash. - privEncryptionKey = doubleHashOfToAddressData[:32] - # The second half of the sha512 hash. - tag = doubleHashOfToAddressData[32:] - state.neededPubkeys[tag] = ( - toaddress, - highlevelcrypto.makeCryptor( - hexlify(privEncryptionKey)) - ) - - for value in Inventory().by_type_and_tag(1, toTag): - # if valid, this function also puts it - # in the pubkeys table. - if protocol.decryptAndCheckPubkeyPayload( - value.payload, toaddress - ) == 'successful': - needToRequestPubkey = False - sqlExecute( - '''UPDATE sent SET ''' - ''' status='doingmsgpow', ''' - ''' retrynumber=0 WHERE ''' - ''' toaddress=? AND ''' - ''' (status='msgqueued' or ''' - ''' status='awaitingpubkey' or ''' - ''' status='doingpubkeypow') AND ''' - ''' folder='sent' ''', - toaddress) - del state.neededPubkeys[tag] - break - # else: - # There was something wrong with this - # pubkey object even though it had - # the correct tag- almost certainly - # because of malicious behavior or - # a badly programmed client. If there are - # any other pubkeys in our inventory - # with the correct tag then we'll try - # to decrypt those. - 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.")) - )) - 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)) - embeddedTime = int(time.time() + TTL) - - # 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. - queryreturn = sqlQuery( - 'SELECT transmitdata FROM pubkeys WHERE address=?', - toaddress) - for row in queryreturn: # pylint: disable=redefined-outer-name - pubkeyPayload, = row - - # 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) - # -length extra bytes (if address version is >= 3) - - # to bypass the address version whose length is definitely 1 - readPosition = 1 - _, 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 - # 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. - continue - readPosition += 4 # to bypass the bitfield of behaviors - # We don't use this key for anything here. - # pubSigningKeyBase256 = - # pubkeyPayload[readPosition:readPosition+64] - readPosition += 64 - pubEncryptionKeyBase256 = pubkeyPayload[ - readPosition:readPosition + 64] - readPosition += 64 - - # 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.")) - )) - elif toAddressVersionNumber >= 3: - requiredAverageProofOfWorkNonceTrialsPerByte, \ - varintLength = decodeVarint( - pubkeyPayload[readPosition:readPosition + 10]) - readPosition += varintLength - 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 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. - sqlExecute( - '''UPDATE sent SET status='toodifficult' ''' - ''' WHERE ackdata=? AND folder='sent' ''', - 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())))) - 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) - - try: - privEncryptionKeyBase58 = 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) - continue - privEncryptionKeyHex = hexlify(shared.decodeWalletImportFormat( - privEncryptionKeyBase58)) - pubEncryptionKeyBase256 = unhexlify(highlevelcrypto.privToPub( - privEncryptionKeyHex))[1:] - requiredAverageProofOfWorkNonceTrialsPerByte = \ - defaults.networkDefaultProofOfWorkNonceTrialsPerByte - requiredPayloadLengthExtraBytes = \ - defaults.networkDefaultPayloadLengthExtraBytes - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "Doing work necessary to send message.")) - )) - - # 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) - - # We need to convert our private keys to public keys in order - # to include them. - try: - privSigningKeyHex, privEncryptionKeyHex, \ - pubSigningKey, pubEncryptionKey = self._getKeysForAddress( - fromaddress) - except (configparser.NoSectionError, configparser.NoOptionError) as err: - self.logger.warning("Section or Option did not found: %s", err) - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "Error! Could not find sender address" - " (your address) in the keys.dat file.")) - )) - except Exception as err: - self.logger.error( - 'Error within sendMsg. Could not read' - ' the keys from the keys.dat file for a requested' - ' address. %s\n', err - ) - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "Error, can't send.")) - )) - continue - - payload += pubSigningKey + pubEncryptionKey - - 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): - payload += encodeVarint( - defaults.networkDefaultProofOfWorkNonceTrialsPerByte) - payload += encodeVarint( - defaults.networkDefaultPayloadLengthExtraBytes) - else: - payload += encodeVarint(config.getint( - fromaddress, 'noncetrialsperbyte')) - payload += encodeVarint(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.' - ) - 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) - 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) - 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())) - )) - continue - - encryptedPayload = pack('>Q', embeddedTime) - 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 - ) - - 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 - ) - 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") - - 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) - ) - continue - - inventoryHash = calculateInventoryHash(encryptedPayload) - objectType = 2 - Inventory()[inventoryHash] = ( - objectType, toStreamNumber, encryptedPayload, embeddedTime, '') - if config.has_section(toaddress) or \ - not protocol.checkBitfield(behaviorBitfield, protocol.BITFIELD_DOESACK): - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "Message sent. Sent at %1" - ).arg(l10n.formatTimestamp())))) - else: - # not sending to a chan or one of my addresses - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "Message sent. Waiting for acknowledgement." - " Sent on %1" - ).arg(l10n.formatTimestamp())) - )) - self.logger.info( - 'Broadcasting inv for my msg(within sendmsg function): %s', - hexlify(inventoryHash) - ) - queues.invQueue.put((toStreamNumber, inventoryHash)) - - # Update the sent message in the sent table with the - # necessary information. - if config.has_section(toaddress) or \ - not protocol.checkBitfield(behaviorBitfield, protocol.BITFIELD_DOESACK): - 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 we are sending to ourselves or a chan, let's put - # the message in our own inbox. - if config.has_section(toaddress): - # Used to detect and ignore duplicate messages in our inbox - sigHash = hashlib.sha512(hashlib.sha512( - signature).digest()).digest()[32:] - t = (inventoryHash, toaddress, fromaddress, subject, int( - time.time()), message, 'inbox', encoding, 0, sigHash) - helper_inbox.insert(t) - - queues.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 - - 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 - ) - 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 - ) - return - retryNumber = queryReturn[0][0] - - if addressVersionNumber <= 3: - state.neededPubkeys[toAddress] = 0 - elif addressVersionNumber >= 4: - # If the user just clicked 'send' then the tag - # (and other information) will already be in the - # neededPubkeys dictionary. But if we are recovering - # from a restart of the client then we have to put it in now. - - # Note that this is the first half of the sha512 hash. - privEncryptionKey = hashlib.sha512(hashlib.sha512( - encodeVarint(addressVersionNumber) - + encodeVarint(streamNumber) + ripe - ).digest()).digest()[:32] - # Note that this is the second half of the sha512 hash. - tag = hashlib.sha512(hashlib.sha512( - encodeVarint(addressVersionNumber) - + encodeVarint(streamNumber) + ripe - ).digest()).digest()[32:] - if 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) - embeddedTime = int(time.time() + TTL) - payload = pack('>Q', embeddedTime) - 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)) - else: - payload += tag - self.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.")) - )) - - payload = self._doPOWDefaults(payload, TTL) - - inventoryHash = calculateInventoryHash(payload) - objectType = 1 - Inventory()[inventoryHash] = ( - objectType, streamNumber, payload, embeddedTime, '') - self.logger.info('sending inv (for the getpubkey message)') - queues.invQueue.put((streamNumber, inventoryHash)) - - # wait 10% past expiration - sleeptill = int(time.time() + TTL * 1.1) - sqlExecute( - '''UPDATE sent SET lastactiontime=?, ''' - ''' status='awaitingpubkey', retrynumber=?, sleeptill=? ''' - ''' WHERE toaddress=? AND (status='doingpubkeypow' OR ''' - ''' status='awaitingpubkey') AND folder='sent' ''', - int(time.time()), retryNumber + 1, sleeptill, toAddress) - - 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 - else: - TTL = 28 * 24 * 60 * 60 # 4 weeks - # Add some randomness to the TTL - TTL = int(TTL + helper_random.randomrandrange(-300, 300)) - embeddedTime = int(time.time() + TTL) - - # type/version/stream already included - payload = pack('>Q', (embeddedTime)) + ackdata - - payload = self._doPOWDefaults( - payload, TTL, log_prefix='(For ack message)', log_time=True) - - return protocol.CreatePacket('object', payload) diff --git a/src/class_smtpDeliver.py b/src/class_smtpDeliver.py deleted file mode 100644 index 9e3b8ab3..00000000 --- a/src/class_smtpDeliver.py +++ /dev/null @@ -1,114 +0,0 @@ -""" -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 - -import queues -import state -from bmconfigparser import config -from network.threads import StoppableThread - -SMTPDOMAIN = "bmaddr.lan" - - -class smtpDeliver(StoppableThread): - """SMTP client thread for delivery""" - name = "smtpDeliver" - _instance = None - - def stopThread(self): - """Relay shutdown instruction""" - queues.UISignalQueue.put(("stopThread", "data")) - super(smtpDeliver, self).stopThread() - - @classmethod - def get(cls): - """(probably) Singleton functionality""" - if not cls._instance: - cls._instance = smtpDeliver() - return cls._instance - - def run(self): - # pylint: disable=too-many-branches,too-many-statements,too-many-locals - # pylint: disable=deprecated-lambda - while state.shutdown == 0: - command, data = queues.UISignalQueue.get() - if command == 'writeNewAddressToTable': - label, address, streamNumber = data - elif command == 'updateStatusBar': - pass - elif command == 'updateSentItemStatusByToAddress': - toAddress, message = data - elif command == 'updateSentItemStatusByAckdata': - ackData, message = data - elif command == 'displayNewInboxMessage': - inventoryHash, toAddress, fromAddress, subject, body = data - dest = config.safeGet("bitmessagesettings", "smtpdeliver", '') - if dest == '': - continue - try: - u = urlparse.urlparse(dest) - to = urlparse.parse_qs(u.query)['to'] - client = smtplib.SMTP(u.hostname, u.port) - 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: - msg['To'] = "\"%s\" <%s>" % (Header(toLabel[0], 'utf-8'), toAddress + '@' + SMTPDOMAIN) - else: - msg['To'] = toAddress + '@' + SMTPDOMAIN - client.ehlo() - 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) - client.quit() - except: # noqa:E722 - self.logger.error('smtp delivery error', exc_info=True) - elif command == 'displayNewSentMessage': - toAddress, fromLabel, fromAddress, subject, message, ackdata = data - elif command == 'updateNetworkStatusTab': - pass - elif command == 'updateNumberOfMessagesProcessed': - pass - elif command == 'updateNumberOfPubkeysProcessed': - pass - elif command == 'updateNumberOfBroadcastsProcessed': - pass - elif command == 'setStatusIcon': - pass - elif command == 'changedInboxUnread': - pass - elif command == 'rerenderMessagelistFromLabels': - pass - elif command == 'rerenderMessagelistToLabels': - pass - elif command == 'rerenderAddressBook': - pass - elif command == 'rerenderSubscriptions': - pass - elif command == 'rerenderBlackWhiteList': - pass - elif command == 'removeInboxRowByMsgid': - pass - elif command == 'newVersionAvailable': - pass - elif command == 'alert': - title, text, exitAfterUserClicksOk = data - elif command == 'stopThread': - break - else: - self.logger.warning( - 'Command sent to smtpDeliver not recognized: %s', 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 deleted file mode 100644 index 7df9e253..00000000 --- a/src/class_sqlThread.py +++ /dev/null @@ -1,640 +0,0 @@ -""" -sqlThread is defined here -""" - -import os -import shutil # used for moving the messages.dat file -import sqlite3 -import sys -import threading -import time - -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 - - -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') - 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)''') - 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)''') - self.cur.execute( - '''CREATE TABLE subscriptions (label text, address text, enabled bool)''') - self.cur.execute( - '''CREATE TABLE addressbook (label text, address text, UNIQUE(address) ON CONFLICT IGNORE)''') - self.cur.execute( - '''CREATE TABLE blacklist (label text, address text, enabled bool)''') - self.cur.execute( - '''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)''') - 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( - '''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',?)''', ( - int(time.time()),)) - self.cur.execute( - '''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: - if str(err) == 'table inbox already exists': - logger.debug('Database file already exists.') - - else: - sys.stderr.write( - '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') - - # People running earlier versions of PyBitmessage do not have the - # usedpersonally field in their pubkeys table. Let's add it. - if settingsversion == 2: - item = '''ALTER TABLE pubkeys ADD usedpersonally text DEFAULT 'no' ''' - parameters = '' - self.cur.execute(item, parameters) - self.conn.commit() - - 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: - item = '''ALTER TABLE inbox ADD encodingtype int DEFAULT '2' ''' - parameters = '' - self.cur.execute(item, parameters) - - item = '''ALTER TABLE inbox ADD read bool DEFAULT '1' ''' - parameters = '' - self.cur.execute(item, parameters) - - item = '''ALTER TABLE sent ADD encodingtype int DEFAULT '2' ''' - parameters = '' - self.cur.execute(item, parameters) - self.conn.commit() - - settingsversion = 4 - - config.set( - 'bitmessagesettings', 'settingsversion', str(settingsversion)) - config.save() - - helper_startup.updateConfig() - - # 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 - # version we are on can stay embedded in the messages.dat file. Let us - # check to see if the settings table exists yet. - item = '''SELECT name FROM sqlite_master WHERE type='table' AND name='settings';''' - parameters = '' - 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.") - 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',?)''', ( - 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);''') - self.cur.execute( - '''INSERT INTO pubkeys_backup SELECT hash, transmitdata, time, usedpersonally FROM 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)''') - 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( - '''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)''') - 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 ''') - - # After code refactoring, the possible status values for sent messages - # have changed. - self.cur.execute( - '''update sent set status='doingmsgpow' where status='doingpow' ''') - self.cur.execute( - '''update sent set status='msgsent' where status='sentmessage' ''') - self.cur.execute( - '''update sent set status='doingpubkeypow' where status='findingpubkey' ''') - self.cur.execute( - '''update sent set status='broadcastqueued' where status='broadcastpending' ''') - self.conn.commit() - - # 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.') - self.cur.execute( - '''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''') - self.cur.execute( - '''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;''') - item = '''update settings set value=? WHERE key='version';''' - parameters = (3,) - self.cur.execute(item, parameters) - - # Add a new column to the inventory table to store tags. - item = '''SELECT value FROM settings WHERE key='version';''' - parameters = '' - 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.') - item = '''ALTER TABLE inventory ADD tag blob DEFAULT '' ''' - parameters = '' - self.cur.execute(item, parameters) - item = '''update settings set value=? WHERE key='version';''' - parameters = (4,) - self.cur.execute(item, parameters) - - # 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';''' - parameters = '' - self.cur.execute(item, parameters) - currentVersion = int(self.cur.fetchall()[0][0]) - if currentVersion == 4: - 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)''') - self.cur.execute( - '''delete from inventory where objecttype = 'pubkey';''') - item = '''update settings set value=? WHERE key='version';''' - parameters = (5,) - self.cur.execute(item, parameters) - - # Add a new table: objectprocessorqueue with which to hold objects - # that have yet to be processed if the user shuts down Bitmessage. - item = '''SELECT value FROM settings WHERE key='version';''' - parameters = '' - self.cur.execute(item, parameters) - currentVersion = int(self.cur.fetchall()[0][0]) - if currentVersion == 5: - self.cur.execute('''DROP TABLE knownnodes''') - self.cur.execute( - '''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) - 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)''') - item = '''update settings set value=? WHERE key='version';''' - parameters = (7,) - self.cur.execute(item, parameters) - logger.debug( - 'Finished dropping and recreating the inventory table.') - - # 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. - 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.') - 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. - self.cur.execute( - '''UPDATE sent SET status='msgqueued' WHERE status='doingmsgpow' or status='badkey';''') - query = '''update settings set value=? WHERE key='version';''' - parameters = (8,) - 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. - 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.') - 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. - - 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...') - 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)''') - 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''') - 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)''') - 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''') - 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)); ''') - - # 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)''') - self.cur.execute( - '''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)''') - 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.') - 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';''') - - # 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! - - try: - testpayload = '\x00\x00' - t = ('1234', 1, testpayload, '12345678', 'no') - self.cur.execute('''INSERT INTO pubkeys VALUES(?,?,?,?,?)''', t) - self.conn.commit() - self.cur.execute( - '''SELECT transmitdata FROM pubkeys WHERE address='1234' ''') - queryreturn = self.cur.fetchall() - for row in queryreturn: - transmitdata, = row - 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') - 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))) - os._exit(0) - else: - logger.error(err) - - # Let us check to see the last time we vaccumed the messages.dat file. - # If it has been more than a month let's do it now. - item = '''SELECT value FROM settings WHERE key='lastvacuumtime';''' - parameters = '' - self.cur.execute(item, parameters) - queryreturn = self.cur.fetchall() - for row in queryreturn: - value, = row - 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 ''') - 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))) - 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() - 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))) - os._exit(0) - elif item == 'exit': - self.conn.close() - logger.info('sqlThread exiting gracefully.') - - return - elif item == 'movemessagstoprog': - logger.debug('the sqlThread is moving the messages.dat file to the local program directory.') - - try: - 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))) - os._exit(0) - self.conn.close() - shutil.move( - paths.lookupAppdataFolder() + 'messages.dat', paths.lookupExeFolder() + 'messages.dat') - self.conn = sqlite3.connect(paths.lookupExeFolder() + 'messages.dat') - self.conn.text_factory = str - self.cur = self.conn.cursor() - elif item == 'movemessagstoappdata': - logger.debug('the sqlThread is moving the messages.dat file to the Appdata folder.') - - try: - 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))) - os._exit(0) - self.conn.close() - shutil.move( - paths.lookupExeFolder() + 'messages.dat', paths.lookupAppdataFolder() + 'messages.dat') - self.conn = sqlite3.connect(paths.lookupAppdataFolder() + 'messages.dat') - self.conn.text_factory = str - self.cur = self.conn.cursor() - elif item == 'deleteandvacuume': - self.cur.execute('''delete from inbox where folder='trash' ''') - self.cur.execute('''delete from sent where folder='trash' ''') - self.conn.commit() - try: - 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))) - os._exit(0) - else: - parameters = helper_sql.sqlSubmitQueue.get() - rowcount = 0 - 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))) - 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('This program shall now abruptly exit!') - - os._exit(0) - - helper_sql.sqlReturnQueue.put((self.cur.fetchall(), rowcount)) - # helper_sql.sqlSubmitQueue.task_done() - - def create_function(self): - # create_function - try: - self.conn.create_function("enaddr", 3, func=encodeAddress, deterministic=True) - except (TypeError, sqlite3.NotSupportedError) as err: - logger.debug( - "Got error while pass deterministic in sqlite create function {}, Passing 3 params".format(err)) - self.conn.create_function("enaddr", 3, encodeAddress) diff --git a/src/debug.py b/src/debug.py deleted file mode 100644 index 639be123..00000000 --- a/src/debug.py +++ /dev/null @@ -1,157 +0,0 @@ -""" -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. - -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. -""" - -import logging -import logging.config -import os -import sys - -from six.moves import configparser - -import helper_startup -import state - -helper_startup.loadConfig() - -# Now can be overriden from a config file, which uses standard python -# logging.config.fileConfig interface -# examples are here: -# https://web.archive.org/web/20170712122006/https://bitmessage.org/forum/index.php/topic,4820.msg11163.html#msg11163 -log_level = 'WARNING' - - -def log_uncaught_exceptions(ex_cls, ex, tb): - """The last resort logging function used for sys.excepthook""" - logging.critical('Unhandled exception', exc_info=(ex_cls, ex, tb)) - - -def configureLogging(): - """ - Configure logging, - using either logging.dat file in the state.appdata dir - or dictionary with hardcoded settings. - """ - sys.excepthook = log_uncaught_exceptions - fail_msg = '' - 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 = { - 'version': 1, - 'formatters': { - 'default': { - 'format': u'%(asctime)s - %(levelname)s - %(message)s', - }, - }, - 'handlers': { - 'console': { - 'class': 'logging.StreamHandler', - 'formatter': 'default', - 'level': log_level, - 'stream': 'ext://sys.stderr' - }, - 'file': { - 'class': 'logging.handlers.RotatingFileHandler', - 'formatter': 'default', - 'level': log_level, - 'filename': os.path.join(state.appdata, 'debug.log'), - 'maxBytes': 2097152, # 2 MiB - 'backupCount': 1, - 'encoding': 'UTF-8', - } - }, - 'loggers': { - 'console_only': { - 'handlers': ['console'], - 'propagate': 0 - }, - 'file_only': { - 'handlers': ['file'], - 'propagate': 0 - }, - 'both': { - 'handlers': ['console', 'file'], - 'propagate': 0 - }, - }, - 'root': { - 'level': log_level, - 'handlers': ['console'], - }, - } - - logging_config['loggers']['default'] = logging_config['loggers'][ - 'file_only' if '-c' in sys.argv else 'both'] - logging.config.dictConfig(logging_config) - - return True, fail_msg - - -def resetLogging(): - """Reconfigure logging in runtime when state.appdata dir changed""" - # pylint: disable=global-statement, used-before-assignment - global logger - for i in logger.handlers: - logger.removeHandler(i) - i.flush() - i.close() - configureLogging() - 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..a6009457 --- /dev/null +++ b/src/defaultKnownNodes.py @@ -0,0 +1,76 @@ +import pickle +import socket +from struct import * +import time +import random +import sys +from time import strftime, localtime + +def createDefaultKnownNodes(appdata): + ############## Stream 1 ################ + stream1 = {} + + stream1['109.91.57.2'] = (8443,int(time.time())) + stream1['66.65.120.151'] = (8080,int(time.time())) + stream1['188.18.69.115'] = (8443,int(time.time())) + stream1['204.236.246.212'] = (8444,int(time.time())) + stream1['85.177.81.73'] = (8444,int(time.time())) + stream1['78.81.56.239'] = (8444,int(time.time())) + stream1['204.236.246.212'] = (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 + + output = open(appdata + 'knownnodes.dat', 'wb') + + # Pickle dictionary using protocol 0. + pickle.dump(allKnownNodes, output) + + output.close() + return allKnownNodes + +def readDefaultKnownNodes(appdata): + pickleFile = open(appdata + 'knownnodes.dat', 'rb') + knownNodes = pickle.load(pickleFile) + pickleFile.close() + knownNodes + for stream, storedValue in knownNodes.items(): + for host,value in storedValue.items(): + port, storedtime = storedValue[host] + print host, '\t', port, '\t', unicode(strftime('%a, %d %b %Y %I:%M %p',localtime(storedtime))) + +if __name__ == "__main__": + + APPNAME = "PyBitmessage" + from os import path, environ + if sys.platform == 'darwin': + from AppKit import NSSearchPathForDirectoriesInDomains + # 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 deleted file mode 100755 index d966d5fe..00000000 --- a/src/depends.py +++ /dev/null @@ -1,470 +0,0 @@ -""" -Utility functions to check the availability of dependencies -and suggest how it may be installed -""" - -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 -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 - ) - -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') -handler = logging.StreamHandler(sys.stdout) -handler.setFormatter(formatter) -logger = logging.getLogger('both') -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]) - return False - - -def check_ripemd160(): - """Check availability of the RIPEMD160 hash function""" - try: - from fallback import RIPEMD160Hash # pylint: disable=relative-import - except ImportError: - return False - return RIPEMD160Hash is not None - - -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.') - 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] - ) - - 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) - 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. - if sqlite_version_number < 3000008: - logger.error( - 'This version of SQLite is too old.' - ' PyBitmessage requires SQLite 3.0.8 or later') - return False - return True - except sqlite3.Error: - logger.exception('An exception occured while checking sqlite.') - return False - finally: - 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.') - return False - - # We need to emulate the way PyElliptic searches for OpenSSL. - if sys.platform == 'win32': - paths = ['libeay32.dll'] - if getattr(sys, 'frozen', False): - paths.insert(0, os.path.join(sys._MEIPASS, 'libeay32.dll')) - else: - paths = ['libcrypto.so', 'libcrypto.so.1.0.0'] - if sys.platform == 'darwin': - paths.extend([ - 'libcrypto.dylib', - '/usr/local/opt/openssl/lib/libcrypto.dylib', - './../Frameworks/libcrypto.dylib' - ]) - - 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 - pass - - openssl_version = None - openssl_hexversion = None - openssl_cflags = None - - 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) - try: - library = ctypes.CDLL(path) - except OSError: - continue - logger.info('OpenSSL Name: %s', library._name) - try: - openssl_version, openssl_hexversion, openssl_cflags = \ - pyelliptic.openssl.get_version(library) - except AttributeError: # sphinx chokes - return True - if not openssl_version: - logger.error('Cannot determine version of this OpenSSL library.') - return False - logger.info('OpenSSL Version: %s', openssl_version) - logger.info('OpenSSL Compile Options: %s', openssl_cflags) - # PyElliptic uses EVP_CIPHER_CTX_new and EVP_CIPHER_CTX_free which were - # introduced in 0.9.8b. - 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.') - 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)) - return False - return True - return False - - -# ..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.') - 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.') - return False - - logger.info('pythondialog Package Version: %s', 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')) - 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: - return False - - logger.info('PyQt Version: %s', QtCore.PYQT_VERSION_STR) - logger.info('Qt Version: %s', 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.') - passed = False - if 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. - """ - 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. - 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+)') - 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() - - # 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] - if optional: - check_functions.extend([check_msgpack, check_pyqt, check_curses]) - - # 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__) - has_all_dependencies = False - - if not has_all_dependencies: - sys.exit( - 'PyBitmessage cannot start. One or more dependencies are' - ' unavailable.' - ) - - -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/help.py b/src/help.py new file mode 100644 index 00000000..f8cc0fe9 --- /dev/null +++ b/src/help.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'help.ui' +# +# Created: Mon Mar 11 11:20:54 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_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", "http://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/help.ui b/src/help.ui similarity index 91% rename from src/bitmessageqt/help.ui rename to src/help.ui index 0e09c759..ae0ff0f5 100644 --- a/src/bitmessageqt/help.ui +++ b/src/help.ui @@ -6,8 +6,8 @@ 0 0 - 405 - 104 + 335 + 96 @@ -17,7 +17,7 @@ - <a href="https://bitmessage.org/wiki/PyBitmessage_Help">https://bitmessage.org/wiki/PyBitmessage_Help</a> + <a href="http://Bitmessage.org/wiki/PyBitmessage_Help">http://Bitmessage.org/wiki/PyBitmessage_Help</a> true diff --git a/src/helper_ackPayload.py b/src/helper_ackPayload.py deleted file mode 100644 index d30f4c0d..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(helper_random.randomBytes(32))) - # Generate a dummy message of random length - # (the smallest possible standard-formatted message is 234 bytes) - dummyMessage = helper_random.randomBytes( - helper_random.randomrandrange(234, 801)) - # Encrypt the message using standard BM encryption (ECIES) - ackdata = highlevelcrypto.encrypt(dummyMessage, dummyPubKeyHex) - acktype = 2 # message - version = 1 - - elif stealthLevel == 1: # Basic privacy payload (random getpubkey) - ackdata = helper_random.randomBytes(32) - acktype = 0 # getpubkey - version = 4 - - else: # Minimum viable payload (non stealth) - ackdata = helper_random.randomBytes(32) - acktype = 2 # message - version = 1 - - ackobject = pack('>I', acktype) + encodeVarint( - version) + encodeVarint(streamNumber) + ackdata - - return ackobject diff --git a/src/helper_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 deleted file mode 100644 index d4f1d105..00000000 --- a/src/helper_bitcoin.py +++ /dev/null @@ -1,56 +0,0 @@ -""" -Calculates bitcoin and testnet address from pubkey -""" - -import hashlib - -from debug import logger -from pyelliptic import arithmetic - - -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)) - return "error" - ripe = hashlib.new('ripemd160') - sha = hashlib.new('sha256') - sha.update(pubkey) - ripe.update(sha.digest()) - ripeWithProdnetPrefix = '\x00' + ripe.digest() - - checksum = hashlib.sha256(hashlib.sha256( - ripeWithProdnetPrefix).digest()).digest()[:4] - binaryBitcoinAddress = ripeWithProdnetPrefix + checksum - numberOfZeroBytesOnBinaryBitcoinAddress = 0 - while binaryBitcoinAddress[0] == '\x00': - numberOfZeroBytesOnBinaryBitcoinAddress += 1 - binaryBitcoinAddress = binaryBitcoinAddress[1:] - base58encoded = arithmetic.changebase(binaryBitcoinAddress, 256, 58) - return "1" * numberOfZeroBytesOnBinaryBitcoinAddress + base58encoded - - -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)) - return "error" - ripe = hashlib.new('ripemd160') - sha = hashlib.new('sha256') - sha.update(pubkey) - ripe.update(sha.digest()) - ripeWithProdnetPrefix = '\x6F' + ripe.digest() - - checksum = hashlib.sha256(hashlib.sha256( - ripeWithProdnetPrefix).digest()).digest()[:4] - binaryBitcoinAddress = ripeWithProdnetPrefix + checksum - numberOfZeroBytesOnBinaryBitcoinAddress = 0 - while binaryBitcoinAddress[0] == '\x00': - numberOfZeroBytesOnBinaryBitcoinAddress += 1 - binaryBitcoinAddress = binaryBitcoinAddress[1:] - base58encoded = arithmetic.changebase(binaryBitcoinAddress, 256, 58) - return "1" * numberOfZeroBytesOnBinaryBitcoinAddress + base58encoded diff --git a/src/helper_inbox.py b/src/helper_inbox.py deleted file mode 100644 index 555795df..00000000 --- a/src/helper_inbox.py +++ /dev/null @@ -1,35 +0,0 @@ -"""Helper Inbox performs inbox messages related operations""" - -import queues -from helper_sql import sqlExecute, sqlQuery - - -def insert(t): - """Perform an insert into the "inbox" table""" - sqlExecute('''INSERT INTO inbox VALUES (?,?,?,?,?,?,?,?,?,?)''', *t) - # shouldn't emit changedInboxUnread and displayNewInboxMessage - # at the same time - # queues.UISignalQueue.put(('changedInboxUnread', None)) - - -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) - - -def isMessageAlreadyInInbox(sigHash): - """Check for previous instances of this message""" - queryReturn = sqlQuery( - '''SELECT COUNT(*) FROM inbox WHERE sighash=?''', sigHash) - return queryReturn[0][0] != 0 diff --git a/src/helper_msgcoding.py b/src/helper_msgcoding.py deleted file mode 100644 index 05fa1c1b..00000000 --- a/src/helper_msgcoding.py +++ /dev/null @@ -1,159 +0,0 @@ -""" -Message encoding end decoding functions -""" - -import string -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 - - -class MsgEncode(object): - """Message encoder class""" - def __init__(self, message, encoding=BITMESSAGE_ENCODING_SIMPLE): - self.data = None - self.encoding = encoding - self.length = 0 - if self.encoding == BITMESSAGE_ENCODING_EXTENDED: - self.encodeExtended(message) - elif self.encoding == BITMESSAGE_ENCODING_SIMPLE: - 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) - except zlib.error: - logger.error("Error compressing message") - raise MsgEncodeException("Error compressing message") - except msgpack.exceptions.PackException: - logger.error("Error msgpacking message") - raise MsgEncodeException("Error msgpacking message") - self.length = len(self.data) - - def encodeSimple(self, message): - """Handle simple encoding""" - self.data = 'Subject:%(subject)s\nBody:%(body)s' % message - 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): - 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") - - 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) - except (msgpack.exceptions.UnpackException, - msgpack.exceptions.ExtraData): - logger.error("Error msgunpacking message") - raise MsgDecodeException("Error msgunpacking message") - - 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 - - def decodeSimple(self, data): - """Handle simple encoding""" - bodyPositionIndex = string.find(data, '\nBody:') - if bodyPositionIndex > 1: - subject = data[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:] - else: - subject = '' - body = data - # Throw away any extra lines (headers) after the subject. - if subject: - subject = subject.splitlines()[0] - self.subject = subject - self.body = body diff --git a/src/helper_random.py b/src/helper_random.py deleted file mode 100644 index 2e6a151b..00000000 --- a/src/helper_random.py +++ /dev/null @@ -1,74 +0,0 @@ -"""Convenience functions for random operations. Not suitable for security / cryptography operations.""" - -import os -import random - -try: - from pyelliptic.openssl import OpenSSL -except ImportError: - from .pyelliptic.openssl import OpenSSL - -NoneType = type(None) - - -def seed(): - """Initialize random number generator""" - random.seed() - - -def randomBytes(n): - """Method randomBytes.""" - try: - return os.urandom(n) - except NotImplementedError: - return OpenSSL.rand(n) - - -def randomshuffle(population): - """Method randomShuffle. - - shuffle the sequence x in place. - shuffles the elements in list in place, - so they are in a random order. - As Shuffle will alter data in-place, - so its input must be a mutable sequence. - In contrast, sample produces a new list - and its input can be much more varied - (tuple, string, xrange, bytearray, set, etc) - """ - random.shuffle(population) - - -def randomsample(population, k): - """Method randomSample. - - return a k length list of unique elements - chosen from the population sequence. - Used for random sampling - without replacement, its called - partial shuffle. - """ - return random.sample(population, k) - - -def randomrandrange(x, y=None): - """Method randomRandrange. - - return a randomly selected element from - range(start, stop). This is equivalent to - choice(range(start, stop)), - but doesnt actually build a range object. - """ - if isinstance(y, NoneType): - return random.randrange(x) # nosec - return random.randrange(x, y) # nosec - - -def randomchoice(population): - """Method randomchoice. - - Return a random element from the non-empty - sequence seq. If seq is empty, raises - IndexError. - """ - return random.choice(population) # nosec diff --git a/src/helper_search.py b/src/helper_search.py deleted file mode 100644 index 9fcb88b5..00000000 --- a/src/helper_search.py +++ /dev/null @@ -1,113 +0,0 @@ -""" -Additional SQL helper for searching messages. -Used by :mod:`.bitmessageqt`. -""" - -from helper_sql import sqlQuery -from tr import _translate - - -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. - - :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' - else: - where = 'toaddress || fromaddress || subject || message' - - sqlStatementBase = 'SELECT toaddress, fromaddress, subject, ' + ( - 'status, ackdata, lastactiontime FROM sent ' if folder == 'sent' - else 'folder, msgid, 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 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: - 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' - 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 - return True diff --git a/src/helper_sent.py b/src/helper_sent.py deleted file mode 100644 index aa76e756..00000000 --- a/src/helper_sent.py +++ /dev/null @@ -1,69 +0,0 @@ -""" -Insert values into sent table -""" - -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 diff --git a/src/helper_sql.py b/src/helper_sql.py deleted file mode 100644 index 8dee9e0c..00000000 --- a/src/helper_sql.py +++ /dev/null @@ -1,153 +0,0 @@ -""" -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 - -from six.moves import queue - - -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) - - if args == (): - sqlSubmitQueue.put('') - elif isinstance(args[0], (list, tuple)): - sqlSubmitQueue.put(args[0]) - else: - sqlSubmitQueue.put(args) - queryreturn, _ = sqlReturnQueue.get() - sql_lock.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) - - if args == (): - sqlSubmitQueue.put('') - else: - sqlSubmitQueue.put(args) - _, rowcount = sqlReturnQueue.get() - sqlSubmitQueue.put('commit') - sql_lock.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() - 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.""" - - def __enter__(self): - sql_lock.acquire() - return self - - def __exit__(self, exc_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) - - if args == (): - sqlSubmitQueue.put('') - else: - sqlSubmitQueue.put(args) - sqlReturnQueue.get() diff --git a/src/helper_startup.py b/src/helper_startup.py deleted file mode 100644 index 52e1bf7a..00000000 --- a/src/helper_startup.py +++ /dev/null @@ -1,373 +0,0 @@ -""" -Startup operations. -""" -# pylint: disable=too-many-branches,too-many-statements - -import ctypes -import logging -import os -import platform -import socket -import sys -import time -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 - -try: - from plugins.plugin import get_plugin -except ImportError: - get_plugin = None - - -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 - - -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.') - 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) - - 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') - 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. - 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', '') - - # 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!" - - 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.') - else: - logger.info('Creating new config files in %s', state.appdata) - if not os.path.exists(state.appdata): - os.makedirs(state.appdata) - if not sys.platform.startswith('win'): - os.umask(0o077) - config.save() - else: - updateConfig() - config_ready.set() - - -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 - 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) diff --git a/src/highlevelcrypto.py b/src/highlevelcrypto.py index 1bdb1593..22f44ed3 100644 --- a/src/highlevelcrypto.py +++ b/src/highlevelcrypto.py @@ -1,144 +1,33 @@ -""" -High level cryptographic functions based on `.pyelliptic` OpenSSL bindings. - -.. note:: - Upstream pyelliptic was upgraded from SHA1 to SHA256 for signing. We must - `upgrade PyBitmessage gracefully. `_ - `More discussion. `_ -""" - -from binascii import hexlify - import pyelliptic -from pyelliptic import OpenSSL from pyelliptic import arithmetic as a - - -__all__ = ['encrypt', 'makeCryptor', 'pointMult', 'privToPub', 'sign', 'verify'] - - -def makeCryptor(privkey, curve='secp256k1'): - """Return a private `.pyelliptic.ECC` instance""" - 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 makeCryptor(privkey): + privkey_bin = '\x02\xca\x00 '+a.changebase(privkey,16,256,minlen=32) + pubkey = a.changebase(a.privtopub(privkey),16,256,minlen=65)[1:] + pubkey_bin = '\x02\xca\x00 '+pubkey[:32]+'\x00 '+pubkey[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:] - return pubkey_bin - - + pubkey_raw = a.changebase(pubkey[2:],16,256,minlen=64) + pubkey_bin = '\x02\xca\x00 '+pubkey_raw[:32]+'\x00 '+pubkey_raw[32:] + return pubkey_bin def makePubCryptor(pubkey): - """Return a public `.pyelliptic.ECC` instance""" - pubkey_bin = hexToPubkey(pubkey) - return pyelliptic.ECC(curve='secp256k1', pubkey=pubkey_bin) - - + 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) - - -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) - - -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 - - -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')) - 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))) - - return mb.raw - - except Exception: - 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) + return a.privtopub(privkey) +# 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): + return makeCryptor(hexPrivkey).sign(msg) +# Verifies with hex public key +def verify(msg,sig,hexPubkey): + return makePubCryptor(hexPubkey).verify(sig,msg) diff --git a/src/iconglossary.py b/src/iconglossary.py new file mode 100644 index 00000000..dc6a7ffd --- /dev/null +++ b/src/iconglossary.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'iconglossary.ui' +# +# Created: Tue Dec 18 14:31:18 2012 +# 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_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(QtGui.QApplication.translate("iconGlossaryDialog", "Icon Glossary", None, QtGui.QApplication.UnicodeUTF8)) + self.groupBox.setTitle(QtGui.QApplication.translate("iconGlossaryDialog", "Icon Glossary", None, QtGui.QApplication.UnicodeUTF8)) + self.label_2.setText(QtGui.QApplication.translate("iconGlossaryDialog", "You have no connections with other peers. ", None, QtGui.QApplication.UnicodeUTF8)) + self.label_4.setText(QtGui.QApplication.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 foward 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, QtGui.QApplication.UnicodeUTF8)) + self.labelPortNumber.setText(QtGui.QApplication.translate("iconGlossaryDialog", "You are using TCP port ?. (This can be changed in the settings).", None, QtGui.QApplication.UnicodeUTF8)) + self.label_6.setText(QtGui.QApplication.translate("iconGlossaryDialog", "You do have connections with other peers and your firewall is correctly configured.", None, QtGui.QApplication.UnicodeUTF8)) + +import bitmessage_icons_rc diff --git a/src/bitmessageqt/iconglossary.ui b/src/iconglossary.ui similarity index 92% rename from src/bitmessageqt/iconglossary.ui rename to src/iconglossary.ui index 1bac94c8..62ea6a98 100644 --- a/src/bitmessageqt/iconglossary.ui +++ b/src/iconglossary.ui @@ -50,7 +50,7 @@ - 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 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 foward 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. Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop @@ -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/images/bitmessage.icns b/src/images/bitmessage.icns deleted file mode 100644 index 5fe52d4e..00000000 Binary files a/src/images/bitmessage.icns and /dev/null differ 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/can-icon-16px.png b/src/images/can-icon-16px.png index c649919f..0603328e 100644 Binary files a/src/images/can-icon-16px.png and b/src/images/can-icon-16px.png differ diff --git a/src/images/can-icon-24px-green.png b/src/images/can-icon-24px-green.png index 652da9c0..247bebc7 100644 Binary files a/src/images/can-icon-24px-green.png and b/src/images/can-icon-24px-green.png differ diff --git a/src/images/can-icon-24px-red.png b/src/images/can-icon-24px-red.png index cf66fcbb..ca32b9ae 100644 Binary files a/src/images/can-icon-24px-red.png and b/src/images/can-icon-24px-red.png differ diff --git a/src/images/can-icon-24px-yellow.png b/src/images/can-icon-24px-yellow.png index 0a26470b..6470b000 100644 Binary files a/src/images/can-icon-24px-yellow.png and b/src/images/can-icon-24px-yellow.png differ diff --git a/src/images/can-icon-24px.png b/src/images/can-icon-24px.png index 30f7313e..45e397e3 100644 Binary files a/src/images/can-icon-24px.png and b/src/images/can-icon-24px.png differ diff --git a/src/images/greenicon.png b/src/images/greenicon.png index cedf9dc2..16bce12d 100644 Binary files a/src/images/greenicon.png and b/src/images/greenicon.png 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/no_identicons.png b/src/images/no_identicons.png deleted file mode 100644 index 513f1d10..00000000 Binary files a/src/images/no_identicons.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/images/qidenticon.png b/src/images/qidenticon.png deleted file mode 100644 index 21abec78..00000000 Binary files a/src/images/qidenticon.png and /dev/null differ diff --git a/src/images/qidenticon_two.png b/src/images/qidenticon_two.png deleted file mode 100644 index 35180659..00000000 Binary files a/src/images/qidenticon_two.png and /dev/null differ diff --git a/src/images/qidenticon_two_x.png b/src/images/qidenticon_two_x.png deleted file mode 100644 index a4851205..00000000 Binary files a/src/images/qidenticon_two_x.png and /dev/null differ diff --git a/src/images/qidenticon_x.png b/src/images/qidenticon_x.png deleted file mode 100644 index 07e903e5..00000000 Binary files a/src/images/qidenticon_x.png and /dev/null differ diff --git a/src/images/redicon.png b/src/images/redicon.png index dfc84b47..9d8440be 100644 Binary files a/src/images/redicon.png and b/src/images/redicon.png differ diff --git a/src/images/yellowicon.png b/src/images/yellowicon.png index 95e451d5..31bf1909 100644 Binary files a/src/images/yellowicon.png and b/src/images/yellowicon.png differ diff --git a/src/inventory.py b/src/inventory.py deleted file mode 100644 index dc8e36bf..00000000 --- a/src/inventory.py +++ /dev/null @@ -1,50 +0,0 @@ -"""The Inventory singleton""" - -# TODO make this dynamic, and watch out for frozen, like with messagetypes -import storage.filesystem -import storage.sqlite -from bmconfigparser import config -from singleton import Singleton - - -def create_inventory_instance(backend="sqlite"): - """ - Create an instance of the inventory class - defined in `storage.`. - """ - return getattr( - getattr(storage, backend), - "{}Inventory".format(backend.title()))() - - -@Singleton -class Inventory(): - """ - Inventory singleton class which uses storage backends - to manage the inventory. - """ - def __init__(self): - self._moduleName = config.safeGet("inventory", "storage") - self._realInventory = create_inventory_instance(self._moduleName) - 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 deleted file mode 100644 index fe02d3f4..00000000 --- a/src/l10n.py +++ /dev/null @@ -1,152 +0,0 @@ -"""Localization helpers""" - -import logging -import os -import re -import sys -import time - -from six.moves import range - -from bmconfigparser import config - -logger = logging.getLogger('default') - -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 - - -windowsLanguageMap = { - "ar": "arabic", - "cs": "czech", - "da": "danish", - "de": "german", - "en": "english", - "eo": "esperanto", - "fr": "french", - "it": "italian", - "ja": "japanese", - "nl": "dutch", - "no": "norwegian", - "pl": "polish", - "pt": "portuguese", - "ru": "russian", - "sk": "slovak", - "zh": "chinese", - "zh_CN": "chinese-simplified", - "zh_HK": "chinese-traditional", - "zh_SG": "chinese-simplified", - "zh_TW": "chinese-traditional" -} - - -time_format = config.safeGet( - 'bitmessagesettings', 'timeformat', DEFAULT_TIME_FORMAT) - -if not re.search(r'\d', time.strftime(time_format)): - 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: - 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 - logger.exception('Could not decode locale formatted timestamp') - # 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. - if timestamp is not None and not isinstance(timestamp, int): - try: - timestamp = int(timestamp) - except (ValueError, TypeError): - timestamp = None - - # 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 - 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) - 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 - - -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: - loc = posixLocale.split(".", 1) - if loc[0] in windowsLanguageMap: - return windowsLanguageMap[loc[0]] - if "_" in posixLocale: - loc = posixLocale.split("_", 1) - if loc[0] in windowsLanguageMap: - return windowsLanguageMap[loc[0]] - if posixLocale != DEFAULT_LANGUAGE: - return getWindowsLocale(DEFAULT_LANGUAGE) - return None diff --git a/src/main.py b/src/main.py deleted file mode 100644 index e1644436..00000000 --- a/src/main.py +++ /dev/null @@ -1,13 +0,0 @@ -"""This module is for thread start.""" -import state -import sys -from bitmessagemain import main -from termcolor import colored -print(colored('kivy is not supported at the moment for this version..', 'red')) -sys.exit() - - -if __name__ == '__main__': - state.kivy = True - print("Kivy Loading......") - main() diff --git a/src/messages.dat reader.py b/src/messages.dat reader.py new file mode 100644 index 00000000..286739e0 --- /dev/null +++ b/src/messages.dat reader.py @@ -0,0 +1,118 @@ +#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 + +APPNAME = "PyBitmessage" +from os import path, environ +if sys.platform == 'darwin': + if "HOME" in environ: + appdata = path.join(environ["HOME"], "Library/Application support/", APPNAME) + '/' + else: + print 'Could not find home folder, please report this message and your OS X version to the BitMessage Github.' + sys.exit() +elif 'win' in sys.platform: + appdata = path.join(environ['APPDATA'], APPNAME) + '\\' +else: + appdata = path.expanduser(path.join("~", "." + APPNAME + "/")) + +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''' + parameters = '' + cur.execute(item, parameters) + output = cur.fetchall() + for row in output: + msgid, toaddress, toripe, fromaddress, subject, message, ackdata, lastactiontime, status, pubkeyretrynumber, msgretrynumber, folder, encodingtype = row + print msgid.encode('hex'), toaddress, 'toripe:', toripe.encode('hex'), 'fromaddress:', fromaddress, 'ENCODING TYPE:', encodingtype, 'SUBJECT:', repr(subject), 'MESSAGE:', repr(message), 'ACKDATA:', ackdata.encode('hex'), lastactiontime, status, pubkeyretrynumber, msgretrynumber, 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 hash, havecorrectnonce, transmitdata, time, usedpersonally from pubkeys''' + parameters = '' + cur.execute(item, parameters) + output = cur.fetchall() + for row in output: + hash, havecorrectnonce, transmitdata, time, usedpersonally = row + print 'Hash:', hash.encode('hex'), '\tHave correct nonce:', havecorrectnonce, '\tTime first broadcast:', unicode(strftime('%a, %d %b %Y %I:%M %p',localtime(time))), '\tUsed by me personally:', usedpersonally, '\tFull pubkey message:', transmitdata.encode('hex') + +def readInventory(): + print 'Printing everything in inventory table:' + item = '''select hash, objecttype, streamnumber, payload, receivedtime from inventory''' + parameters = '' + cur.execute(item, parameters) + output = cur.fetchall() + for row in output: + hash, objecttype, streamnumber, payload, receivedtime = row + print 'Hash:', hash.encode('hex'), objecttype, streamnumber, '\t', payload.encode('hex'), '\t', unicode(strftime('%a, %d %b %Y %I:%M %p',localtime(receivedtime))) + + +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() + 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/multiqueue.py b/src/multiqueue.py deleted file mode 100644 index 88b6a4dd..00000000 --- a/src/multiqueue.py +++ /dev/null @@ -1,54 +0,0 @@ -""" -A queue with multiple internal subqueues. -Elements are added into a random subqueue, and retrieval rotates -""" - -from collections import deque - -from six.moves import queue - -try: - import helper_random -except ImportError: - from . import helper_random - - -class MultiQueue(queue.Queue): - """A base queue class""" - # pylint: disable=redefined-builtin,attribute-defined-outside-init - 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[helper_random.randomrandrange(self.queueCount)].append( - (item)) - - # Get an item from the queue - def _get(self): - return self.queues[self.iter].popleft() - - def iterate(self): - """Increment the iteration counter""" - self.iter = (self.iter + 1) % self.queueCount - - def totalSize(self): - """Return the total number of items in all the queues""" - return sum(len(x) for x in self.queues) diff --git a/src/namecoin.py b/src/namecoin.py deleted file mode 100644 index a16cb3d7..00000000 --- a/src/namecoin.py +++ /dev/null @@ -1,373 +0,0 @@ -""" -Namecoin queries -""" -# pylint: disable=too-many-branches,protected-access - -import base64 -import httplib -import json -import os -import socket -import sys - -import defaults -from addresses import decodeAddress -from bmconfigparser import config -from debug import logger -from tr import _translate # translate - -configSection = "bitmessagesettings" - - -class RPCError(Exception): - """Error thrown when the RPC call returns an error.""" - - error = None - - def __init__(self, data): - super(RPCError, self).__init__() - 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.""" - - user = None - password = None - host = None - port = None - 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). - """ - 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") - else: - self.nmctype = options["type"] - self.host = options["host"] - self.port = int(options["port"]) - self.user = options["user"] - self.password = options["password"] - - assert self.nmctype == "namecoind" or self.nmctype == "nmcontrol" - if self.nmctype == "namecoind": - self.con = httplib.HTTPConnection(self.host, self.port, timeout=3) - - 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("/") - if slashPos < 0: - display_name = identity - identity = "id/" + identity - else: - display_name = identity.split("/")[1] - - try: - if self.nmctype == "namecoind": - res = self.callRPC("name_show", [identity]) - res = res["value"] - elif self.nmctype == "nmcontrol": - res = self.callRPC("data", ["getValue", identity]) - res = res["reply"] - if not res: - return (_translate( - "MainWindow", "The name %1 was not found." - ).arg(identity.decode("utf-8", "ignore")), None) - else: - assert False - except RPCError as exc: - logger.exception("Namecoin query RPC exception") - if isinstance(exc.error, dict): - errmsg = exc.error["message"] - else: - errmsg = exc.error - return (_translate( - "MainWindow", "The namecoin query failed (%1)" - ).arg(errmsg.decode("utf-8", "ignore")), None) - except AssertionError: - return (_translate( - "MainWindow", "Unknown namecoin interface type: %1" - ).arg(self.nmctype.decode("utf-8", "ignore")), None) - except Exception: - logger.exception("Namecoin query exception") - return (_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") - - 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) - - def test(self): - """ - Test the connection settings. This routine tries to query a "getinfo" - command, and builds either an error message or a success message with - some info from it. - """ - try: - if self.nmctype == "namecoind": - try: - vers = self.callRPC("getinfo", [])["version"] - except RPCError: - vers = self.callRPC("getnetworkinfo", [])["version"] - - v3 = vers % 100 - vers = vers / 100 - v2 = vers % 100 - vers = vers / 100 - v1 = vers - if v3 == 0: - 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"))) - - elif self.nmctype == "nmcontrol": - 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." - ) - ) - - logger.error("Unexpected nmcontrol reply: %s", res) - message = ( - "failed", - _translate( - "MainWindow", - "Couldn\'t understand NMControl." - ) - ) - - else: - sys.exit("Unsupported Namecoin type") - - 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.""" - - data = {"method": method, "params": params, "id": self.queryid} - if self.nmctype == "namecoind": - resp = self.queryHTTP(json.dumps(data)) - elif self.nmctype == "nmcontrol": - resp = self.queryServer(json.dumps(data)) - else: - assert False - val = json.loads(resp) - - if val["id"] != self.queryid: - raise Exception("ID mismatch in JSON RPC answer.") - - if self.nmctype == "namecoind": - self.queryid = self.queryid + 1 - - error = val["error"] - if error is None: - return val["result"] - - if isinstance(error, bool): - raise RPCError(val["result"]) - raise RPCError(error) - - def queryHTTP(self, data): - """Query the server via HTTP.""" - - 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 - - return result - - def queryServer(self, data): - """Helper routine sending data to the RPC " - "server and returning the result.""" - - 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) - result = "" - - while True: - tmp = s.recv(self.bufsize) - if not tmp: - break - result += tmp - - 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! - """ - - 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) + "/" - else: - sys.exit( - "Could not find home folder, please report this message" - " and your OS X version to the BitMessage Github." - ) - - elif "win32" in sys.platform or "win64" in sys.platform: - dataFolder = path.join(environ["APPDATA"], app) + "\\" - else: - dataFolder = path.join(environ["HOME"], ".%s" % app) + "/" - - return dataFolder - - -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") - - # Try to read user/password from .namecoin configuration file. - defaultUser = "" - defaultPass = "" - nmcFolder = lookupNamecoinFolder() - nmcConfig = nmcFolder + "namecoin.conf" - try: - nmc = open(nmcConfig, "r") - - while True: - line = nmc.readline() - if line == "": - break - parts = line.split("=") - if len(parts) == 2: - key = parts[0] - 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 - - 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) - - # If still nothing found, set empty at least. - if not hasUser: - config.set(configSection, "namecoinrpcuser", defaultUser) - if not hasPass: - config.set(configSection, "namecoinrpcpassword", defaultPass) - - # Set default port now, possibly to found value. - if not hasPort: - config.set(configSection, "namecoinrpcport", defaults.namecoinDefaultRpcPort) diff --git a/src/network/__init__.py b/src/network/__init__.py deleted file mode 100644 index 1b5aef92..00000000 --- a/src/network/__init__.py +++ /dev/null @@ -1,47 +0,0 @@ -""" -Network subsystem package -""" - -try: - from .announcethread import AnnounceThread - from .connectionpool import BMConnectionPool -except ImportError: - AnnounceThread = None - BMConnectionPool = None -from .threads import StoppableThread - - -__all__ = ["AnnounceThread", "BMConnectionPool", "StoppableThread"] - - -def start(config, state): - """Start network threads""" - from .addrthread import AddrThread - from .dandelion import Dandelion - from .downloadthread import DownloadThread - from .invthread import InvThread - from .networkthread import BMNetworkThread - from .knownnodes import readKnownNodes - from .receivequeuethread import ReceiveQueueThread - from .uploadthread import UploadThread - - readKnownNodes() - # init, needs to be early because other thread may access it early - Dandelion() - BMConnectionPool().connectToStream(1) - for thread in ( - BMNetworkThread(), InvThread(), AddrThread(), - DownloadThread(), UploadThread() - ): - thread.daemon = True - thread.start() - - # Optional components - for i in range(config.getint('threads', 'receive')): - thread = ReceiveQueueThread(i) - thread.daemon = True - thread.start() - if config.safeGetBoolean('bitmessagesettings', 'udp'): - state.announceThread = AnnounceThread() - state.announceThread.daemon = True - state.announceThread.start() diff --git a/src/network/addrthread.py b/src/network/addrthread.py deleted file mode 100644 index fea0910e..00000000 --- a/src/network/addrthread.py +++ /dev/null @@ -1,50 +0,0 @@ -""" -Announce addresses as they are received from other hosts -""" -from six.moves import queue - -# magic imports! -import state -from helper_random import randomshuffle -from protocol import assembleAddrMessage -from queues import addrQueue # FIXME: init with queue -from network.connectionpool import BMConnectionPool - -from threads import StoppableThread - - -class AddrThread(StoppableThread): - """(Node) address broadcasting thread""" - name = "AddrBroadcaster" - - def run(self): - while not state.shutdown: - chunk = [] - while True: - try: - data = addrQueue.get(False) - chunk.append(data) - except queue.Empty: - break - - if chunk: - # Choose peers randomly - connections = BMConnectionPool().establishedConnections() - randomshuffle(connections) - for i in connections: - randomshuffle(chunk) - filtered = [] - for stream, peer, seen, destination in chunk: - # peer's own address or address received from peer - if i.destination in (peer, destination): - continue - if stream not in i.streams: - continue - filtered.append((stream, peer, seen)) - if filtered: - i.append_write_buf(assembleAddrMessage(filtered)) - - 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 84807757..00000000 --- a/src/network/announcethread.py +++ /dev/null @@ -1,44 +0,0 @@ -""" -Announce myself (node address) -""" -import time - -# magic imports! -import state -from bmconfigparser import config -from protocol import assembleAddrMessage -from network.connectionpool import BMConnectionPool - -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 and state.shutdown == 0: - 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 BMConnectionPool().udpSockets.values(): - if not connection.announcing: - continue - for stream in state.streamsInWhichIAmParticipating: - addr = ( - stream, - Peer( - '127.0.0.1', - config.safeGetInt('bitmessagesettings', 'port')), - int(time.time())) - connection.append_write_buf(assembleAddrMessage([addr])) diff --git a/src/network/asyncore_pollchoose.py b/src/network/asyncore_pollchoose.py deleted file mode 100644 index bdd312c6..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 sys -import time -import warnings -from errno import ( - EADDRINUSE, EAGAIN, EALREADY, EBADF, ECONNABORTED, ECONNREFUSED, - ECONNRESET, EHOSTUNREACH, EINPROGRESS, EINTR, EINVAL, EISCONN, ENETUNREACH, - ENOTCONN, ENOTSOCK, EPIPE, ESHUTDOWN, ETIMEDOUT, EWOULDBLOCK, errorcode -) -from threading import current_thread - -import helper_random - -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 helper_random.randomsample(r, len(r)): - obj = map.get(fd) - if obj is None: - continue - read(obj) - - for fd in helper_random.randomsample(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 helper_random.randomsample(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 helper_random.randomsample(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 = helper_random.randomsample(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 5d7fdcbd..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 -from addresses import calculateInventoryHash -from inventory import Inventory -from network.dandelion import Dandelion - -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 state.streamsInWhichIAmParticipating: - logger.debug( - 'The streamNumber %i isn\'t one we are interested in.', - self.streamNumber) - raise BMObjectUnwantedStreamError() - - def checkAlreadyHave(self): - """ - Check if we already have the object - (so that we don't duplicate it in inventory - or advertise it unnecessarily) - """ - # if it's a stem duplicate, pretend we don't have it - if Dandelion().hasHash(self.inventoryHash): - return - if self.inventoryHash in 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 b03626eb..00000000 --- a/src/network/bmproto.py +++ /dev/null @@ -1,679 +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 connectionpool -import knownnodes -import protocol -import state -from bmconfigparser import config -from inventory import Inventory -from queues import invQueue, objectProcessorQueue, portCheckerQueue -from randomtrackingdict import RandomTrackingDict -from network.advanceddispatcher import AdvancedDispatcher -from network.bmobject import ( - BMObject, BMObjectAlreadyHaveError, BMObjectExpiredError, - BMObjectInsufficientPOWError, BMObjectInvalidError, - BMObjectUnwantedStreamError -) -from network.dandelion import Dandelion -from network.proxy import ProxyError - -from node import Node, Peer -from objectracker import ObjectTracker, missingObjects - - -logger = logging.getLogger('default') - - -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, dandelion=False): - """ - Common inv announce implementation: - both inv and dinv depending on *dandelion* kwarg - """ - items = self.decode_payload_content("l32s") - - if len(items) > protocol.MAX_OBJECT_COUNT: - logger.error( - 'Too many items in %sinv message!', 'd' if dandelion else '') - raise BMProtoExcessiveDataError() - - # ignore dinv if dandelion turned off - if dandelion and not state.dandelion: - return True - - for i in map(str, items): - if i in Inventory() and not Dandelion().hasHash(i): - continue - if dandelion and not Dandelion().hasHash(i): - Dandelion().addHash(i, self) - self.handleReceivedInventory(i) - - return True - - def bm_command_inv(self): - """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 Inventory() and Dandelion().hasHash( - self.object.inventoryHash): - Dandelion().removeHash( - self.object.inventoryHash, "cycle detection") - - Inventory()[self.object.inventoryHash] = ( - self.object.objectType, self.object.streamNumber, - buffer(self.payload[objectOffset:]), self.object.expiresTime, # noqa: F821 - buffer(self.object.tag) # noqa: F821 - ) - self.handleReceivedObject( - self.object.streamNumber, self.object.inventoryHash) - invQueue.put(( - self.object.streamNumber, self.object.inventoryHash, - self.destination)) - return True - - def _decode_addr(self): - return self.decode_payload_content("LQIQ16sH") - - def bm_command_addr(self): - """Incoming addresses, process them""" - # not using services - for seenTime, stream, _, ip, port in self._decode_addr(): - ip = str(ip) - if ( - stream not in state.streamsInWhichIAmParticipating - # FIXME: should check against complete list - or ip.startswith('bootstrap') - ): - 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.BMConnectionPool().streams, True, - nodeid=self.nodeid)) - logger.debug( - '%(host)s:%(port)i sending version', - self.destination._asdict()) - if ((self.services & protocol.NODE_SSL == protocol.NODE_SSL) - and protocol.haveSSL(not self.isOutbound)): - self.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.BMConnectionPool().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.BMConnectionPool().inboundConnections - or len(connectionpool.BMConnectionPool()) - > config.safeGetInt( - 'bitmessagesettings', 'maxtotalconnections') - + config.safeGetInt( - 'bitmessagesettings', 'maxbootstrapconnections') - ): - self.append_write_buf(protocol.assembleErrorMessage( - errorText="Server full, please try again later.", fatal=2)) - logger.debug( - 'Closed connection to %s due to server full' - ' or duplicate inbound/outbound.', self.destination) - return False - if connectionpool.BMConnectionPool().isAlreadyConnected(self.nonce): - self.append_write_buf(protocol.assembleErrorMessage( - errorText="I'm connected to myself. Closing connection.", - fatal=2)) - logger.debug( - "Closed connection to %s because I'm connected to myself.", - self.destination) - return False - - return True - - @staticmethod - def stopDownloadingObject(hashId, forwardAnyway=False): - """Stop downloading object *hashId*""" - for connection in connectionpool.BMConnectionPool().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 d7062d24..00000000 --- a/src/network/connectionchooser.py +++ /dev/null @@ -1,78 +0,0 @@ -""" -Select which node to connect to -""" -# pylint: disable=too-many-branches -import logging -import random - -import knownnodes -import protocol -import state -from bmconfigparser import config -from queues import queue, portCheckerQueue - -logger = logging.getLogger('default') - - -def getDiscoveredPeer(): - """Get a peer from the local peer discovery list""" - try: - peer = random.choice(state.discoveredPeers.keys()) # nosec B311 - 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 4823b3c8..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 asyncore_pollchoose as asyncore -import helper_random -import knownnodes -import protocol -import state -from bmconfigparser import config -from connectionchooser import chooseConnection -from node import Peer -from proxy import Proxy -from singleton import Singleton -from tcp import ( - bootstrap, Socks4aBMConnection, Socks5BMConnection, - TCPConnection, TCPServer) -from udp import UDPSocket - -logger = logging.getLogger('default') - - -@Singleton -class BMConnectionPool(object): - """Pool of all existing connections""" - # pylint: disable=too-many-instance-attributes - - trustedPeer = None - """ - If the trustedpeer option is specified in keys.dat then this will - contain a Peer which will be connected to instead of using the - addresses advertised by other peers. - - The expected use case is where the user has a trusted server where - they run a Bitmessage daemon permanently. If they then run a second - instance of the client on a local machine periodically when they want - to check for messages it will sync with the network a lot faster - without compromising security. - """ - - def __init__(self): - asyncore.set_rates( - config.safeGetInt( - "bitmessagesettings", "maxdownloadrate"), - config.safeGetInt( - "bitmessagesettings", "maxuploadrate") - ) - 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) - state.streamsInWhichIAmParticipating.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 = helper_random.randomchoice([ - 'quzwelsuziwqgpt2.onion', None - ]) - elif proxy_type == 'SOCKS4a': - connection_base = Socks4aBMConnection # FIXME: I cannot test - else: - # This should never happen because socksproxytype setting - # is handled in bitmessagemain before starting the connectionpool - return - - bootstrapper = bootstrap(connection_base) - if not hostname: - port = helper_random.randomchoice([8080, 8444]) - hostname = 'bootstrap%s.bitmessage.org' % port - else: - port = 8444 - self.addConnection(bootstrapper(hostname, port)) - - def loop(self): # pylint: disable=too-many-branches,too-many-statements - """Main Connectionpool's loop""" - # pylint: disable=too-many-locals - # 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( - helper_random.randomchoice(self.streams)) - except ValueError: - continue - if chosen in self.outboundConnections: - continue - if chosen.host in self.inboundConnections: - continue - # don't connect to self - if chosen in state.ownAddresses: - continue - # 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) diff --git a/src/network/dandelion.py b/src/network/dandelion.py deleted file mode 100644 index 4f3cd07b..00000000 --- a/src/network/dandelion.py +++ /dev/null @@ -1,196 +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 - -import connectionpool -import state -from queues import invQueue -from singleton import Singleton - -# 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') - - -@Singleton -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() - - @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 addHash(self, hashId, source=None, stream=1): - """Add inventory vector to dandelion stem""" - if not state.dandelion: - return - with self.lock: - self.hashMap[hashId] = Stem( - self.getNodeStem(source), - stream, - self.poissonTimeout()) - - def setHashStream(self, hashId, stream=1): - """ - Update stream for inventory vector (as inv/dinv commands don't - include streams, we only learn this after receiving the object) - """ - with self.lock: - if hashId in self.hashMap: - self.hashMap[hashId] = Stem( - self.hashMap[hashId].child, - 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): - """ - 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): - """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)""" - with self.lock: - try: - # random two connections - self.stem = sample( - connectionpool.BMConnectionPool( - ).outboundConnections.values(), MAX_STEMS) - # not enough stems available - except ValueError: - self.stem = connectionpool.BMConnectionPool( - ).outboundConnections.values() - self.nodeMap = {} - # hashMap stays to cater for pending stems - self.refresh = time() + REASSIGN_INTERVAL diff --git a/src/network/downloadthread.py b/src/network/downloadthread.py deleted file mode 100644 index 0ae83b5b..00000000 --- a/src/network/downloadthread.py +++ /dev/null @@ -1,84 +0,0 @@ -""" -`DownloadThread` class definition -""" -import time - -import addresses -import helper_random -import protocol -from dandelion import Dandelion -from inventory import Inventory -from network.connectionpool import BMConnectionPool -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 = BMConnectionPool().establishedConnections() - helper_random.randomshuffle(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 Inventory() and not Dandelion().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 14217041..00000000 --- a/src/network/invthread.py +++ /dev/null @@ -1,111 +0,0 @@ -""" -Thread to send inv annoucements -""" -import Queue -import random -from time import time - -import addresses -import protocol -import state -from network.connectionpool import BMConnectionPool -from network.dandelion import Dandelion -from queues import 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 BMConnectionPool().connections(): - if not i.fullyEstablished: - continue - for x in expired: - streamNumber, hashid, _ = x - try: - del i.objectsNewToMe[hashid] - except KeyError: - if streamNumber in i.streams: - with i.objectsNewToThemLock: - i.objectsNewToThem[hashid] = time() - - -class InvThread(StoppableThread): - """Main thread that sends inv annoucements""" - - name = "InvBroadcaster" - - @staticmethod - def handleLocallyGenerated(stream, hashId): - """Locally generated inventory items require special handling""" - Dandelion().addHash(hashId, stream=stream) - for connection in BMConnectionPool().connections(): - if state.dandelion and connection != \ - Dandelion().objectChildStem(hashId): - continue - connection.objectsNewToThem[hashId] = time() - - def run(self): # pylint: disable=too-many-branches - while not state.shutdown: # pylint: disable=too-many-nested-blocks - chunk = [] - while True: - # Dandelion fluff trigger by expiration - handleExpiredDandelion(Dandelion().expire()) - try: - data = invQueue.get(False) - chunk.append((data[0], data[1])) - # locally generated - if len(data) == 2 or data[2] is None: - self.handleLocallyGenerated(data[0], data[1]) - except Queue.Empty: - break - - if chunk: - for connection in BMConnectionPool().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().objectChildStem(inv[1]): - # Fluff trigger by RNG - # auto-ignore if config set to 0, i.e. dandelion is off - if random.randint(1, 100) >= state.dandelion: # nosec B311 - 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() - - if Dandelion().refresh < time(): - Dandelion().reRandomiseStems() - - self.stop.wait(1) diff --git a/src/network/knownnodes.py b/src/network/knownnodes.py deleted file mode 100644 index b74c9a15..00000000 --- a/src/network/knownnodes.py +++ /dev/null @@ -1,267 +0,0 @@ -""" -Manipulations with knownNodes dictionary. -""" -# TODO: knownnodes object maybe? -# pylint: disable=global-statement - -import json -import logging -import os -import pickle # nosec B403 -import threading -import time -try: - from collections.abc import Iterable -except ImportError: - from collections import Iterable - -import state -from bmconfigparser import config -from network.node import Peer - -state.Peer = Peer - -knownNodesLock = threading.RLock() -"""Thread lock for knownnodes modification""" -knownNodes = {stream: {} for stream in range(1, 4)} -"""The dict of known nodes for each stream""" - -knownNodesTrimAmount = 2000 -"""trim stream knownnodes dict to this length""" - -knownNodesForgetRating = -0.5 -"""forget a node after rating is this low""" - -knownNodesActual = False - -logger = logging.getLogger('default') - -DEFAULT_NODES = ( - Peer('5.45.99.75', 8444), - Peer('75.167.159.54', 8444), - Peer('95.165.168.168', 8444), - Peer('85.180.139.241', 8444), - Peer('158.222.217.190', 8080), - Peer('178.62.12.187', 8448), - Peer('24.188.198.204', 8111), - Peer('109.147.204.113', 1195), - Peer('178.11.46.221', 8444) -) - - -def json_serialize_knownnodes(output): - """ - Reorganize knownnodes dict and write it as JSON to output - """ - _serialized = [] - for stream, peers in knownNodes.iteritems(): - for peer, info in peers.iteritems(): - info.update(rating=round(info.get('rating', 0), 2)) - _serialized.append({ - 'stream': stream, 'peer': peer._asdict(), 'info': info - }) - json.dump(_serialized, output, indent=4) - - -def json_deserialize_knownnodes(source): - """ - Read JSON from source and make knownnodes dict - """ - global knownNodesActual - for node in json.load(source): - peer = node['peer'] - info = node['info'] - peer = Peer(str(peer['host']), peer.get('port', 8444)) - knownNodes[node['stream']][peer] = info - if not (knownNodesActual - or info.get('self')) and peer not in DEFAULT_NODES: - knownNodesActual = True - - -def pickle_deserialize_old_knownnodes(source): - """ - Unpickle source and reorganize knownnodes dict if it has old format - the old format was {Peer:lastseen, ...} - the new format is {Peer:{"lastseen":i, "rating":f}} - """ - global knownNodes - knownNodes = pickle.load(source) # nosec B301 - for stream in knownNodes.keys(): - for node, params in knownNodes[stream].iteritems(): - if isinstance(params, (float, int)): - addKnownNode(stream, node, params) - - -def saveKnownNodes(dirName=None): - """Save knownnodes to filesystem""" - if dirName is None: - dirName = state.appdata - with knownNodesLock: - with open(os.path.join(dirName, 'knownnodes.dat'), 'wb') as output: - json_serialize_knownnodes(output) - - -def addKnownNode(stream, peer, lastseen=None, is_self=False): - """ - Add a new node to the dict or update lastseen if it already exists. - Do it for each stream number if *stream* is `Iterable`. - Returns True if added a new node. - """ - # pylint: disable=too-many-branches - if isinstance(stream, Iterable): - with knownNodesLock: - for s in stream: - addKnownNode(s, peer, lastseen, is_self) - return - - rating = 0.0 - if not lastseen: - # FIXME: maybe about 28 days? - lastseen = int(time.time()) - else: - lastseen = int(lastseen) - try: - info = knownNodes[stream].get(peer) - if lastseen > info['lastseen']: - info['lastseen'] = lastseen - except (KeyError, TypeError): - pass - else: - return - - if not is_self: - if len(knownNodes[stream]) > config.safeGetInt( - "knownnodes", "maxnodes"): - return - - knownNodes[stream][peer] = { - 'lastseen': lastseen, - 'rating': rating or 1 if is_self else 0, - 'self': is_self, - } - return True - - -def createDefaultKnownNodes(): - """Creating default Knownnodes""" - past = time.time() - 2418600 # 28 days - 10 min - for peer in DEFAULT_NODES: - addKnownNode(1, peer, past) - saveKnownNodes() - - -def readKnownNodes(): - """Load knownnodes from filesystem""" - try: - with open(state.appdata + 'knownnodes.dat', 'rb') as source: - with knownNodesLock: - try: - json_deserialize_knownnodes(source) - except ValueError: - source.seek(0) - pickle_deserialize_old_knownnodes(source) - except (IOError, OSError, KeyError, EOFError): - logger.debug( - 'Failed to read nodes from knownnodes.dat', exc_info=True) - createDefaultKnownNodes() - - # your own onion address, if setup - onionhostname = config.safeGet('bitmessagesettings', 'onionhostname') - if onionhostname and ".onion" in onionhostname: - onionport = config.safeGetInt('bitmessagesettings', 'onionport') - if onionport: - self_peer = Peer(onionhostname, onionport) - addKnownNode(1, self_peer, is_self=True) - state.ownAddresses[self_peer] = True - - -def increaseRating(peer): - """Increase rating of a peer node""" - increaseAmount = 0.1 - maxRating = 1 - with knownNodesLock: - for stream in knownNodes.keys(): - try: - knownNodes[stream][peer]["rating"] = min( - knownNodes[stream][peer]["rating"] + increaseAmount, - maxRating - ) - except KeyError: - pass - - -def decreaseRating(peer): - """Decrease rating of a peer node""" - decreaseAmount = 0.1 - minRating = -1 - with knownNodesLock: - for stream in knownNodes.keys(): - try: - knownNodes[stream][peer]["rating"] = max( - knownNodes[stream][peer]["rating"] - decreaseAmount, - minRating - ) - except KeyError: - pass - - -def trimKnownNodes(recAddrStream=1): - """Triming Knownnodes""" - if len(knownNodes[recAddrStream]) < \ - config.safeGetInt("knownnodes", "maxnodes"): - return - with knownNodesLock: - oldestList = sorted( - knownNodes[recAddrStream], - key=lambda x: x['lastseen'] - )[:knownNodesTrimAmount] - for oldest in oldestList: - del knownNodes[recAddrStream][oldest] - - -def dns(): - """Add DNS names to knownnodes""" - for port in [8080, 8444]: - addKnownNode( - 1, Peer('bootstrap%s.bitmessage.org' % port, port)) - - -def cleanupKnownNodes(): - """ - Cleanup knownnodes: remove old nodes and nodes with low rating - """ - global knownNodesActual - now = int(time.time()) - needToWriteKnownNodesToDisk = False - - with knownNodesLock: - for stream in knownNodes: - if stream not in state.streamsInWhichIAmParticipating: - continue - keys = knownNodes[stream].keys() - for node in keys: - if len(knownNodes[stream]) <= 1: # leave at least one node - if stream == 1: - knownNodesActual = False - break - try: - age = now - knownNodes[stream][node]["lastseen"] - # scrap old nodes (age > 28 days) - if age > 2419200: - needToWriteKnownNodesToDisk = True - del knownNodes[stream][node] - continue - # scrap old nodes (age > 3 hours) with low rating - if (age > 10800 and knownNodes[stream][node]["rating"] - <= knownNodesForgetRating): - needToWriteKnownNodesToDisk = True - del knownNodes[stream][node] - continue - except TypeError: - logger.warning('Error in %s', node) - keys = [] - - # Let us write out the knowNodes to disk - # if there is anything new to write out. - if needToWriteKnownNodesToDisk: - saveKnownNodes() diff --git a/src/network/networkthread.py b/src/network/networkthread.py deleted file mode 100644 index ef4f92ba..00000000 --- a/src/network/networkthread.py +++ /dev/null @@ -1,42 +0,0 @@ -""" -A thread to handle network concerns -""" -import network.asyncore_pollchoose as asyncore -import state -from network.connectionpool import BMConnectionPool -from queues import excQueue -from threads import StoppableThread - - -class BMNetworkThread(StoppableThread): - """Main network thread""" - name = "Asyncore" - - def run(self): - try: - while not self._stopped and state.shutdown == 0: - BMConnectionPool().loop() - except Exception as e: - excQueue.put((self.name, e)) - raise - - def stopThread(self): - super(BMNetworkThread, self).stopThread() - for i in BMConnectionPool().listeningSockets.values(): - try: - i.close() - except: # nosec B110 # pylint:disable=bare-except - pass - for i in BMConnectionPool().outboundConnections.values(): - try: - i.close() - except: # nosec B110 # pylint:disable=bare-except - pass - for i in BMConnectionPool().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 65e06de4..00000000 --- a/src/network/objectracker.py +++ /dev/null @@ -1,136 +0,0 @@ -""" -Module for tracking objects -""" -import time -from threading import RLock - -import network.connectionpool -from network.dandelion import Dandelion -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 network.connectionpool.BMConnectionPool().connections(): - if not i.fullyEstablished: - continue - try: - del i.objectsNewToMe[hashid] - except KeyError: - if streamNumber in i.streams and ( - not Dandelion().hasHash(hashid) - or Dandelion().objectChildStem(hashid) == i): - with i.objectsNewToThemLock: - i.objectsNewToThem[hashid] = time.time() - # update stream number, - # which we didn't have when we just received the dinv - # also resets expiration of the stem mode - Dandelion().setHashStream(hashid, streamNumber) - - if i == self: - try: - with i.objectsNewToThemLock: - del i.objectsNewToThem[hashid] - except KeyError: - pass - 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 56c01b77..00000000 --- a/src/network/receivequeuethread.py +++ /dev/null @@ -1,56 +0,0 @@ -""" -Process data incoming from network -""" -import errno -import Queue -import socket - -import state -from network.advanceddispatcher import UnknownStateError -from network.connectionpool import BMConnectionPool -from queues 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 and state.shutdown == 0: - try: - dest = receiveDataQueue.get(block=True, timeout=1) - except Queue.Empty: - continue - - if self._stopped or state.shutdown: - break - - # cycle as long as there is data - # methods should return False if there isn't enough data, - # or the connection is to be aborted - - # state_* methods should return False if there isn't - # enough data, or the connection is to be aborted - - try: - connection = BMConnectionPool().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 82e6c87f..00000000 --- a/src/network/stats.py +++ /dev/null @@ -1,78 +0,0 @@ -""" -Network statistics -""" -import time - -import asyncore_pollchoose as asyncore -from network.connectionpool import BMConnectionPool -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 BMConnectionPool().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 BMConnectionPool().inboundConnections.values() + \ - # BMConnectionPool().outboundConnections.values(): - # for k in connection.objectsNewToThem.keys(): - # tmp[k] = True - # This probably isn't the correct logic so it's disabled - # return len(tmp) - return 0 diff --git a/src/network/tcp.py b/src/network/tcp.py deleted file mode 100644 index 0bfde3bb..00000000 --- a/src/network/tcp.py +++ /dev/null @@ -1,450 +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 helper_random -import l10n -import protocol -import state -from bmconfigparser import config -from helper_random import randomBytes -from inventory import Inventory -from queues import invQueue, receiveDataQueue, UISignalQueue -from tr import _translate - -import asyncore_pollchoose as asyncore -import connectionpool -import knownnodes -from network.advanceddispatcher import AdvancedDispatcher -from network.bmproto import BMProto -from network.dandelion import Dandelion -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().maybeAddStem(self) - self.sendAddr() - self.sendBigInv() - - def sendAddr(self): - """Send a partial list of known addresses to peer.""" - # We are going to share a maximum number of 1000 addrs (per overlapping - # stream) with our peer. 500 from overlapping streams, 250 from the - # left child stream, and 250 from the right child stream. - maxAddrCount = config.safeGetInt( - "bitmessagesettings", "maxaddrperstreamsend", 500) - - 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] = helper_random.randomsample(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 Inventory().unexpired_hashes_by_stream(stream): - # don't advertise stem objects on bigInv - if Dandelion().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.BMConnectionPool().streams, - 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().maybeRemoveStem(self) - else: - self.checkTimeOffsetNotification() - if host_is_global: - knownnodes.decreaseRating(self.destination) - BMProto.handle_close(self) - - -class Socks5BMConnection(Socks5Connection, TCPConnection): - """SOCKS5 wrapper for TCP connections""" - - def __init__(self, address): - Socks5Connection.__init__(self, address=address) - TCPConnection.__init__(self, address=address, sock=self.socket) - self.set_state("init") - - def state_proxy_handshake_done(self): - """ - State when SOCKS5 connection succeeds, we need to send a - Bitmessage handshake to peer. - """ - Socks5Connection.state_proxy_handshake_done(self) - self.nodeid = randomBytes(8) - self.append_write_buf( - protocol.assembleVersionMessage( - self.destination.host, self.destination.port, - connectionpool.BMConnectionPool().streams, - False, nodeid=self.nodeid)) - self.set_state("bm_header", expectBytes=protocol.Header.size) - return True - - -class Socks4aBMConnection(Socks4aConnection, TCPConnection): - """SOCKS4a wrapper for TCP connections""" - - def __init__(self, address): - Socks4aConnection.__init__(self, address=address) - TCPConnection.__init__(self, address=address, sock=self.socket) - self.set_state("init") - - def state_proxy_handshake_done(self): - """ - State when SOCKS4a connection succeeds, we need to send a - Bitmessage handshake to peer. - """ - Socks4aConnection.state_proxy_handshake_done(self) - self.nodeid = randomBytes(8) - self.append_write_buf( - protocol.assembleVersionMessage( - self.destination.host, self.destination.port, - connectionpool.BMConnectionPool().streams, - False, nodeid=self.nodeid)) - self.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.BMConnectionPool()) - > config.safeGetInt( - 'bitmessagesettings', 'maxtotalconnections') - + config.safeGetInt( - 'bitmessagesettings', 'maxbootstrapconnections') + 10 - ): - # 10 is a sort of buffer, in between it will go through - # the version handshake and return an error to the peer - logger.warning("Server full, dropping connection") - sock.close() - return - try: - connectionpool.BMConnectionPool().addConnection( - TCPConnection(sock=sock)) - except socket.error: - pass 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 a3774b44..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 queues 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 1a9891ec..00000000 --- a/src/network/udp.py +++ /dev/null @@ -1,149 +0,0 @@ -""" -UDP protocol handler -""" -import logging -import socket -import time - -# magic imports! -import protocol -import state -from queues 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 state.streamsInWhichIAmParticipating: - 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 7d80d789..00000000 --- a/src/network/uploadthread.py +++ /dev/null @@ -1,69 +0,0 @@ -""" -`UploadThread` class definition -""" -import time - -import helper_random -import protocol -from inventory import Inventory -from network.connectionpool import BMConnectionPool -from network.dandelion import Dandelion -from randomtrackingdict import RandomTrackingDict -from threads import StoppableThread - - -class UploadThread(StoppableThread): - """ - This is a thread that uploads the objects that the peers requested from me - """ - maxBufSize = 2097152 # 2MB - name = "Uploader" - - def run(self): - while not self._stopped: - uploaded = 0 - # Choose uploading peers randomly - connections = BMConnectionPool().establishedConnections() - helper_random.randomshuffle(connections) - for i in connections: - now = time.time() - # avoid unnecessary delay - if i.skipUntil >= now: - continue - if len(i.write_buf) > self.maxBufSize: - continue - try: - request = i.pendingUpload.randomKeys( - RandomTrackingDict.maxPending) - except KeyError: - continue - payload = bytearray() - chunk_count = 0 - for chunk in request: - del i.pendingUpload[chunk] - if Dandelion().hasHash(chunk) and \ - i != Dandelion().objectChildStem(chunk): - i.antiIntersectionDelay() - self.logger.info( - '%s asked for a stem object we didn\'t offer to it.', - i.destination) - break - try: - payload.extend(protocol.CreatePacket( - 'object', Inventory()[chunk].payload)) - chunk_count += 1 - except KeyError: - i.antiIntersectionDelay() - self.logger.info( - '%s asked for an object we don\'t have.', - i.destination) - break - if not chunk_count: - continue - i.append_write_buf(payload) - self.logger.debug( - '%s:%i Uploading %i objects', - i.destination.host, i.destination.port, chunk_count) - uploaded += chunk_count - if not uploaded: - self.stop.wait(1) diff --git a/src/newaddressdialog.py b/src/newaddressdialog.py new file mode 100644 index 00000000..637c3a46 --- /dev/null +++ b/src/newaddressdialog.py @@ -0,0 +1,184 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'newaddressdialog.ui' +# +# Created: Tue Apr 30 12:21:14 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_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(QtGui.QApplication.translate("NewAddressDialog", "Create new Address", None, QtGui.QApplication.UnicodeUTF8)) + self.label.setText(QtGui.QApplication.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, QtGui.QApplication.UnicodeUTF8)) + self.label_5.setText(QtGui.QApplication.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, QtGui.QApplication.UnicodeUTF8)) + self.radioButtonRandomAddress.setText(QtGui.QApplication.translate("NewAddressDialog", "Use a random number generator to make an address", None, QtGui.QApplication.UnicodeUTF8)) + self.radioButtonDeterministicAddress.setText(QtGui.QApplication.translate("NewAddressDialog", "Use a passpharase to make addresses", None, QtGui.QApplication.UnicodeUTF8)) + self.checkBoxEighteenByteRipe.setText(QtGui.QApplication.translate("NewAddressDialog", "Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter", None, QtGui.QApplication.UnicodeUTF8)) + self.groupBoxDeterministic.setTitle(QtGui.QApplication.translate("NewAddressDialog", "Make deterministic addresses", None, QtGui.QApplication.UnicodeUTF8)) + self.label_9.setText(QtGui.QApplication.translate("NewAddressDialog", "Address version number: 3", None, QtGui.QApplication.UnicodeUTF8)) + self.label_8.setText(QtGui.QApplication.translate("NewAddressDialog", "In addition to your passphrase, you must remember these numbers:", None, QtGui.QApplication.UnicodeUTF8)) + self.label_6.setText(QtGui.QApplication.translate("NewAddressDialog", "Passphrase", None, QtGui.QApplication.UnicodeUTF8)) + self.label_11.setText(QtGui.QApplication.translate("NewAddressDialog", "Number of addresses to make based on your passphrase:", None, QtGui.QApplication.UnicodeUTF8)) + self.label_10.setText(QtGui.QApplication.translate("NewAddressDialog", "Stream number: 1", None, QtGui.QApplication.UnicodeUTF8)) + self.label_7.setText(QtGui.QApplication.translate("NewAddressDialog", "Retype passphrase", None, QtGui.QApplication.UnicodeUTF8)) + self.groupBox.setTitle(QtGui.QApplication.translate("NewAddressDialog", "Randomly generate address", None, QtGui.QApplication.UnicodeUTF8)) + self.label_2.setText(QtGui.QApplication.translate("NewAddressDialog", "Label (not shown to anyone except you)", None, QtGui.QApplication.UnicodeUTF8)) + self.radioButtonMostAvailable.setText(QtGui.QApplication.translate("NewAddressDialog", "Use the most available stream", None, QtGui.QApplication.UnicodeUTF8)) + self.label_3.setText(QtGui.QApplication.translate("NewAddressDialog", " (best if this is the first of many addresses you will create)", None, QtGui.QApplication.UnicodeUTF8)) + self.radioButtonExisting.setText(QtGui.QApplication.translate("NewAddressDialog", "Use the same stream as an existing address", None, QtGui.QApplication.UnicodeUTF8)) + self.label_4.setText(QtGui.QApplication.translate("NewAddressDialog", "(saves you some bandwidth and processing power)", None, QtGui.QApplication.UnicodeUTF8)) + diff --git a/src/bitmessageqt/newaddressdialog.ui b/src/newaddressdialog.ui similarity index 99% rename from src/bitmessageqt/newaddressdialog.ui rename to src/newaddressdialog.ui index 8b5276cc..3da926ae 100644 --- a/src/bitmessageqt/newaddressdialog.ui +++ b/src/newaddressdialog.ui @@ -76,7 +76,7 @@ The 'Random Number' option is selected by default but deterministic addresses ha - Use a passphrase to make addresses + Use a passpharase to make addresses buttonGroup @@ -99,7 +99,7 @@ The 'Random Number' option is selected by default but deterministic addresses ha - Address version number: 4 + Address version number: 3 @@ -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/newsubscriptiondialog.py b/src/newsubscriptiondialog.py new file mode 100644 index 00000000..b849d50f --- /dev/null +++ b/src/newsubscriptiondialog.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'newsubscriptiondialog.ui' +# +# Created: Fri Oct 19 15:30:41 2012 +# 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_NewSubscriptionDialog(object): + def setupUi(self, NewSubscriptionDialog): + NewSubscriptionDialog.setObjectName(_fromUtf8("NewSubscriptionDialog")) + NewSubscriptionDialog.resize(368, 192) + self.formLayout = QtGui.QFormLayout(NewSubscriptionDialog) + self.formLayout.setFieldGrowthPolicy(QtGui.QFormLayout.AllNonFixedFieldsGrow) + self.formLayout.setObjectName(_fromUtf8("formLayout")) + self.label_2 = QtGui.QLabel(NewSubscriptionDialog) + self.label_2.setObjectName(_fromUtf8("label_2")) + self.formLayout.setWidget(0, QtGui.QFormLayout.SpanningRole, self.label_2) + self.newsubscriptionlabel = QtGui.QLineEdit(NewSubscriptionDialog) + self.newsubscriptionlabel.setObjectName(_fromUtf8("newsubscriptionlabel")) + self.formLayout.setWidget(2, QtGui.QFormLayout.SpanningRole, self.newsubscriptionlabel) + self.label = QtGui.QLabel(NewSubscriptionDialog) + self.label.setObjectName(_fromUtf8("label")) + self.formLayout.setWidget(4, QtGui.QFormLayout.LabelRole, self.label) + spacerItem = QtGui.QSpacerItem(302, 10, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) + self.formLayout.setItem(4, QtGui.QFormLayout.FieldRole, spacerItem) + self.lineEditSubscriptionAddress = QtGui.QLineEdit(NewSubscriptionDialog) + self.lineEditSubscriptionAddress.setObjectName(_fromUtf8("lineEditSubscriptionAddress")) + self.formLayout.setWidget(5, QtGui.QFormLayout.SpanningRole, self.lineEditSubscriptionAddress) + 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(8, QtGui.QFormLayout.FieldRole, self.buttonBox) + spacerItem1 = QtGui.QSpacerItem(20, 10, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) + self.formLayout.setItem(7, QtGui.QFormLayout.FieldRole, spacerItem1) + self.labelSubscriptionAddressCheck = QtGui.QLabel(NewSubscriptionDialog) + self.labelSubscriptionAddressCheck.setText(_fromUtf8("")) + self.labelSubscriptionAddressCheck.setWordWrap(True) + self.labelSubscriptionAddressCheck.setObjectName(_fromUtf8("labelSubscriptionAddressCheck")) + self.formLayout.setWidget(6, QtGui.QFormLayout.SpanningRole, self.labelSubscriptionAddressCheck) + + 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(QtGui.QApplication.translate("NewSubscriptionDialog", "Add new entry", None, QtGui.QApplication.UnicodeUTF8)) + self.label_2.setText(QtGui.QApplication.translate("NewSubscriptionDialog", "Label", None, QtGui.QApplication.UnicodeUTF8)) + self.label.setText(QtGui.QApplication.translate("NewSubscriptionDialog", "Address", None, QtGui.QApplication.UnicodeUTF8)) + diff --git a/src/bitmessageqt/addaddressdialog.ui b/src/newsubscriptiondialog.ui similarity index 75% rename from src/bitmessageqt/addaddressdialog.ui rename to src/newsubscriptiondialog.ui index 09701fa4..84475e09 100644 --- a/src/bitmessageqt/addaddressdialog.ui +++ b/src/newsubscriptiondialog.ui @@ -1,21 +1,15 @@ - AddAddressDialog - + NewSubscriptionDialog + 0 0 368 - 232 + 192 - - - 368 - 200 - - Add new entry @@ -31,7 +25,7 @@ - + @@ -40,18 +34,21 @@
- - + + + + Qt::Vertical + + + + 302 + 10 + + + - - - - - - - true - - + + @@ -64,18 +61,28 @@ - + Qt::Vertical 20 - 40 + 10 + + + + + + + true + + + @@ -83,7 +90,7 @@ buttonBox accepted() - AddAddressDialog + NewSubscriptionDialog accept() @@ -99,7 +106,7 @@ buttonBox rejected() - AddAddressDialog + NewSubscriptionDialog reject() diff --git a/src/openclpow.py b/src/openclpow.py deleted file mode 100644 index 5391590c..00000000 --- a/src/openclpow.py +++ /dev/null @@ -1,111 +0,0 @@ -""" -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 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 f77f455a..cb20e667 100644 --- a/src/proofofwork.py +++ b/src/proofofwork.py @@ -1,396 +1,42 @@ -# pylint: disable=too-many-branches,too-many-statements,protected-access -""" -Proof of work calculation -""" - -import ctypes -import hashlib -import os -import sys -import tempfile -import time -from struct import pack, unpack -from subprocess import call # nosec B404 - -import openclpow -import paths -import queues -import state -import tr -from bmconfigparser import config -from 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) - - def _set_idle(): - if 'linux' in sys.platform: + import sys + try: + sys.getwindowsversion() + import win32api,win32process,win32con + pid = win32api.GetCurrentProcessId() + handle = win32api.OpenProcess(win32con.PROCESS_ALL_ACCESS, True, pid) + win32process.SetPriorityClass(handle, win32process.IDLE_PRIORITY_CLASS) + except: + import os os.nice(20) - else: - try: - # pylint: disable=no-member,import-error - sys.getwindowsversion() - import win32api - import win32process - import win32con - 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 - pass - def _pool_worker(nonce, initialHash, target, pool_size): + import hashlib + import sys + import os + from struct import unpack, pack _set_idle() - trialValue = float('inf') + trialValue = 99999999999999999999 while trialValue > target: - nonce += pool_size - trialValue, = unpack('>Q', hashlib.sha512(hashlib.sha512( - pack('>Q', nonce) + initialHash).digest()).digest()[0:8]) + nonce += pool_size + trialValue, = unpack('>Q',hashlib.sha512(hashlib.sha512(pack('>Q',nonce) + initialHash).digest()).digest()[0:8]) return [trialValue, nonce] - -def _doSafePoW(target, initialHash): - logger.debug("Safe PoW start") - nonce = 0 - trialValue = float('inf') - while trialValue > target and state.shutdown == 0: - nonce += 1 - trialValue, = unpack('>Q', hashlib.sha512(hashlib.sha512( - pack('>Q', nonce) + initialHash).digest()).digest()[0:8]) - if state.shutdown != 0: - raise StopIteration("Interrupted") # pylint: misplaced-bare-raise - logger.debug("Safe PoW done") - return [trialValue, nonce] - - -def _doFastPoW(target, initialHash): - logger.debug("Fast PoW start") +def run(target, initialHash): from multiprocessing import Pool, cpu_count + import time try: - pool_size = cpu_count() - except: # noqa:E722 - pool_size = 4 - try: - maxCores = config.getint('bitmessagesettings', 'maxcores') - except: # noqa:E722 - maxCores = 99999 - if pool_size > maxCores: - pool_size = maxCores - + pool_size = cpu_count() + except: + pool_size = 4 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))) while True: - if state.shutdown > 0: - try: - pool.terminate() - pool.join() - except: # nosec B110 # noqa:E722 # pylint:disable=bare-except - pass - raise StopIteration("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() - logger.debug("Fast PoW done") - return result[0], result[1] - time.sleep(0.2) - - -def _doCPoW(target, initialHash): - with LogOutput(): - h = initialHash - m = target - out_h = ctypes.pointer(ctypes.create_string_buffer(h, 64)) - out_m = ctypes.c_ulonglong(m) - logger.debug("C PoW start") - nonce = bmpow(out_h, out_m) - - trialValue, = unpack('>Q', hashlib.sha512(hashlib.sha512(pack('>Q', nonce) + initialHash).digest()).digest()[0:8]) - if state.shutdown != 0: - raise StopIteration("Interrupted") - logger.debug("C PoW done") - return [trialValue, nonce] - - -def _doGPUPoW(target, initialHash): - logger.debug("GPU PoW start") - nonce = openclpow.do_opencl_pow(initialHash.encode("hex"), target) - trialValue, = unpack('>Q', hashlib.sha512(hashlib.sha512(pack('>Q', nonce) + initialHash).digest()).digest()[0:8]) - 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 = [] - raise Exception("GPU did not calculate correctly.") - if state.shutdown != 0: - raise StopIteration("Interrupted") - logger.debug("GPU PoW done") - return [trialValue, nonce] - - -def estimate(difficulty, format=False): # pylint: disable=redefined-builtin - """ - .. todo: fix unused variable - """ - ret = difficulty / 10 - if ret < 1: - ret = 1 - - if format: - # pylint: disable=unused-variable - out = str(int(ret)) + " seconds" - if ret > 60: - ret /= 60 - out = str(int(ret)) + " minutes" - if ret > 60: - ret /= 60 - out = str(int(ret)) + " hours" - if ret > 24: - ret /= 24 - out = str(int(ret)) + " days" - if ret > 7: - out = str(int(ret)) + " weeks" - if ret > 31: - out = str(int(ret)) + " months" - 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 - call(["make", "-C", os.path.join(paths.codePath(), "bitmsghash"), - '-f', 'Makefile.bsd']) # nosec B607, B603 - else: - # GNU make - 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: # noqa:E722 - notifyBuild(True) - - -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(): - try: - return _doGPUPoW(target, initialHash) - except StopIteration: - raise - except: # nosec B110 # noqa:E722 # pylint:disable=bare-except - 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: - # 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) - 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() - - -# 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' - try: - # MSVS - bso = ctypes.WinDLL(os.path.join(paths.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 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) - 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: - bmpow = None - if bmpow is None: - buildCPoW() + for i in range(pool_size): + if result[i].ready(): + result = result[i].get() + pool.terminate() + return result[0], result[1] + time.sleep(1) diff --git a/src/protocol.py b/src/protocol.py deleted file mode 100644 index 722ce9c1..00000000 --- a/src/protocol.py +++ /dev/null @@ -1,578 +0,0 @@ -""" -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 fallback import RIPEMD160Hash -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 - - -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) - - -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 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)))) - - -# 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( - remoteHost, remotePort, participatingStreams, server=False, nodeid=None -): - """ - Construct the payload of a version message, - return the resulting bytes of running `CreatePacket` on it - """ - payload = b'' - payload += pack('>L', 3) # protocol version. - # bitflags of the services I offer. - payload += pack( - '>q', - NODE_NETWORK - | (NODE_SSL if haveSSL(server) else 0) - | (NODE_DANDELION if state.dandelion else 0) - ) - payload += pack('>q', 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 state.dandelion else 0) - ) - # = 127.0.0.1. This will be ignored by the remote host. - # The actual remote connected IP will be used. - payload += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF' + pack( - '>L', 2130706433) - # we have a separate extPort and incoming over clearnet - # or outgoing through clearnet - extport = config.safeGetInt('bitmessagesettings', 'extport') - if ( - extport and ((server and not checkSocksIP(remoteHost)) or ( - config.get('bitmessagesettings', 'socksproxytype') - == 'none' and not server)) - ): - payload += pack('>H', extport) - elif checkSocksIP(remoteHost) and server: # incoming connection over Tor - payload += pack( - '>H', config.getint('bitmessagesettings', 'onionport')) - else: # no extport and not incoming over Tor - payload += pack( - '>H', config.getint('bitmessagesettings', 'port')) - - 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 - publicSigningKey = '\x04' + decryptedData[readPosition:readPosition + 64] - readPosition += 64 - publicEncryptionKey = '\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(publicSigningKey)): - logger.info( - 'ECDSA verify failed (within decryptAndCheckPubkeyPayload)') - return 'failed' - - logger.info( - 'ECDSA verify passed (within decryptAndCheckPubkeyPayload)') - - sha = hashlib.new('sha512') - sha.update(publicSigningKey + publicEncryptionKey) - embeddedRipe = RIPEMD160Hash(sha.digest()).digest() - - 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(publicSigningKey), hexlify(publicEncryptionKey) - ) - - t = (address, addressVersion, storedData, int(time.time()), 'yes') - sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?,?)''', *t) - return 'successful' - except varintDecodeError: - logger.info( - 'Pubkey decryption was UNsuccessful due to a malformed varint.') - 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' 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..2d1559b2 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 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): """ 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_DigestInit_ex(md_ctx, digest_alg(), None) + OpenSSL.EVP_MD_CTX_init(md_ctx) + OpenSSL.EVP_DigestInit(md_ctx, OpenSSL.EVP_ecdsa()) - 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.EVP_DigestFinal(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,126 +356,105 @@ 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): """ 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_DigestInit_ex(md_ctx, digest_alg(), None) - if OpenSSL.EVP_DigestUpdate(md_ctx, binputb, len(inputb)) == 0: + + OpenSSL.EVP_MD_CTX_init(md_ctx) + OpenSSL.EVP_DigestInit(md_ctx, OpenSSL.EVP_ecdsa()) + 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) + OpenSSL.EVP_DigestFinal(md_ctx, digest, dgst_len) ret = OpenSSL.ECDSA_verify( 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 = ctx.ciphering(data) mac = hmac_sha256(key_m, ciphertext) - return ciphertext + mac + return iv + pubkey + ciphertext + mac def decrypt(self, data, ciphername='aes-256-cbc'): """ 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): + if hmac_sha256(key_m, ciphertext) != 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..d8ade000 100644 --- a/src/pyelliptic/hash.py +++ b/src/pyelliptic/hash.py @@ -1,36 +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 - - -# For python3 -def _equals_bytes(a, b): - if len(a) != len(b): - return False - result = 0 - for x, y in zip(a, b): - result |= x ^ y - return result == 0 - - -def _equals_str(a, b): - if len(a) != len(b): - return False - result = 0 - for x, y in zip(a, b): - result |= ord(x) ^ ord(y) - return result == 0 - - -def equals(a, b): - """Compare two strings or bytearrays""" - if isinstance(a, str): - return _equals_str(a, b) - return _equals_bytes(a, b) +from pyelliptic.openssl import OpenSSL def hmac_sha256(k, m): @@ -58,7 +32,6 @@ def hmac_sha512(k, m): def pbkdf2(password, salt=None, i=10000, keylen=64): - """Key derivation function using SHA256""" if salt is None: salt = OpenSSL.rand(8) p_password = OpenSSL.malloc(password, len(password)) diff --git a/src/pyelliptic/openssl.py b/src/pyelliptic/openssl.py index deb81644..59a0d324 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 @@ -385,78 +231,47 @@ class _OpenSSL(object): self.EVP_DigestInit.restype = ctypes.c_int self._lib.EVP_DigestInit.argtypes = [ctypes.c_void_p, ctypes.c_void_p] - 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 self.EVP_DigestFinal.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] - self.EVP_DigestFinal_ex = self._lib.EVP_DigestFinal_ex - 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.restype = None 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 +287,31 @@ 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] - - try: - self.PKCS5_PBKDF2_HMAC = self._lib.PKCS5_PBKDF2_HMAC - except Exception: - # The above is not compatible with all versions of OSX. - self.PKCS5_PBKDF2_HMAC = self._lib.PKCS5_PBKDF2_HMAC_SHA1 + ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p] + self.PKCS5_PBKDF2_HMAC = self._lib.PKCS5_PBKDF2_HMAC 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 +355,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 +371,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,103 +388,48 @@ 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. - # Fixed in Bitmessage version 0.4.2 (in source code on 2013-10-13) - while self.RAND_bytes(buffer_, size) != 1: - import time - time.sleep(1) - return buffer_.raw + buffer = self.malloc(0, size) + self.RAND_bytes(buffer, size) + 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 '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'), - ]) - 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']) - elif 'win32' in sys.platform or 'win64' in sys.platform: - libdir.append('libeay32.dll') - else: - libdir.append('libcrypto.so') - libdir.append('libssl.so') - libdir.append('libcrypto.so.1.0.0') - libdir.append('libssl.so.1.0.0') - if 'linux' in sys.platform or 'darwin' in sys.platform \ - or 'bsd' in sys.platform: - libdir.append(find_library('ssl')) - elif 'win32' in sys.platform or 'win64' in sys.platform: - libdir.append(find_library('libeay32')) - for library in libdir: +try: + OpenSSL = _OpenSSL('libcrypto.so') +except: + try: + OpenSSL = _OpenSSL('libeay32.dll') + except: try: - OpenSSL = _OpenSSL(library) - return - except Exception: # nosec B110 - pass - raise Exception( - "Couldn't find and load the OpenSSL library. You must install it.") - - -loadOpenSSL() + OpenSSL = _OpenSSL('libcrypto.dylib') + except: + try: + # try homebrew installation + OpenSSL = _OpenSSL('/usr/local/opt/openssl/lib/libcrypto.dylib') + except: + try: + from os import path + lib_path = path.join(sys._MEIPASS, "libeay32.dll") + OpenSSL = _OpenSSL(lib_path) + except: + if 'linux' in sys.platform or 'darwin' in sys.platform: + try: + from ctypes.util import find_library + OpenSSL = _OpenSSL(find_library('ssl')) + except Exception, err: + sys.stderr.write('(On Linux) Couldn\'t find and load the OpenSSL library. You must install it. If you believe that you already have it installed, this exception information might be of use:\n') + from ctypes.util import find_library + OpenSSL = _OpenSSL(find_library('ssl')) + else: + raise Exception("Couldn't find and load the OpenSSL library. You must install it.") diff --git a/src/pyelliptic/tests/__init__.py b/src/pyelliptic/tests/__init__.py deleted file mode 100644 index b53ef881..00000000 --- a/src/pyelliptic/tests/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -import sys - -if getattr(sys, 'frozen', None): - from test_arithmetic import TestArithmetic - from test_blindsig import TestBlindSig - from test_ecc import TestECC - from test_openssl import TestOpenSSL - - __all__ = ["TestArithmetic", "TestBlindSig", "TestECC", "TestOpenSSL"] diff --git a/src/pyelliptic/tests/samples.py b/src/pyelliptic/tests/samples.py deleted file mode 100644 index d6d71c02..00000000 --- a/src/pyelliptic/tests/samples.py +++ /dev/null @@ -1,74 +0,0 @@ -"""Testing samples""" - -from binascii import unhexlify - -# pubkey K -sample_pubkey = unhexlify( - '0409d4e5c0ab3d25fe' - '048c64c9da1a242c' - '7f19417e9517cd26' - '6950d72c75571358' - '5c6178e97fe092fc' - '897c9a1f1720d577' - '0ae8eaad2fa8fcbd' - '08e9324a5dde1857' -) - -sample_iv = unhexlify( - 'bddb7c2829b08038' - '753084a2f3991681' -) - -# Private key r -sample_ephem_privkey = unhexlify( - '5be6facd941b76e9' - 'd3ead03029fbdb6b' - '6e0809293f7fb197' - 'd0c51f84e96b8ba4' -) -# Public key R -sample_ephem_pubkey = unhexlify( - '040293213dcf1388b6' - '1c2ae5cf80fee6ff' - 'ffc049a2f9fe7365' - 'fe3867813ca81292' - 'df94686c6afb565a' - 'c6149b153d61b3b2' - '87ee2c7f997c1423' - '8796c12b43a3865a' -) - -# First 32 bytes of H called key_e -sample_enkey = unhexlify( - '1705438282678671' - '05263d4828efff82' - 'd9d59cbf08743b69' - '6bcc5d69fa1897b4' -) - -# Last 32 bytes of H called key_m -sample_mackey = unhexlify( - 'f83f1e9cc5d6b844' - '8d39dc6a9d5f5b7f' - '460e4a78e9286ee8' - 'd91ce1660a53eacd' -) - -# No padding of input! -sample_data = b'The quick brown fox jumps over the lazy dog.' - -sample_ciphertext = unhexlify( - '64203d5b24688e25' - '47bba345fa139a5a' - '1d962220d4d48a0c' - 'f3b1572c0d95b616' - '43a6f9a0d75af7ea' - 'cc1bd957147bf723' -) - -sample_mac = unhexlify( - 'f2526d61b4851fb2' - '3409863826fd2061' - '65edc021368c7946' - '571cead69046e619' -) diff --git a/src/pyelliptic/tests/test_arithmetic.py b/src/pyelliptic/tests/test_arithmetic.py deleted file mode 100644 index 7b5c59b1..00000000 --- a/src/pyelliptic/tests/test_arithmetic.py +++ /dev/null @@ -1,84 +0,0 @@ -""" -Test the arithmetic functions -""" - -from binascii import unhexlify -import unittest - -try: - from pyelliptic import arithmetic -except ImportError: - from pybitmessage.pyelliptic import arithmetic - - -# These keys are from addresses test script -sample_pubsigningkey = ( - b'044a367f049ec16cb6b6118eb734a9962d10b8db59c890cd08f210c43ff08bdf09d' - b'16f502ca26cd0713f38988a1237f1fc8fa07b15653c996dc4013af6d15505ce') -sample_pubencryptionkey = ( - b'044597d59177fc1d89555d38915f581b5ff2286b39d022ca0283d2bdd5c36be5d3c' - b'e7b9b97792327851a562752e4b79475d1f51f5a71352482b241227f45ed36a9') -sample_privsigningkey = \ - b'93d0b61371a54b53df143b954035d612f8efa8a3ed1cf842c2186bfd8f876665' -sample_privencryptionkey = \ - b'4b0b73a54e19b059dc274ab69df095fe699f43b17397bca26fdf40f4d7400a3a' - -sample_factor = \ - 66858749573256452658262553961707680376751171096153613379801854825275240965733 -# G * sample_factor -sample_point = ( - 33567437183004486938355437500683826356288335339807546987348409590129959362313, - 94730058721143827257669456336351159718085716196507891067256111928318063085006 -) - - -class TestArithmetic(unittest.TestCase): - """Test arithmetic functions""" - def test_base10_multiply(self): - """Test arithmetic.base10_multiply""" - self.assertEqual( - sample_point, - arithmetic.base10_multiply(arithmetic.G, sample_factor)) - - def test_decode(self): - """Decode sample privsigningkey from hex to int and compare to factor""" - self.assertEqual( - arithmetic.decode(sample_privsigningkey, 16), sample_factor) - - def test_encode(self): - """Encode sample factor into hex and compare to privsigningkey""" - self.assertEqual( - arithmetic.encode(sample_factor, 16), sample_privsigningkey) - - def test_changebase(self): - """Check the results of changebase()""" - self.assertEqual( - arithmetic.changebase(sample_privsigningkey, 16, 256, minlen=32), - unhexlify(sample_privsigningkey)) - self.assertEqual( - arithmetic.changebase(sample_pubsigningkey, 16, 256, minlen=64), - unhexlify(sample_pubsigningkey)) - self.assertEqual( - 32, # padding - len(arithmetic.changebase(sample_privsigningkey[:5], 16, 256, 32))) - - def test_hex_to_point(self): - """Check that sample_pubsigningkey is sample_point encoded in hex""" - self.assertEqual( - arithmetic.hex_to_point(sample_pubsigningkey), sample_point) - - def test_point_to_hex(self): - """Check that sample_point is sample_pubsigningkey decoded from hex""" - self.assertEqual( - arithmetic.point_to_hex(sample_point), sample_pubsigningkey) - - def test_privtopub(self): - """Generate public keys and check the result""" - self.assertEqual( - arithmetic.privtopub(sample_privsigningkey), - sample_pubsigningkey - ) - self.assertEqual( - arithmetic.privtopub(sample_privencryptionkey), - sample_pubencryptionkey - ) diff --git a/src/pyelliptic/tests/test_blindsig.py b/src/pyelliptic/tests/test_blindsig.py deleted file mode 100644 index 8c4b2b9d..00000000 --- a/src/pyelliptic/tests/test_blindsig.py +++ /dev/null @@ -1,277 +0,0 @@ -""" -Test for ECC blind signatures -""" -import os -import unittest -from hashlib import sha256 - -try: - from pyelliptic import ECCBlind, ECCBlindChain, OpenSSL -except ImportError: - from pybitmessage.pyelliptic import ECCBlind, ECCBlindChain, OpenSSL - -# pylint: disable=protected-access - - -class TestBlindSig(unittest.TestCase): - """ - Test case for ECC blind signature - """ - def test_blind_sig(self): - """Test full sequence using a random certifier key and a random message""" - # See page 127 of the paper - # (1) Initialization - signer_obj = ECCBlind() - point_r = signer_obj.signer_init() - self.assertEqual(len(signer_obj.pubkey()), 35) - - # (2) Request - requester_obj = ECCBlind(pubkey=signer_obj.pubkey()) - # only 64 byte messages are planned to be used in Bitmessage - msg = os.urandom(64) - msg_blinded = requester_obj.create_signing_request(point_r, msg) - self.assertEqual(len(msg_blinded), 32) - - # check - self.assertNotEqual(sha256(msg).digest(), msg_blinded) - - # (3) Signature Generation - signature_blinded = signer_obj.blind_sign(msg_blinded) - assert isinstance(signature_blinded, bytes) - self.assertEqual(len(signature_blinded), 32) - - # (4) Extraction - signature = requester_obj.unblind(signature_blinded) - assert isinstance(signature, bytes) - self.assertEqual(len(signature), 65) - - self.assertNotEqual(signature, signature_blinded) - - # (5) Verification - verifier_obj = ECCBlind(pubkey=signer_obj.pubkey()) - self.assertTrue(verifier_obj.verify(msg, signature)) - - def test_is_odd(self): - """Test our implementation of BN_is_odd""" - for _ in range(1024): - obj = ECCBlind() - x = OpenSSL.BN_new() - y = OpenSSL.BN_new() - OpenSSL.EC_POINT_get_affine_coordinates( - obj.group, obj.Q, x, y, None) - self.assertEqual(OpenSSL.BN_is_odd(y), - OpenSSL.BN_is_odd_compatible(y)) - - def test_serialize_ec_point(self): - """Test EC point serialization/deserialization""" - for _ in range(1024): - try: - obj = ECCBlind() - obj2 = ECCBlind() - randompoint = obj.Q - serialized = obj._ec_point_serialize(randompoint) - secondpoint = obj2._ec_point_deserialize(serialized) - x0 = OpenSSL.BN_new() - y0 = OpenSSL.BN_new() - OpenSSL.EC_POINT_get_affine_coordinates(obj.group, - randompoint, x0, - y0, obj.ctx) - x1 = OpenSSL.BN_new() - y1 = OpenSSL.BN_new() - OpenSSL.EC_POINT_get_affine_coordinates(obj2.group, - secondpoint, x1, - y1, obj2.ctx) - - self.assertEqual(OpenSSL.BN_cmp(y0, y1), 0) - self.assertEqual(OpenSSL.BN_cmp(x0, x1), 0) - self.assertEqual(OpenSSL.EC_POINT_cmp(obj.group, randompoint, - secondpoint, None), 0) - finally: - OpenSSL.BN_free(x0) - OpenSSL.BN_free(x1) - OpenSSL.BN_free(y0) - OpenSSL.BN_free(y1) - del obj - del obj2 - - def test_serialize_bn(self): - """Test Bignum serialization/deserialization""" - for _ in range(1024): - obj = ECCBlind() - obj2 = ECCBlind() - randomnum = obj.d - serialized = obj._bn_serialize(randomnum) - secondnum = obj2._bn_deserialize(serialized) - self.assertEqual(OpenSSL.BN_cmp(randomnum, secondnum), 0) - - def test_blind_sig_many(self): - """Test a lot of blind signatures""" - for _ in range(1024): - self.test_blind_sig() - - def test_blind_sig_value(self): - """Test blind signature value checking""" - signer_obj = ECCBlind(value=5) - point_r = signer_obj.signer_init() - requester_obj = ECCBlind(pubkey=signer_obj.pubkey()) - msg = os.urandom(64) - msg_blinded = requester_obj.create_signing_request(point_r, msg) - signature_blinded = signer_obj.blind_sign(msg_blinded) - signature = requester_obj.unblind(signature_blinded) - verifier_obj = ECCBlind(pubkey=signer_obj.pubkey()) - self.assertFalse(verifier_obj.verify(msg, signature, value=8)) - - def test_blind_sig_expiration(self): - """Test blind signature expiration checking""" - signer_obj = ECCBlind(year=2020, month=1) - point_r = signer_obj.signer_init() - requester_obj = ECCBlind(pubkey=signer_obj.pubkey()) - msg = os.urandom(64) - msg_blinded = requester_obj.create_signing_request(point_r, msg) - signature_blinded = signer_obj.blind_sign(msg_blinded) - signature = requester_obj.unblind(signature_blinded) - verifier_obj = ECCBlind(pubkey=signer_obj.pubkey()) - self.assertFalse(verifier_obj.verify(msg, signature)) - - def test_blind_sig_chain(self): # pylint: disable=too-many-locals - """Test blind signature chain using a random certifier key and a random message""" - - test_levels = 4 - msg = os.urandom(1024) - - ca = ECCBlind() - signer_obj = ca - - output = bytearray() - - for level in range(test_levels): - if not level: - output.extend(ca.pubkey()) - requester_obj = ECCBlind(pubkey=signer_obj.pubkey()) - child_obj = ECCBlind() - point_r = signer_obj.signer_init() - pubkey = child_obj.pubkey() - - if level == test_levels - 1: - msg_blinded = requester_obj.create_signing_request(point_r, - msg) - else: - msg_blinded = requester_obj.create_signing_request(point_r, - pubkey) - signature_blinded = signer_obj.blind_sign(msg_blinded) - signature = requester_obj.unblind(signature_blinded) - if level != test_levels - 1: - output.extend(pubkey) - output.extend(signature) - signer_obj = child_obj - verifychain = ECCBlindChain(ca=ca.pubkey(), chain=bytes(output)) - self.assertTrue(verifychain.verify(msg=msg, value=1)) - - def test_blind_sig_chain_wrong_ca(self): # pylint: disable=too-many-locals - """Test blind signature chain with an unlisted ca""" - - test_levels = 4 - msg = os.urandom(1024) - - ca = ECCBlind() - fake_ca = ECCBlind() - signer_obj = fake_ca - - output = bytearray() - - for level in range(test_levels): - requester_obj = ECCBlind(pubkey=signer_obj.pubkey()) - child_obj = ECCBlind() - if not level: - # unlisted CA, but a syntactically valid pubkey - output.extend(fake_ca.pubkey()) - point_r = signer_obj.signer_init() - pubkey = child_obj.pubkey() - - if level == test_levels - 1: - msg_blinded = requester_obj.create_signing_request(point_r, - msg) - else: - msg_blinded = requester_obj.create_signing_request(point_r, - pubkey) - signature_blinded = signer_obj.blind_sign(msg_blinded) - signature = requester_obj.unblind(signature_blinded) - if level != test_levels - 1: - output.extend(pubkey) - output.extend(signature) - signer_obj = child_obj - verifychain = ECCBlindChain(ca=ca.pubkey(), chain=bytes(output)) - self.assertFalse(verifychain.verify(msg, 1)) - - def test_blind_sig_chain_wrong_msg(self): # pylint: disable=too-many-locals - """Test blind signature chain with a fake message""" - - test_levels = 4 - msg = os.urandom(1024) - fake_msg = os.urandom(1024) - - ca = ECCBlind() - signer_obj = ca - - output = bytearray() - - for level in range(test_levels): - if not level: - output.extend(ca.pubkey()) - requester_obj = ECCBlind(pubkey=signer_obj.pubkey()) - child_obj = ECCBlind() - point_r = signer_obj.signer_init() - pubkey = child_obj.pubkey() - - if level == test_levels - 1: - msg_blinded = requester_obj.create_signing_request(point_r, - msg) - else: - msg_blinded = requester_obj.create_signing_request(point_r, - pubkey) - signature_blinded = signer_obj.blind_sign(msg_blinded) - signature = requester_obj.unblind(signature_blinded) - if level != test_levels - 1: - output.extend(pubkey) - output.extend(signature) - signer_obj = child_obj - verifychain = ECCBlindChain(ca=ca.pubkey(), chain=bytes(output)) - self.assertFalse(verifychain.verify(fake_msg, 1)) - - def test_blind_sig_chain_wrong_intermediary(self): # pylint: disable=too-many-locals - """Test blind signature chain using a fake intermediary pubkey""" - - test_levels = 4 - msg = os.urandom(1024) - wrong_level = 2 - - ca = ECCBlind() - signer_obj = ca - fake_intermediary = ECCBlind() - - output = bytearray() - - for level in range(test_levels): - if not level: - output.extend(ca.pubkey()) - requester_obj = ECCBlind(pubkey=signer_obj.pubkey()) - child_obj = ECCBlind() - point_r = signer_obj.signer_init() - pubkey = child_obj.pubkey() - - if level == test_levels - 1: - msg_blinded = requester_obj.create_signing_request(point_r, - msg) - else: - msg_blinded = requester_obj.create_signing_request(point_r, - pubkey) - signature_blinded = signer_obj.blind_sign(msg_blinded) - signature = requester_obj.unblind(signature_blinded) - if level == wrong_level: - output.extend(fake_intermediary.pubkey()) - elif level != test_levels - 1: - output.extend(pubkey) - output.extend(signature) - signer_obj = child_obj - verifychain = ECCBlindChain(ca=ca.pubkey(), chain=bytes(output)) - self.assertFalse(verifychain.verify(msg, 1)) diff --git a/src/pyelliptic/tests/test_ecc.py b/src/pyelliptic/tests/test_ecc.py deleted file mode 100644 index e87d1c21..00000000 --- a/src/pyelliptic/tests/test_ecc.py +++ /dev/null @@ -1,102 +0,0 @@ -"""Tests for ECC object""" - -import os -import unittest -from hashlib import sha512 - -try: - import pyelliptic -except ImportError: - from pybitmessage import pyelliptic - -from .samples import ( - sample_pubkey, sample_iv, sample_ephem_privkey, sample_ephem_pubkey, - sample_enkey, sample_mackey, sample_data, sample_ciphertext, sample_mac) - - -sample_pubkey_x = sample_ephem_pubkey[1:-32] -sample_pubkey_y = sample_ephem_pubkey[-32:] -sample_pubkey_bin = ( - b'\x02\xca\x00\x20' + sample_pubkey_x + b'\x00\x20' + sample_pubkey_y) -sample_privkey_bin = b'\x02\xca\x00\x20' + sample_ephem_privkey - - -class TestECC(unittest.TestCase): - """The test case for ECC""" - - def test_random_keys(self): - """A dummy test for random keys in ECC object""" - eccobj = pyelliptic.ECC(curve='secp256k1') - self.assertTrue(len(eccobj.privkey) <= 32) - pubkey = eccobj.get_pubkey() - self.assertEqual(pubkey[:4], b'\x02\xca\x00\x20') - - def test_short_keys(self): - """Check formatting of the keys with leading zeroes""" - # pylint: disable=protected-access - def sample_key(_): - """Fake ECC keypair""" - return os.urandom(32), os.urandom(31), os.urandom(30) - - try: - gen_orig = pyelliptic.ECC._generate - pyelliptic.ECC._generate = sample_key - eccobj = pyelliptic.ECC(curve='secp256k1') - pubkey = eccobj.get_pubkey() - self.assertEqual(pubkey[:4], b'\x02\xca\x00\x20') - self.assertEqual(pubkey[36:38], b'\x00\x20') - self.assertEqual(len(pubkey[38:]), 32) - finally: - pyelliptic.ECC._generate = gen_orig - - def test_decode_keys(self): - """Check keys decoding""" - # pylint: disable=protected-access - curve_secp256k1 = pyelliptic.OpenSSL.get_curve('secp256k1') - curve, raw_privkey, _ = pyelliptic.ECC._decode_privkey( - sample_privkey_bin) - self.assertEqual(curve, curve_secp256k1) - self.assertEqual( - pyelliptic.OpenSSL.get_curve_by_id(curve), 'secp256k1') - self.assertEqual(sample_ephem_privkey, raw_privkey) - - curve, pubkey_x, pubkey_y, _ = pyelliptic.ECC._decode_pubkey( - sample_pubkey_bin) - self.assertEqual(curve, curve_secp256k1) - self.assertEqual(sample_pubkey_x, pubkey_x) - self.assertEqual(sample_pubkey_y, pubkey_y) - - def test_encode_keys(self): - """Check keys encoding""" - cryptor = pyelliptic.ECC( - pubkey_x=sample_pubkey_x, - pubkey_y=sample_pubkey_y, - raw_privkey=sample_ephem_privkey, curve='secp256k1') - self.assertEqual(cryptor.get_privkey(), sample_privkey_bin) - self.assertEqual(cryptor.get_pubkey(), sample_pubkey_bin) - - def test_encryption_parts(self): - """Check results of the encryption steps against samples in the Spec""" - ephem = pyelliptic.ECC( - pubkey_x=sample_pubkey_x, - pubkey_y=sample_pubkey_y, - raw_privkey=sample_ephem_privkey, curve='secp256k1') - key = sha512(ephem.raw_get_ecdh_key( - sample_pubkey[1:-32], sample_pubkey[-32:])).digest() - self.assertEqual(sample_enkey, key[:32]) - self.assertEqual(sample_mackey, key[32:]) - - ctx = pyelliptic.Cipher(sample_enkey, sample_iv, 1) - self.assertEqual(ctx.ciphering(sample_data), sample_ciphertext) - self.assertEqual( - sample_mac, - pyelliptic.hash.hmac_sha256( - sample_mackey, - sample_iv + sample_pubkey_bin + sample_ciphertext)) - - def test_decryption(self): - """Check decription of a message by random cryptor""" - random_recipient = pyelliptic.ECC(curve='secp256k1') - payload = pyelliptic.ECC.encrypt( - sample_data, random_recipient.get_pubkey()) - self.assertEqual(random_recipient.decrypt(payload), sample_data) diff --git a/src/pyelliptic/tests/test_openssl.py b/src/pyelliptic/tests/test_openssl.py deleted file mode 100644 index cb789277..00000000 --- a/src/pyelliptic/tests/test_openssl.py +++ /dev/null @@ -1,57 +0,0 @@ -""" -Test if OpenSSL is working correctly -""" -import unittest - -try: - from pyelliptic.openssl import OpenSSL -except ImportError: - from pybitmessage.pyelliptic import OpenSSL - -try: - OpenSSL.BN_bn2binpad - have_pad = True -except AttributeError: - have_pad = None - - -class TestOpenSSL(unittest.TestCase): - """ - Test cases for OpenSSL - """ - def test_is_odd(self): - """Test BN_is_odd implementation""" - ctx = OpenSSL.BN_CTX_new() - a = OpenSSL.BN_new() - group = OpenSSL.EC_GROUP_new_by_curve_name( - OpenSSL.get_curve("secp256k1")) - OpenSSL.EC_GROUP_get_order(group, a, ctx) - - bad = 0 - for _ in range(1024): - OpenSSL.BN_rand(a, OpenSSL.BN_num_bits(a), 0, 0) - if not OpenSSL.BN_is_odd(a) == OpenSSL.BN_is_odd_compatible(a): - bad += 1 - self.assertEqual(bad, 0) - - @unittest.skipUnless(have_pad, 'Skipping OpenSSL pad test') - def test_padding(self): - """Test an alternative implementation of bn2binpad""" - - ctx = OpenSSL.BN_CTX_new() - a = OpenSSL.BN_new() - n = OpenSSL.BN_new() - group = OpenSSL.EC_GROUP_new_by_curve_name( - OpenSSL.get_curve("secp256k1")) - OpenSSL.EC_GROUP_get_order(group, n, ctx) - - bad = 0 - for _ in range(1024): - OpenSSL.BN_rand(a, OpenSSL.BN_num_bits(n), 0, 0) - b = OpenSSL.malloc(0, OpenSSL.BN_num_bytes(n)) - c = OpenSSL.malloc(0, OpenSSL.BN_num_bytes(a)) - OpenSSL.BN_bn2binpad(a, b, OpenSSL.BN_num_bytes(n)) - OpenSSL.BN_bn2bin(a, c) - if b.raw != c.raw.rjust(OpenSSL.BN_num_bytes(n), b'\x00'): - bad += 1 - self.assertEqual(bad, 0) diff --git a/src/qidenticon.py b/src/qidenticon.py deleted file mode 100644 index 13be3578..00000000 --- a/src/qidenticon.py +++ /dev/null @@ -1,276 +0,0 @@ -### -# 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: -# -# 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 ------ - ->>> import qidenticon ->>> qidenticon.render_identicon(code, size) - -Returns an instance of :class:`QPixmap` 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 - - -class IdenticonRendererBase(object): - """Encapsulate methods around rendering identicons""" - - PATH_SET = [] - - def __init__(self, code): - """ - :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` - """ - - # decode the code - 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)) - - # fill background - 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) - - # side patch - kwds['foreColor'] = foreColor - kwds['patch_type'] = side[0] - for i in range(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): - 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 - """ - :param size: patch size - """ - path = self.PATH_SET[patch_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]) - - 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) - if penwidth > 0: - pen_color = QtGui.QColor(255, 255, 255) - pen = QtGui.QPen(pen_color, QtCore.Qt.SolidPattern) - pen.setWidth(penwidth) - - painter = QtGui.QPainter() - painter.begin(image) - painter.setPen(nopen) - - 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) - polygon = poly_rect.subtracted(polygon) - painter.setBrush(foreBrush) - if penwidth > 0: - # draw the borders - painter.setPen(pen) - painter.drawPolygon(polygon, QtCore.Qt.WindingFill) - # draw the fill - painter.setPen(nopen) - painter.drawPolygon(polygon, QtCore.Qt.WindingFill) - - painter.end() - - return image - - def decode(self, code, twoColor): - """virtual functions""" - raise NotImplementedError - - -class DonRenderer(IdenticonRendererBase): - """ - Don Park's implementation of identicon, see: - https://blog.docuverse.com/2007/01/18/identicon-updated-and-source-released - """ - - PATH_SET = [ - # [0] full square: - [(0, 0), (4, 0), (4, 4), (0, 4)], - # [1] right-angled triangle pointing top-left: - [(0, 0), (4, 0), (0, 4)], - # [2] upwardy triangle: - [(2, 0), (4, 4), (0, 4)], - # [3] left half of square, standing rectangle: - [(0, 0), (2, 0), (2, 4), (0, 4)], - # [4] square standing on diagonale: - [(2, 0), (4, 2), (2, 4), (0, 2)], - # [5] kite pointing topleft: - [(0, 0), (4, 2), (4, 4), (2, 4)], - # [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: - [(0, 0), (4, 2), (2, 4)], - # [8] small centered square: - [(1, 1), (3, 1), (3, 3), (1, 3)], - # [9] two small triangles: - [(2, 0), (4, 0), (0, 4), (0, 2), (2, 2)], - # [10] small topleft square: - [(0, 0), (2, 0), (2, 2), (0, 2)], - # [11] downpointing right-angled triangle on bottom: - [(0, 2), (4, 2), (2, 4)], - # [12] uppointing right-angled triangle on bottom: - [(2, 2), (4, 4), (0, 4)], - # [13] small rightbottom pointing right-angled triangle on topleft: - [(2, 0), (2, 2), (0, 2)], - # [14] small lefttop pointing right-angled triangle on topleft: - [(0, 0), (2, 0), (0, 2)], - # [15] empty: - []] - # 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] - 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 - - 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 = QtGui.QColor(*secondColor) - else: - secondColor = foreColor - - return (middleType, middleInvert, 0),\ - (cornerType, cornerInvert, cornerTurn),\ - (sideType, sideInvert, sideTurn),\ - foreColor, secondColor, swap_cross - - -def render_identicon( - code, size, twoColor=False, opacity=255, penwidth=0, renderer=None): - """Render an image""" - if not renderer: - renderer = DonRenderer - return renderer(code).render(size, twoColor, opacity, penwidth) diff --git a/src/queues.py b/src/queues.py deleted file mode 100644 index 4a9b98d2..00000000 --- a/src/queues.py +++ /dev/null @@ -1,55 +0,0 @@ -"""Most of the queues used by bitmessage threads are defined here.""" - -import threading -import time - -from six.moves import queue - -try: - from multiqueue import MultiQueue -except ImportError: - from .multiqueue import MultiQueue - - -class ObjectProcessorQueue(queue.Queue): - """Special queue class using lock for `.threads.objectProcessor`""" - - maxSize = 32000000 - - def __init__(self): - queue.Queue.__init__(self) - self.sizeLock = threading.Lock() - #: in Bytes. We maintain this to prevent nodes from flooding us - #: with objects which take up too much memory. If this gets - #: too big we'll sleep before asking for further objects. - self.curSize = 0 - - def put(self, item, block=True, timeout=None): - while self.curSize >= self.maxSize: - time.sleep(1) - with self.sizeLock: - self.curSize += len(item[1]) - queue.Queue.put(self, item, block, timeout) - - def get(self, block=True, timeout=None): - item = queue.Queue.get(self, block, timeout) - with self.sizeLock: - self.curSize -= len(item[1]) - return item - - -workerQueue = queue.Queue() -UISignalQueue = queue.Queue() -addressGeneratorQueue = queue.Queue() -#: `.network.ReceiveQueueThread` instances dump objects they hear -#: on the network into this queue to be processed. -objectProcessorQueue = ObjectProcessorQueue() -invQueue = MultiQueue() -addrQueue = MultiQueue() -portCheckerQueue = queue.Queue() -receiveDataQueue = queue.Queue() -#: The address generator thread uses this queue to get information back -#: to the API thread. -apiAddressGeneratorReturnQueue = queue.Queue() -#: for exceptions -excQueue = queue.Queue() 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/regenerateaddresses.py b/src/regenerateaddresses.py new file mode 100644 index 00000000..dfcd9135 --- /dev/null +++ b/src/regenerateaddresses.py @@ -0,0 +1,115 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'regenerateaddresses.ui' +# +# Created: Tue Apr 30 12:21:16 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_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(False) + 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.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(QtGui.QApplication.translate("regenerateAddressesDialog", "Regenerate Existing Addresses", None, QtGui.QApplication.UnicodeUTF8)) + self.groupBox.setTitle(QtGui.QApplication.translate("regenerateAddressesDialog", "Regenerate existing addresses", None, QtGui.QApplication.UnicodeUTF8)) + self.label_6.setText(QtGui.QApplication.translate("regenerateAddressesDialog", "Passphrase", None, QtGui.QApplication.UnicodeUTF8)) + self.label_11.setText(QtGui.QApplication.translate("regenerateAddressesDialog", "Number of addresses to make based on your passphrase:", None, QtGui.QApplication.UnicodeUTF8)) + self.label_2.setText(QtGui.QApplication.translate("regenerateAddressesDialog", "Address version Number:", None, QtGui.QApplication.UnicodeUTF8)) + self.lineEditAddressVersionNumber.setText(QtGui.QApplication.translate("regenerateAddressesDialog", "3", None, QtGui.QApplication.UnicodeUTF8)) + self.label_3.setText(QtGui.QApplication.translate("regenerateAddressesDialog", "Stream number:", None, QtGui.QApplication.UnicodeUTF8)) + self.lineEditStreamNumber.setText(QtGui.QApplication.translate("regenerateAddressesDialog", "1", None, QtGui.QApplication.UnicodeUTF8)) + self.checkBoxEighteenByteRipe.setText(QtGui.QApplication.translate("regenerateAddressesDialog", "Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter", None, QtGui.QApplication.UnicodeUTF8)) + self.label_4.setText(QtGui.QApplication.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, QtGui.QApplication.UnicodeUTF8)) + self.label.setText(QtGui.QApplication.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, QtGui.QApplication.UnicodeUTF8)) + diff --git a/src/bitmessageqt/regenerateaddresses.ui b/src/regenerateaddresses.ui similarity index 98% rename from src/bitmessageqt/regenerateaddresses.ui rename to src/regenerateaddresses.ui index e58a20e9..89cdf901 100644 --- a/src/bitmessageqt/regenerateaddresses.ui +++ b/src/regenerateaddresses.ui @@ -86,14 +86,14 @@ - Address version number: + Address version Number: - true + false @@ -108,7 +108,7 @@ - + 3 diff --git a/src/settings.py b/src/settings.py new file mode 100644 index 00000000..0cd7b07e --- /dev/null +++ b/src/settings.py @@ -0,0 +1,225 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'settings.ui' +# +# Created: Sat May 25 13:20:18 2013 +# by: PyQt4 UI code generator 4.9.5 +# +# 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_settingsDialog(object): + def setupUi(self, settingsDialog): + settingsDialog.setObjectName(_fromUtf8("settingsDialog")) + settingsDialog.resize(445, 343) + 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.gridLayout_5 = QtGui.QGridLayout(self.tabUserInterface) + self.gridLayout_5.setObjectName(_fromUtf8("gridLayout_5")) + self.checkBoxStartOnLogon = QtGui.QCheckBox(self.tabUserInterface) + self.checkBoxStartOnLogon.setObjectName(_fromUtf8("checkBoxStartOnLogon")) + self.gridLayout_5.addWidget(self.checkBoxStartOnLogon, 0, 0, 1, 1) + self.checkBoxStartInTray = QtGui.QCheckBox(self.tabUserInterface) + self.checkBoxStartInTray.setObjectName(_fromUtf8("checkBoxStartInTray")) + self.gridLayout_5.addWidget(self.checkBoxStartInTray, 1, 0, 1, 1) + self.checkBoxMinimizeToTray = QtGui.QCheckBox(self.tabUserInterface) + self.checkBoxMinimizeToTray.setChecked(True) + self.checkBoxMinimizeToTray.setObjectName(_fromUtf8("checkBoxMinimizeToTray")) + self.gridLayout_5.addWidget(self.checkBoxMinimizeToTray, 2, 0, 1, 1) + self.checkBoxShowTrayNotifications = QtGui.QCheckBox(self.tabUserInterface) + self.checkBoxShowTrayNotifications.setObjectName(_fromUtf8("checkBoxShowTrayNotifications")) + self.gridLayout_5.addWidget(self.checkBoxShowTrayNotifications, 3, 0, 1, 1) + self.checkBoxPortableMode = QtGui.QCheckBox(self.tabUserInterface) + self.checkBoxPortableMode.setObjectName(_fromUtf8("checkBoxPortableMode")) + self.gridLayout_5.addWidget(self.checkBoxPortableMode, 4, 0, 1, 1) + self.label_7 = QtGui.QLabel(self.tabUserInterface) + self.label_7.setWordWrap(True) + self.label_7.setObjectName(_fromUtf8("label_7")) + self.gridLayout_5.addWidget(self.label_7, 5, 0, 1, 1) + self.labelSettingsNote = QtGui.QLabel(self.tabUserInterface) + self.labelSettingsNote.setText(_fromUtf8("")) + self.labelSettingsNote.setWordWrap(True) + self.labelSettingsNote.setObjectName(_fromUtf8("labelSettingsNote")) + self.gridLayout_5.addWidget(self.labelSettingsNote, 6, 0, 1, 1) + spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) + self.gridLayout_5.addItem(spacerItem, 7, 0, 1, 1) + 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.groupBox = QtGui.QGroupBox(self.tabNetworkSettings) + self.groupBox.setObjectName(_fromUtf8("groupBox")) + self.gridLayout_3 = QtGui.QGridLayout(self.groupBox) + self.gridLayout_3.setObjectName(_fromUtf8("gridLayout_3")) + spacerItem1 = QtGui.QSpacerItem(125, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_3.addItem(spacerItem1, 0, 0, 1, 1) + self.label = QtGui.QLabel(self.groupBox) + self.label.setObjectName(_fromUtf8("label")) + self.gridLayout_3.addWidget(self.label, 0, 1, 1, 1) + self.lineEditTCPPort = QtGui.QLineEdit(self.groupBox) + self.lineEditTCPPort.setObjectName(_fromUtf8("lineEditTCPPort")) + self.gridLayout_3.addWidget(self.lineEditTCPPort, 0, 2, 1, 1) + self.gridLayout_4.addWidget(self.groupBox, 0, 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.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.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.gridLayout_4.addWidget(self.groupBox_2, 1, 0, 1, 1) + spacerItem2 = QtGui.QSpacerItem(20, 70, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) + self.gridLayout_4.addItem(spacerItem2, 2, 0, 1, 1) + self.tabWidgetSettings.addTab(self.tabNetworkSettings, _fromUtf8("")) + self.tab = QtGui.QWidget() + self.tab.setObjectName(_fromUtf8("tab")) + self.gridLayout_6 = QtGui.QGridLayout(self.tab) + self.gridLayout_6.setObjectName(_fromUtf8("gridLayout_6")) + self.label_8 = QtGui.QLabel(self.tab) + 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_9 = QtGui.QLabel(self.tab) + 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.lineEditTotalDifficulty = QtGui.QLineEdit(self.tab) + 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) + self.label_11 = QtGui.QLabel(self.tab) + 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.lineEditSmallMessageDifficulty = QtGui.QLineEdit(self.tab) + 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.label_12 = QtGui.QLabel(self.tab) + self.label_12.setWordWrap(True) + self.label_12.setObjectName(_fromUtf8("label_12")) + self.gridLayout_6.addWidget(self.label_12, 4, 0, 1, 3) + self.label_10 = QtGui.QLabel(self.tab) + self.label_10.setWordWrap(True) + self.label_10.setObjectName(_fromUtf8("label_10")) + self.gridLayout_6.addWidget(self.label_10, 2, 0, 1, 3) + self.tabWidgetSettings.addTab(self.tab, _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.checkBoxShowTrayNotifications) + settingsDialog.setTabOrder(self.checkBoxShowTrayNotifications, 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.buttonBox) + + def retranslateUi(self, settingsDialog): + settingsDialog.setWindowTitle(QtGui.QApplication.translate("settingsDialog", "Settings", None, QtGui.QApplication.UnicodeUTF8)) + self.checkBoxStartOnLogon.setText(QtGui.QApplication.translate("settingsDialog", "Start Bitmessage on user login", None, QtGui.QApplication.UnicodeUTF8)) + self.checkBoxStartInTray.setText(QtGui.QApplication.translate("settingsDialog", "Start Bitmessage in the tray (don\'t show main window)", None, QtGui.QApplication.UnicodeUTF8)) + self.checkBoxMinimizeToTray.setText(QtGui.QApplication.translate("settingsDialog", "Minimize to tray", None, QtGui.QApplication.UnicodeUTF8)) + self.checkBoxShowTrayNotifications.setText(QtGui.QApplication.translate("settingsDialog", "Show notification when message received", None, QtGui.QApplication.UnicodeUTF8)) + self.checkBoxPortableMode.setText(QtGui.QApplication.translate("settingsDialog", "Run in Portable Mode", None, QtGui.QApplication.UnicodeUTF8)) + self.label_7.setText(QtGui.QApplication.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, QtGui.QApplication.UnicodeUTF8)) + self.tabWidgetSettings.setTabText(self.tabWidgetSettings.indexOf(self.tabUserInterface), QtGui.QApplication.translate("settingsDialog", "User Interface", None, QtGui.QApplication.UnicodeUTF8)) + self.groupBox.setTitle(QtGui.QApplication.translate("settingsDialog", "Listening port", None, QtGui.QApplication.UnicodeUTF8)) + self.label.setText(QtGui.QApplication.translate("settingsDialog", "Listen for connections on port:", None, QtGui.QApplication.UnicodeUTF8)) + self.groupBox_2.setTitle(QtGui.QApplication.translate("settingsDialog", "Proxy server / Tor", None, QtGui.QApplication.UnicodeUTF8)) + self.label_2.setText(QtGui.QApplication.translate("settingsDialog", "Type:", None, QtGui.QApplication.UnicodeUTF8)) + self.comboBoxProxyType.setItemText(0, QtGui.QApplication.translate("settingsDialog", "none", None, QtGui.QApplication.UnicodeUTF8)) + self.comboBoxProxyType.setItemText(1, QtGui.QApplication.translate("settingsDialog", "SOCKS4a", None, QtGui.QApplication.UnicodeUTF8)) + self.comboBoxProxyType.setItemText(2, QtGui.QApplication.translate("settingsDialog", "SOCKS5", None, QtGui.QApplication.UnicodeUTF8)) + self.label_3.setText(QtGui.QApplication.translate("settingsDialog", "Server hostname:", None, QtGui.QApplication.UnicodeUTF8)) + self.label_4.setText(QtGui.QApplication.translate("settingsDialog", "Port:", None, QtGui.QApplication.UnicodeUTF8)) + self.checkBoxAuthentication.setText(QtGui.QApplication.translate("settingsDialog", "Authentication", None, QtGui.QApplication.UnicodeUTF8)) + self.label_5.setText(QtGui.QApplication.translate("settingsDialog", "Username:", None, QtGui.QApplication.UnicodeUTF8)) + self.label_6.setText(QtGui.QApplication.translate("settingsDialog", "Pass:", None, QtGui.QApplication.UnicodeUTF8)) + self.tabWidgetSettings.setTabText(self.tabWidgetSettings.indexOf(self.tabNetworkSettings), QtGui.QApplication.translate("settingsDialog", "Network Settings", None, QtGui.QApplication.UnicodeUTF8)) + self.label_8.setText(QtGui.QApplication.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, QtGui.QApplication.UnicodeUTF8)) + self.label_9.setText(QtGui.QApplication.translate("settingsDialog", "Total difficulty:", None, QtGui.QApplication.UnicodeUTF8)) + self.label_11.setText(QtGui.QApplication.translate("settingsDialog", "Small message difficulty:", None, QtGui.QApplication.UnicodeUTF8)) + self.label_12.setText(QtGui.QApplication.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, QtGui.QApplication.UnicodeUTF8)) + self.label_10.setText(QtGui.QApplication.translate("settingsDialog", "The \'Total difficulty\' affects the absolute amount of work the sender must complete. Doubling this value doubles the amount of work.", None, QtGui.QApplication.UnicodeUTF8)) + self.tabWidgetSettings.setTabText(self.tabWidgetSettings.indexOf(self.tab), QtGui.QApplication.translate("settingsDialog", "Demanded difficulty", None, QtGui.QApplication.UnicodeUTF8)) + diff --git a/src/settings.ui b/src/settings.ui new file mode 100644 index 00000000..971480bd --- /dev/null +++ b/src/settings.ui @@ -0,0 +1,462 @@ + + + settingsDialog + + + + 0 + 0 + 445 + 343 + + + + Settings + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + 0 + + + + true + + + User Interface + + + + + + Start Bitmessage on user login + + + + + + + Start Bitmessage in the tray (don't show main window) + + + + + + + Minimize to tray + + + true + + + + + + + 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. + + + true + + + + + + + + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Network Settings + + + + + + Listening port + + + + + + Qt::Horizontal + + + + 125 + 20 + + + + + + + + Listen for connections on port: + + + + + + + + + + + + + Proxy server / Tor + + + + + + Type: + + + + + + + + none + + + + + SOCKS4a + + + + + SOCKS5 + + + + + + + + Server hostname: + + + + + + + + + + Port: + + + + + + + + + + Authentication + + + + + + + Username: + + + + + + + false + + + + + + + Pass: + + + + + + + false + + + Qt::ImhHiddenText|Qt::ImhNoAutoUppercase|Qt::ImhNoPredictiveText + + + QLineEdit::Password + + + + + + + + + + Qt::Vertical + + + + 20 + 70 + + + + + + + + + Demanded 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. + + + true + + + + + + + Qt::Horizontal + + + + 203 + 20 + + + + + + + + Total difficulty: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 70 + 16777215 + + + + + + + + Qt::Horizontal + + + + 203 + 20 + + + + + + + + Small message difficulty: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 70 + 16777215 + + + + + + + + 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. + + + true + + + + + + + The 'Total difficulty' affects the absolute amount of work the sender must complete. Doubling this value doubles the amount of work. + + + true + + + + + + + + + + + tabWidgetSettings + checkBoxStartOnLogon + checkBoxStartInTray + checkBoxMinimizeToTray + checkBoxShowTrayNotifications + lineEditTCPPort + comboBoxProxyType + lineEditSocksHostname + lineEditSocksPort + checkBoxAuthentication + lineEditSocksUsername + lineEditSocksPassword + buttonBox + + + + + buttonBox + accepted() + settingsDialog + accept() + + + 257 + 330 + + + 157 + 274 + + + + + buttonBox + rejected() + settingsDialog + reject() + + + 325 + 330 + + + 286 + 274 + + + + + checkBoxAuthentication + toggled(bool) + lineEditSocksUsername + setEnabled(bool) + + + 125 + 190 + + + 233 + 189 + + + + + checkBoxAuthentication + toggled(bool) + lineEditSocksPassword + setEnabled(bool) + + + 79 + 190 + + + 370 + 192 + + + + + diff --git a/src/shared.py b/src/shared.py index cb7968f6..9ca07e69 100644 --- a/src/shared.py +++ b/src/shared.py @@ -1,254 +1,206 @@ -""" -Some shared functions +softwareVersion = '0.3.1' -.. deprecated:: 0.6.3 - Should be moved to different places and this file removed, - but it needs refactoring. -""" -from __future__ import division - -# Libraries. -import hashlib -import os -import stat -import subprocess # nosec B404 +import threading import sys -from binascii import hexlify - -# Project imports. +from addresses import * import highlevelcrypto -import state -from addresses import decodeAddress, encodeVarint -from bmconfigparser import config -from debug import logger -from helper_sql import sqlQuery - -from pyelliptic import arithmetic - +import Queue +import pickle +import os +import signal 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. broadcastSendersForWhichImWatching = {} +workerQueue = Queue.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() +UISignalQueue = Queue.Queue() +addressGeneratorQueue = Queue.Queue() +knownNodesLock = threading.Lock() +knownNodes = {} +sendDataQueues = [] #each sendData thread puts its queue in this list. +inventory = {} #of objects (like msg payloads and pubkey payloads) Does not include protocol headers (the first 24 bytes of each packet). +inventoryLock = threading.Lock() #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. +#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 = 320 #The amount of work that should be performed (and demanded) per byte of the payload. Double this number to double the work. +networkDefaultPayloadLengthExtraBytes = 14000 #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. + +def lookupAppdataFolder(): + APPNAME = "PyBitmessage" + from os import path, environ + if sys.platform == 'darwin': + 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 BitMessage Github.' + sys.exit() + + elif 'win32' in sys.platform or 'win64' in sys.platform: + dataFolder = path.join(environ['APPDATA'], APPNAME) + '\\' + else: + dataFolder = path.expanduser(path.join("~", "." + APPNAME + "/")) + return dataFolder def isAddressInMyAddressBook(address): - """Is address in my addressbook?""" - queryreturn = sqlQuery( - '''select address from addressbook where address=?''', - address) + t = (address,) + sqlLock.acquire() + sqlSubmitQueue.put('''select address from addressbook where address=?''') + sqlSubmitQueue.put(t) + queryreturn = sqlReturnQueue.get() + sqlLock.release() return queryreturn != [] - -# 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 != []: + sqlLock.acquire() + sqlSubmitQueue.put('''SELECT address FROM whitelist where address=? and enabled = '1' ''') + sqlSubmitQueue.put((address,)) + queryreturn = sqlReturnQueue.get() + sqlLock.release() + if queryreturn <> []: return True - queryreturn = sqlQuery( - '''select address from subscriptions where address=?''' - ''' and enabled = '1' ''', - address) - if queryreturn != []: + sqlLock.acquire() + sqlSubmitQueue.put('''select address from subscriptions where address=? and enabled = '1' ''') + sqlSubmitQueue.put((address,)) + queryreturn = sqlReturnQueue.get() + sqlLock.release() + if queryreturn <> []: return True return False +def safeConfigGetBoolean(section,field): + try: + return config.getboolean(section,field) + except: + return False def decodeWalletImportFormat(WIFstring): - # pylint: disable=inconsistent-return-statements - """ - Convert private key from base58 that's used in the config file to - 8-bit binary string - """ - fullString = arithmetic.changebase(WIFstring, 58, 256) + fullString = arithmetic.changebase(WIFstring,58,256) privkey = fullString[:-4] - if fullString[-4:] != \ - hashlib.sha256(hashlib.sha256(privkey).digest()).digest()[:4]: - logger.critical( - 'Major problem! When trying to decode one of your' - ' private keys, the checksum failed. Here are the first' - ' 6 characters of the PRIVATE key: %s', - str(WIFstring)[:6] - ) - os._exit(0) # pylint: disable=protected-access - # return "" - elif privkey[0] == '\x80': # checksum passed - return privkey[1:] - - logger.critical( - 'Major problem! When trying to decode one of your private keys,' - ' the checksum passed but the key doesn\'t begin with hex 80.' - ' Here is the PRIVATE key: %s', WIFstring - ) - os._exit(0) # pylint: disable=protected-access + if fullString[-4:] != hashlib.sha256(hashlib.sha256(privkey).digest()).digest()[:4]: + sys.stderr.write('Major problem! When trying to decode one of your private keys, the checksum failed. Here is the PRIVATE key: %s\n' % str(WIFstring)) + return "" + else: + #checksum passed + if privkey[0] == '\x80': + return privkey[1:] + else: + sys.stderr.write('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\n' % str(WIFstring)) + return "" def reloadMyAddressHashes(): - """Reload keys for user's addresses from the config file""" - logger.debug('reloading keys from keys.dat file') + printLock.acquire() + print 'reloading keys from keys.dat file' + printLock.release() myECCryptorObjects.clear() myAddressesByHash.clear() - myAddressesByTag.clear() - # myPrivateKeys.clear() - - keyfileSecure = checkSensitiveFilePermissions(os.path.join( - state.appdata, 'keys.dat')) - hasEnabledKeys = False - for addressInKeysFile in config.addresses(): - isEnabled = config.getboolean(addressInKeysFile, 'enabled') - if isEnabled: - hasEnabledKeys = True - # status - addressVersionNumber, streamNumber, hashobj = decodeAddress(addressInKeysFile)[1:] - if addressVersionNumber in (2, 3, 4): - # Returns a simple 32 bytes of information encoded - # in 64 Hex characters, or null if there was an error. - privEncryptionKey = hexlify(decodeWalletImportFormat( - config.get(addressInKeysFile, 'privencryptionkey'))) - # It is 32 bytes encoded as 64 hex characters - if len(privEncryptionKey) == 64: - myECCryptorObjects[hashobj] = \ - highlevelcrypto.makeCryptor(privEncryptionKey) - myAddressesByHash[hashobj] = addressInKeysFile - tag = hashlib.sha512(hashlib.sha512( - encodeVarint(addressVersionNumber) - + encodeVarint(streamNumber) + hashobj).digest()).digest()[32:] - myAddressesByTag[tag] = addressInKeysFile - else: - logger.error( - 'Error in reloadMyAddressHashes: Can\'t handle' - ' address versions other than 2, 3, or 4.' - ) - - if not keyfileSecure: - fixSensitiveFilePermissions(os.path.join( - state.appdata, 'keys.dat'), hasEnabledKeys) - + #myPrivateKeys.clear() + configSections = config.sections() + for addressInKeysFile in configSections: + if addressInKeysFile <> 'bitmessagesettings': + isEnabled = config.getboolean(addressInKeysFile, 'enabled') + if isEnabled: + status,addressVersionNumber,streamNumber,hash = decodeAddress(addressInKeysFile) + if addressVersionNumber == 2 or addressVersionNumber == 3: + privEncryptionKey = decodeWalletImportFormat(config.get(addressInKeysFile, 'privencryptionkey')).encode('hex') #returns a simple 32 bytes of information encoded in 64 Hex characters, or null if there was an error + if len(privEncryptionKey) == 64:#It is 32 bytes encoded as 64 hex characters + myECCryptorObjects[hash] = highlevelcrypto.makeCryptor(privEncryptionKey) + myAddressesByHash[hash] = addressInKeysFile + else: + sys.stderr.write('Error in reloadMyAddressHashes: Can\'t handle address versions other than 2 or 3.\n') def reloadBroadcastSendersForWhichImWatching(): - """ - Reinitialize runtime data for the broadcasts I'm subscribed to - from the config file - """ + printLock.acquire() + print 'reloading subscriptions...' + printLock.release() broadcastSendersForWhichImWatching.clear() MyECSubscriptionCryptorObjects.clear() - queryreturn = sqlQuery('SELECT address FROM subscriptions where enabled=1') - logger.debug('reloading subscriptions...') + sqlLock.acquire() + sqlSubmitQueue.put('SELECT address FROM subscriptions where enabled=1') + sqlSubmitQueue.put('') + queryreturn = sqlReturnQueue.get() + sqlLock.release() 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. + privEncryptionKey = hashlib.sha512(encodeVarint(addressVersionNumber)+encodeVarint(streamNumber)+hash).digest()[:32] + MyECSubscriptionCryptorObjects[hash] = highlevelcrypto.makeCryptor(privEncryptionKey.encode('hex')) - if addressVersionNumber <= 3: - privEncryptionKey = hashlib.sha512( - encodeVarint(addressVersionNumber) - + encodeVarint(streamNumber) + hashobj - ).digest()[:32] - MyECSubscriptionCryptorObjects[hashobj] = \ - highlevelcrypto.makeCryptor(hexlify(privEncryptionKey)) - else: - doubleHashOfAddressData = hashlib.sha512(hashlib.sha512( - encodeVarint(addressVersionNumber) - + encodeVarint(streamNumber) + hashobj - ).digest()).digest() - tag = doubleHashOfAddressData[32:] - privEncryptionKey = doubleHashOfAddressData[:32] - MyECSubscriptionCryptorObjects[tag] = \ - highlevelcrypto.makeCryptor(hexlify(privEncryptionKey)) +def doCleanShutdown(): + knownNodesLock.acquire() + UISignalQueue.put(('updateStatusBar','Saving the knownNodes list of peers to disk...')) + output = open(appdata + 'knownnodes.dat', 'wb') + print 'finished opening knownnodes.dat. Now pickle.dump' + pickle.dump(knownNodes, output) + print 'Completed pickle.dump. Closing output...' + output.close() + knownNodesLock.release() + printLock.acquire() + print 'Finished closing knownnodes.dat output file.' + printLock.release() + UISignalQueue.put(('updateStatusBar','Done saving the knownNodes list of peers to disk.')) + broadcastToSendDataQueues((0, 'shutdown', 'all')) -def fixPotentiallyInvalidUTF8Data(text): - """Sanitise invalid UTF-8 strings""" + printLock.acquire() + print 'Flushing inventory in memory out to disk...' + printLock.release() + UISignalQueue.put(('updateStatusBar','Flushing inventory in memory out to disk. This should normally only take a second...')) + flushInventory() + + #This one last useless query will guarantee that the previous flush committed before we close the program. + sqlLock.acquire() + sqlSubmitQueue.put('SELECT address FROM subscriptions') + sqlSubmitQueue.put('') + sqlReturnQueue.get() + sqlSubmitQueue.put('exit') + sqlLock.release() + printLock.acquire() + print 'Finished flushing inventory.' + printLock.release() + + #Messy hack to kill child processes immediately. May not work on all platforms. try: - text.decode('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) + os.killpg(os.getpgid(os.getpid()), signal.SIGTERM) + except: + os.kill(os.getpid(), signal.SIGTERM) + if safeConfigGetBoolean('bitmessagesettings','daemon'): + printLock.acquire() + print 'Done.' + printLock.release() + os._exit(0) -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 - # Windows systems. - return True - elif sys.platform[:7] == 'freebsd': - # FreeBSD file systems are the same as major Linux file systems - 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 - - -# 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.') - else: - 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 - new_permissions = ( - allowed_permissions & present_permissions) - os.chmod(filename, new_permissions) - - logger.info('Keyfile permissions automatically fixed.') - - except Exception: - logger.exception('Keyfile permissions could not be fixed.') - raise +#Wen you want to command a sendDataThread 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): + #print 'running broadcastToSendDataQueues' + for q in sendDataQueues: + q.put((data)) + +def flushInventory(): + #Note that the singleCleanerThread clears out the inventory dictionary from time to time, although it only clears things that have been in the dictionary for a long time. This clears the inventory dictionary Now. + sqlLock.acquire() + for hash, storedValue in inventory.items(): + objectType, streamNumber, payload, receivedTime = storedValue + t = (hash,objectType,streamNumber,payload,receivedTime) + sqlSubmitQueue.put('''INSERT INTO inventory VALUES (?,?,?,?,?)''') + sqlSubmitQueue.put(t) + sqlReturnQueue.get() + del inventory[hash] + sqlSubmitQueue.put('commit') + sqlLock.release() diff --git a/src/shutdown.py b/src/shutdown.py deleted file mode 100644 index 3e2b8ca8..00000000 --- a/src/shutdown.py +++ /dev/null @@ -1,91 +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 inventory import Inventory -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...')) - 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..7ecca3b7 100644 --- a/src/singleton.py +++ b/src/singleton.py @@ -1,22 +1,61 @@ -""" -Singleton decorator definition -""" +#! /usr/bin/env python -from functools import wraps +import sys +import os +import errno +import tempfile +from multiprocessing import Process -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 based on the full path to the script file. - # 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=""): + import sys + self.initialized = False + basename = os.path.splitext(os.path.abspath(sys.argv[0]))[0].replace("/", "-").replace(":", "").replace("\\", "-") + '-%s' % flavor_id + '.lock' + self.lockfile = os.path.normpath(tempfile.gettempdir() + '/' + basename) + + 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 + import fcntl + self.fp = open(self.lockfile, 'w') + try: + fcntl.lockf(self.fp, fcntl.LOCK_EX | fcntl.LOCK_NB) + except IOError: + print 'Another instance of this application is already running' + sys.exit(-1) + self.initialized = True + + def __del__(self): + import sys + if not self.initialized: + return + try: + if sys.platform == 'win32': + if hasattr(self, 'fd'): + os.close(self.fd) + os.unlink(self.lockfile) + else: + import fcntl + fcntl.lockf(self.fp, fcntl.LOCK_UN) + if os.path.isfile(self.lockfile): + os.unlink(self.lockfile) + except Exception, e: + sys.exit(-1) 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..ebb68f45 --- /dev/null +++ b/src/socks/__init__.py @@ -0,0 +1,382 @@ +"""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") + +_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. + """ + data = self.recv(count) + 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, destaddr, destport): + """__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])) + # 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 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 __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 + _orgsocket.connect(self, (self.__proxy[1], portnum)) + self.__negotiatesocks5(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])) diff --git a/src/specialaddressbehavior.py b/src/specialaddressbehavior.py new file mode 100644 index 00000000..78ff890d --- /dev/null +++ b/src/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/specialaddressbehavior.ui b/src/specialaddressbehavior.ui similarity index 100% rename from src/bitmessageqt/specialaddressbehavior.ui rename to src/specialaddressbehavior.ui 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/sslkeys/cert.pem b/src/sslkeys/cert.pem deleted file mode 100644 index a976db75..00000000 --- a/src/sslkeys/cert.pem +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICWDCCAcGgAwIBAgIJAJs5yni/cDh5MA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV -BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX -aWRnaXRzIFB0eSBMdGQwHhcNMTUxMTEzMDk1NTU3WhcNMTUxMTE0MDk1NTU3WjBF -MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 -ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB -gQCg8XkFpIAYsTSBealTubvu4dzpMnnAOwANG5K9TJeclG9O65cmKWpH8k3hNDif -xagIAI8UanBsQo6SQrK1Iby2kz6DCKmySO1OwoNOOF0Ok31N+5aWsQvYF1wLbk2m -Ti/CSLWBgL25ywCCiP3Mgr+krapT4TrfvF4gCchUdcxMQQIDAQABo1AwTjAdBgNV -HQ4EFgQUWuFUJQC6zu6OTDgHZzhfZxsgJOMwHwYDVR0jBBgwFoAUWuFUJQC6zu6O -TDgHZzhfZxsgJOMwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQAT1I/x -GbsYAE4pM4sVQrcuz7jLwr3k5Zve0z4WKR41W17Nc44G3DyLbkTWYESLfAYsivkx -tRRtYTtJm1qmTPtedXQJK+wJGNHCWRfwSB2CYwmO7+C2rYYzkFndN68kB6RJmyOr -eCX+9vkbQqgh7KfiNquJxCfMSDfhA2RszU43jg== ------END CERTIFICATE----- diff --git a/src/sslkeys/key.pem b/src/sslkeys/key.pem deleted file mode 100644 index a47bcc3c..00000000 --- a/src/sslkeys/key.pem +++ /dev/null @@ -1,16 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAKDxeQWkgBixNIF5 -qVO5u+7h3OkyecA7AA0bkr1Ml5yUb07rlyYpakfyTeE0OJ/FqAgAjxRqcGxCjpJC -srUhvLaTPoMIqbJI7U7Cg044XQ6TfU37lpaxC9gXXAtuTaZOL8JItYGAvbnLAIKI -/cyCv6StqlPhOt+8XiAJyFR1zExBAgMBAAECgYEAmd2hpQpayMCJgQsOHhRgnoXi -jDOMgIInj2CADmguPi0OqTXEoGBR0ozNdfNV+zGdbmESaSNFbcrHwP7xGQgzABlv -5ANLgBYrHnW/oFCCuw4Lj/CAAHRA4its+2wzf13BYoVitDiYBt3JMRqwLV03aHyy -Oqhvt2nVicz85+HERj0CQQDMJAPUIyOQLO+BPC5MsuxyQFJgie0aB5swumxanOv4 -J8GIvulNEJMG/dq+h/x4paV2LGDlUAOsBUmjXfTPMQAHAkEAydQtYorqYqhFZWWD -3lUMAoa8pGb6BfNXUqxdH0H8fk6B7OxYPpvwm7ce1lD1Oje3/+rMnn8i6A1p9HUy -l9wvdwJAdhxIUs7Z3qsBD8bgCuRixV/NyalDk5HfCnxyAKNWK8fkw9ehaEM0rhDm -JOLNAojkiND4ZvS6iyasCmdsIwx4tQJAAV+eR3NmkPFQN5ZvRU4S3NmJ4xyISw4S -5A8kOxg53aovHCunlhV9l7GxVggLAzBp4iX46oM2+5lLxUwe4gWvlQJBAK0IR8bB -85bKZ+M/O8rbs9kQHjx6GCbbDxH+qbIKkNcvLUvMgwwIFKiwqX+Tedtu2xET0mQM -9tEE5eMBOJ8GrxQ= ------END PRIVATE KEY----- diff --git a/src/state.py b/src/state.py deleted file mode 100644 index cac5db19..00000000 --- a/src/state.py +++ /dev/null @@ -1,73 +0,0 @@ -""" -Global runtime variables. -""" - -neededPubkeys = {} -streamsInWhichIAmParticipating = [] - -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 = {} - -dandelion = 0 - -kivy = False - -kivyapp = None - -testmode = False - -clientHasReceivedIncomingConnections = False -"""used by API command clientStatus""" - -numberOfMessagesProcessed = 0 -numberOfBroadcastsProcessed = 0 -numberOfPubkeysProcessed = 0 - -statusIconColor = 'red' -""" -GUI status icon color -.. note:: bad style, refactor it -""" - -ackdataForWhichImWatching = {} - -thisapp = None -"""Singleton instance""" - -backend_py3_compatible = False diff --git a/src/storage/__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 868efe2b..00000000 --- a/src/tests/core.py +++ /dev/null @@ -1,436 +0,0 @@ -""" -Tests for core and those that do not work outside -(because of import error for example) -""" - -import atexit -import os -import pickle # nosec -import Queue -import random # nosec -import shutil -import socket -import string -import sys -import threading -import time -import unittest - -import protocol -import state -import helper_sent -import helper_addressbook - -from bmconfigparser import config -from helper_msgcoding import MsgEncode, MsgDecode -from helper_sql import sqlQuery -from network import asyncore_pollchoose as asyncore, knownnodes -from network.bmproto import BMProto -from network.connectionpool import BMConnectionPool -from network.node import Node, Peer -from network.tcp import Socks4aBMConnection, Socks5BMConnection, TCPConnection -from queues import excQueue -from version import softwareVersion - -from common import cleanup - -try: - socket.socket().bind(('127.0.0.1', 9050)) - tor_port_free = True -except (OSError, socket.error): - tor_port_free = False - -frozen = getattr(sys, 'frozen', None) -knownnodes_file = os.path.join(state.appdata, 'knownnodes.dat') - - -def pickle_knownnodes(): - """Generate old style pickled knownnodes.dat""" - now = time.time() - with open(knownnodes_file, 'wb') as dst: - pickle.dump({ - stream: { - Peer( - '%i.%i.%i.%i' % tuple([ - random.randint(1, 255) for i in range(4)]), - 8444): {'lastseen': now, 'rating': 0.1} - for i in range(1, 4) # 3 test nodes - } - for stream in range(1, 4) # 3 test streams - }, dst) - - -class TestCore(unittest.TestCase): - """Test case, which runs in main pybitmessage thread""" - addr = 'BM-2cVvkzJuQDsQHLqxRXc6HZGPLZnkBLzEZY' - - def tearDown(self): - """Reset possible unexpected settings after test""" - knownnodes.addKnownNode(1, Peer('127.0.0.1', 8444), is_self=True) - config.remove_option('bitmessagesettings', 'dontconnect') - config.remove_option('bitmessagesettings', 'onionservicesonly') - config.set('bitmessagesettings', 'socksproxytype', 'none') - - def test_msgcoding(self): - """test encoding and decoding (originally from helper_msgcoding)""" - msg_data = { - 'subject': ''.join( - random.choice(string.ascii_lowercase + string.digits) # nosec - for _ in range(40)), - 'body': ''.join( - random.choice(string.ascii_lowercase + string.digits) # nosec - for _ in range(10000)) - } - - obj1 = MsgEncode(msg_data, 1) - obj2 = MsgEncode(msg_data, 2) - obj3 = MsgEncode(msg_data, 3) - # print "1: %i 2: %i 3: %i" % ( - # len(obj1.data), len(obj2.data), len(obj3.data)) - - obj1e = MsgDecode(1, obj1.data) - # no subject in trivial encoding - self.assertEqual(msg_data['body'], obj1e.body) - - obj2e = MsgDecode(2, obj2.data) - self.assertEqual(msg_data['subject'], obj2e.subject) - self.assertEqual(msg_data['body'], obj2e.body) - - obj3e = MsgDecode(3, obj3.data) - self.assertEqual(msg_data['subject'], obj3e.subject) - self.assertEqual(msg_data['body'], obj3e.body) - - try: - MsgEncode({'body': 'A msg with no subject'}, 3) - except Exception as e: - self.fail( - 'Exception %s while trying to encode message' - ' with no subject!' % e - ) - - @unittest.skip('Bad environment for asyncore.loop') - def test_tcpconnection(self): - """initial fill script from network.tcp""" - config.set('bitmessagesettings', 'dontconnect', 'true') - try: - for peer in (Peer("127.0.0.1", 8448),): - direct = TCPConnection(peer) - while asyncore.socket_map: - print("loop, state = %s" % direct.state) - asyncore.loop(timeout=10, count=1) - except: # noqa:E722 - self.fail('Exception in test loop') - - def _load_knownnodes(self, filepath): - with knownnodes.knownNodesLock: - shutil.copyfile(filepath, knownnodes_file) - try: - knownnodes.readKnownNodes() - except AttributeError as e: - self.fail('Failed to load knownnodes: %s' % e) - - @staticmethod - def _wipe_knownnodes(): - with knownnodes.knownNodesLock: - knownnodes.knownNodes = {stream: {} for stream in range(1, 4)} - - @staticmethod - def _outdate_knownnodes(): - with knownnodes.knownNodesLock: - for nodes in knownnodes.knownNodes.itervalues(): - for node in nodes.itervalues(): - node['lastseen'] -= 2419205 # older than 28 days - - def test_knownnodes_pickle(self): - """ensure that 3 nodes was imported for each stream""" - pickle_knownnodes() - self._wipe_knownnodes() - knownnodes.readKnownNodes() - for nodes in knownnodes.knownNodes.itervalues(): - self_count = n = 0 - for n, node in enumerate(nodes.itervalues()): - if node.get('self'): - self_count += 1 - self.assertEqual(n - self_count, 2) - - def test_knownnodes_default(self): - """test adding default knownnodes if nothing loaded""" - cleanup(files=('knownnodes.dat',)) - self._wipe_knownnodes() - knownnodes.readKnownNodes() - self.assertGreaterEqual( - len(knownnodes.knownNodes[1]), len(knownnodes.DEFAULT_NODES)) - - def test_0_cleaner(self): - """test knownnodes starvation leading to IndexError in Asyncore""" - self._outdate_knownnodes() - # time.sleep(303) # singleCleaner wakes up every 5 min - knownnodes.cleanupKnownNodes() - self.assertTrue(knownnodes.knownNodes[1]) - while True: - try: - thread, exc = excQueue.get(block=False) - except Queue.Empty: - return - if thread == 'Asyncore' and isinstance(exc, IndexError): - self.fail("IndexError because of empty knownNodes!") - - def _initiate_bootstrap(self): - config.set('bitmessagesettings', 'dontconnect', 'true') - self._wipe_knownnodes() - knownnodes.addKnownNode(1, Peer('127.0.0.1', 8444), is_self=True) - knownnodes.cleanupKnownNodes() - time.sleep(5) - - def _check_connection(self, full=False): - """ - Check if there is at least one outbound connection to remote host - with name not starting with "bootstrap" in 6 minutes at most, - fail otherwise. - """ - _started = time.time() - config.remove_option('bitmessagesettings', 'dontconnect') - proxy_type = config.safeGet( - 'bitmessagesettings', 'socksproxytype') - if proxy_type == 'SOCKS5': - connection_base = Socks5BMConnection - elif proxy_type == 'SOCKS4a': - connection_base = Socks4aBMConnection - else: - connection_base = TCPConnection - c = 360 - while c > 0: - time.sleep(1) - c -= 2 - for peer, con in BMConnectionPool().outboundConnections.iteritems(): - if ( - peer.host.startswith('bootstrap') - or peer.host == 'quzwelsuziwqgpt2.onion' - ): - if c < 60: - self.fail( - 'Still connected to bootstrap node %s after %.2f' - ' seconds' % (peer, time.time() - _started)) - c += 1 - break - else: - self.assertIsInstance(con, connection_base) - self.assertNotEqual(peer.host, '127.0.0.1') - if full and not con.fullyEstablished: - continue - return - self.fail( - 'Failed to connect during %.2f sec' % (time.time() - _started)) - - def _check_knownnodes(self): - for stream in knownnodes.knownNodes.itervalues(): - for peer in stream: - if peer.host.startswith('bootstrap'): - self.fail( - 'Bootstrap server in knownnodes: %s' % peer.host) - - def test_dontconnect(self): - """all connections are closed 5 seconds after setting dontconnect""" - self._initiate_bootstrap() - self.assertEqual(len(BMConnectionPool().connections()), 0) - - def test_connection(self): - """test connection to bootstrap servers""" - self._initiate_bootstrap() - for port in [8080, 8444]: - for item in socket.getaddrinfo( - 'bootstrap%s.bitmessage.org' % port, 80): - try: - addr = item[4][0] - socket.inet_aton(item[4][0]) - except (TypeError, socket.error): - continue - else: - knownnodes.addKnownNode(1, Peer(addr, port)) - self._check_connection(True) - - def test_bootstrap(self): - """test bootstrapping""" - config.set('bitmessagesettings', 'socksproxytype', 'none') - self._initiate_bootstrap() - self._check_connection() - self._check_knownnodes() - # backup potentially enough knownnodes - knownnodes.saveKnownNodes() - with knownnodes.knownNodesLock: - shutil.copyfile(knownnodes_file, knownnodes_file + '.bak') - - @unittest.skipIf(tor_port_free, 'no running tor detected') - def test_bootstrap_tor(self): - """test bootstrapping with tor""" - config.set('bitmessagesettings', 'socksproxytype', 'SOCKS5') - self._initiate_bootstrap() - self._check_connection() - self._check_knownnodes() - - @unittest.skipIf(tor_port_free, 'no running tor detected') - def test_onionservicesonly(self): - """ensure bitmessage doesn't try to connect to non-onion nodes - if onionservicesonly set, wait at least 3 onion nodes - """ - config.set('bitmessagesettings', 'socksproxytype', 'SOCKS5') - config.set('bitmessagesettings', 'onionservicesonly', 'true') - self._load_knownnodes(knownnodes_file + '.bak') - if len([ - node for node in knownnodes.knownNodes[1] - if node.host.endswith('.onion') - ]) < 3: # generate fake onion nodes if have not enough - with knownnodes.knownNodesLock: - for f in ('a', 'b', 'c', 'd'): - knownnodes.addKnownNode(1, Peer(f * 16 + '.onion', 8444)) - config.remove_option('bitmessagesettings', 'dontconnect') - tried_hosts = set() - for _ in range(360): - time.sleep(1) - for peer in BMConnectionPool().outboundConnections: - if peer.host.endswith('.onion'): - tried_hosts.add(peer.host) - else: - if not peer.host.startswith('bootstrap'): - self.fail( - 'Found non onion hostname %s in outbound' - 'connections!' % peer.host) - if len(tried_hosts) > 2: - return - self.fail('Failed to find at least 3 nodes to connect within 360 sec') - - @unittest.skipIf(frozen, 'skip fragile test') - def test_udp(self): - """check default udp setting and presence of Announcer thread""" - self.assertTrue( - config.safeGetBoolean('bitmessagesettings', 'udp')) - for thread in threading.enumerate(): - if thread.name == 'Announcer': # find Announcer thread - break - else: - return self.fail('No Announcer thread found') - - @staticmethod - def _decode_msg(data, pattern): - proto = BMProto() - proto.bm_proto_reset() - proto.payload = data[protocol.Header.size:] - return proto.decode_payload_content(pattern) - - def test_version(self): - """check encoding/decoding of the version message""" - # with single stream - msg = protocol.assembleVersionMessage('127.0.0.1', 8444, [1]) - decoded = self._decode_msg(msg, "IQQiiQlsLv") - peer, _, ua, streams = self._decode_msg(msg, "IQQiiQlsLv")[4:] - self.assertEqual( - peer, Node(11 if state.dandelion else 3, '127.0.0.1', 8444)) - self.assertEqual(ua, '/PyBitmessage:' + softwareVersion + '/') - self.assertEqual(streams, [1]) - # with multiple streams - msg = protocol.assembleVersionMessage('127.0.0.1', 8444, [1, 2, 3]) - decoded = self._decode_msg(msg, "IQQiiQlslv") - peer, _, ua = decoded[4:7] - streams = decoded[7:] - self.assertEqual(streams, [1, 2, 3]) - - def test_insert_method_msgid(self): - """Test insert method of helper_sent module with message sending""" - fromAddress = 'BM-2cTrmD22fLRrumi3pPLg1ELJ6PdAaTRTdfg' - toAddress = 'BM-2cUGaEcGz9Zft1SPAo8FJtfzyADTpEgU9U' - message = 'test message' - subject = 'test subject' - result = helper_sent.insert( - toAddress=toAddress, fromAddress=fromAddress, - subject=subject, message=message - ) - queryreturn = sqlQuery( - '''select msgid from sent where ackdata=?''', result) - self.assertNotEqual(queryreturn[0][0] if queryreturn else '', '') - - column_type = sqlQuery( - '''select typeof(msgid) from sent where ackdata=?''', result) - self.assertEqual(column_type[0][0] if column_type else '', 'text') - - @unittest.skipIf(frozen, 'not packed test_pattern into the bundle') - def test_old_knownnodes_pickle(self): - """Testing old (v0.6.2) version knownnodes.dat file""" - try: - self._load_knownnodes( - os.path.join( - os.path.abspath(os.path.dirname(__file__)), - 'test_pattern', 'knownnodes.dat')) - except self.failureException: - raise - finally: - cleanup(files=('knownnodes.dat',)) - - @staticmethod - def delete_address_from_addressbook(address): - """Clean up addressbook""" - sqlQuery('''delete from addressbook where address=?''', address) - - def test_add_same_address_twice_in_addressbook(self): - """checking same address is added twice in addressbook""" - self.assertTrue( - helper_addressbook.insert(label='test1', address=self.addr)) - self.assertFalse( - helper_addressbook.insert(label='test1', address=self.addr)) - self.delete_address_from_addressbook(self.addr) - - def test_is_address_present_in_addressbook(self): - """checking is address added in addressbook or not""" - helper_addressbook.insert(label='test1', address=self.addr) - queryreturn = sqlQuery( - 'select count(*) from addressbook where address=?', self.addr) - self.assertEqual(queryreturn[0][0], 1) - self.delete_address_from_addressbook(self.addr) - - def test_adding_two_same_case_sensitive_addresses(self): - """Testing same case sensitive address store in addressbook""" - address1 = 'BM-2cVWtdUzPwF7UNGDrZftWuHWiJ6xxBpiSP' - address2 = 'BM-2CvwTDuZpWf7ungdRzFTwUhwIj6XXbPIsp' - self.assertTrue( - helper_addressbook.insert(label='test1', address=address1)) - self.assertTrue( - helper_addressbook.insert(label='test2', address=address2)) - self.delete_address_from_addressbook(address1) - self.delete_address_from_addressbook(address2) - - -def run(): - """Starts all tests intended for core run""" - loader = unittest.defaultTestLoader - loader.sortTestMethodsUsing = None - suite = loader.loadTestsFromTestCase(TestCore) - if frozen: - try: - from pybitmessage import tests - suite.addTests(loader.loadTestsFromModule(tests)) - except ImportError: - pass - try: - from pyelliptic import tests - suite.addTests(loader.loadTestsFromModule(tests)) - except ImportError: - pass - try: - import bitmessageqt.tests - from xvfbwrapper import Xvfb - except ImportError: - Xvfb = None - else: - qt_tests = loader.loadTestsFromModule(bitmessageqt.tests) - suite.addTests(qt_tests) - - def keep_exc(ex_cls, exc, tb): # pylint: disable=unused-argument - """Own exception hook for test cases""" - excQueue.put(('tests', exc)) - - sys.excepthook = keep_exc - - if Xvfb: - vdisplay = Xvfb(width=1024, height=768) - vdisplay.start() - atexit.register(vdisplay.stop) - return unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/src/tests/mockbm/__init__.py b/src/tests/mockbm/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/tests/mockbm/bitmessagemock.py b/src/tests/mockbm/bitmessagemock.py deleted file mode 100644 index d9ee857b..00000000 --- a/src/tests/mockbm/bitmessagemock.py +++ /dev/null @@ -1,32 +0,0 @@ -# pylint: disable=no-name-in-module, import-error - -""" -Bitmessage mock -""" - -from pybitmessage.class_addressGenerator import addressGenerator -from pybitmessage.inventory import Inventory -from pybitmessage.mpybit import NavigateApp -from pybitmessage import state - - -class MockMain(object): # pylint: disable=too-few-public-methods - """Mock main function""" - - def __init__(self): - """Start main application""" - addressGeneratorThread = addressGenerator() - # close the main program even if there are threads left - addressGeneratorThread.start() - Inventory() - state.kivyapp = NavigateApp() - state.kivyapp.run() - - -def main(): - """Triggers main module""" - MockMain() - - -if __name__ == "__main__": - main() diff --git a/src/tests/mockbm/images b/src/tests/mockbm/images deleted file mode 120000 index 847b03ed..00000000 --- a/src/tests/mockbm/images +++ /dev/null @@ -1 +0,0 @@ -../../images/ \ No newline at end of file diff --git a/src/tests/mockbm/kivy_main.py b/src/tests/mockbm/kivy_main.py deleted file mode 100644 index 79bb413e..00000000 --- a/src/tests/mockbm/kivy_main.py +++ /dev/null @@ -1,8 +0,0 @@ -"""Mock kivy app with mock threads.""" -from pybitmessage import state - -if __name__ == '__main__': - state.kivy = True - print("Kivy Loading......") - from bitmessagemock import main - main() diff --git a/src/tests/mockbm/pybitmessage/__init__.py b/src/tests/mockbm/pybitmessage/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/tests/mockbm/pybitmessage/addresses.py b/src/tests/mockbm/pybitmessage/addresses.py deleted file mode 120000 index 88fcee82..00000000 --- a/src/tests/mockbm/pybitmessage/addresses.py +++ /dev/null @@ -1 +0,0 @@ -../../../addresses.py \ No newline at end of file diff --git a/src/tests/mockbm/pybitmessage/bmconfigparser.py b/src/tests/mockbm/pybitmessage/bmconfigparser.py deleted file mode 120000 index da05040e..00000000 --- a/src/tests/mockbm/pybitmessage/bmconfigparser.py +++ /dev/null @@ -1 +0,0 @@ -../../../bmconfigparser.py \ No newline at end of file diff --git a/src/tests/mockbm/pybitmessage/class_addressGenerator.py b/src/tests/mockbm/pybitmessage/class_addressGenerator.py deleted file mode 100644 index 34258bbc..00000000 --- a/src/tests/mockbm/pybitmessage/class_addressGenerator.py +++ /dev/null @@ -1,80 +0,0 @@ -""" -A thread for creating addresses -""" - -from six.moves import queue - -from pybitmessage import state -from pybitmessage import queues - -from pybitmessage.bmconfigparser import BMConfigParser - -from pybitmessage.network.threads import StoppableThread - - -fake_addresses = { - 'BM-2cUgQGcTLWAkC6dNsv2Bc8XB3Y1GEesVLV': { - 'privsigningkey': '5KWXwYq1oJMzghUSJaJoWPn8VdeBbhDN8zFot1cBd6ezKKReqBd', - 'privencryptionkey': '5JaeFJs8iPcQT3N8676r3gHKvJ5mTWXy1VLhGCEDqRs4vpvpxV8' - }, - 'BM-2cUd2dm8MVMokruMTcGhhteTpyRZCAMhnA': { - 'privsigningkey': '5JnJ79nkcwjo4Aj7iG8sFMkzYoQqWfpUjTcitTuFJZ1YKHZz98J', - 'privencryptionkey': '5JXgNzTRouFLqSRFJvuHMDHCYPBvTeMPBiHt4Jeb6smNjhUNTYq' - }, - 'BM-2cWyvL54WytfALrJHZqbsDHca5QkrtByAW': { - 'privsigningkey': '5KVE4gLmcfYVicLdgyD4GmnbBTFSnY7Yj2UCuytQqgBBsfwDhpi', - 'privencryptionkey': '5JTw48CGm5CP8fyJUJQMq8HQANQMHDHp2ETUe1dgm6EFpT1egD7' - }, - 'BM-2cTE65PK9Y4AQEkCZbazV86pcQACocnRXd': { - 'privsigningkey': '5KCuyReHx9MB4m5hhEyCWcLEXqc8rxhD1T2VWk8CicPFc8B6LaZ', - 'privencryptionkey': '5KBRpwXdX3n2tP7f583SbFgfzgs6Jemx7qfYqhdH7B1Vhe2jqY6' - }, - 'BM-2cX5z1EgmJ87f2oKAwXdv4VQtEVwr2V3BG': { - 'privsigningkey': '5K5UK7qED7F1uWCVsehudQrszLyMZxFVnP6vN2VDQAjtn5qnyRK', - 'privencryptionkey': '5J5coocoJBX6hy5DFTWKtyEgPmADpSwfQTazMpU7QPeART6oMAu' - } -} - - -class addressGenerator(StoppableThread): - """A thread for creating fake addresses""" - name = "addressGenerator" - address_list = list(fake_addresses.keys()) - - def stopThread(self): - """"To stop address generator thread""" - try: - queues.addressGeneratorQueue.put(("stopThread", "data")) - except queue.Full: - self.logger.warning('addressGeneratorQueue is Full') - super(addressGenerator, self).stopThread() # pylint: disable=super-with-arguments - - def run(self): - """ - Process the requests for addresses generation - from `.queues.addressGeneratorQueue` - """ - while state.shutdown == 0: - queueValue = queues.addressGeneratorQueue.get() - try: - address = self.address_list.pop(0) - except IndexError: - self.logger.error( - 'Program error: you can only create 5 fake addresses') - continue - - if len(queueValue) >= 3: - label = queueValue[3] - else: - label = '' - - BMConfigParser().add_section(address) - BMConfigParser().set(address, 'label', label) - BMConfigParser().set(address, 'enabled', 'true') - BMConfigParser().set( - address, 'privsigningkey', fake_addresses[address]['privsigningkey']) - BMConfigParser().set( - address, 'privencryptionkey', fake_addresses[address]['privencryptionkey']) - BMConfigParser().save() - - queues.addressGeneratorQueue.task_done() diff --git a/src/tests/mockbm/pybitmessage/inventory.py b/src/tests/mockbm/pybitmessage/inventory.py deleted file mode 100644 index 6173c3cd..00000000 --- a/src/tests/mockbm/pybitmessage/inventory.py +++ /dev/null @@ -1,15 +0,0 @@ -"""The Inventory singleton""" - -# TODO make this dynamic, and watch out for frozen, like with messagetypes -from pybitmessage.singleton import Singleton - - -# pylint: disable=old-style-class,too-few-public-methods -@Singleton -class Inventory(): - """ - Inventory singleton class which uses storage backends - to manage the inventory. - """ - def __init__(self): - self.numberOfInventoryLookupsPerformed = 0 diff --git a/src/tests/mockbm/pybitmessage/network/__init__.py b/src/tests/mockbm/pybitmessage/network/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/tests/mockbm/pybitmessage/network/threads.py b/src/tests/mockbm/pybitmessage/network/threads.py deleted file mode 120000 index c95b4c36..00000000 --- a/src/tests/mockbm/pybitmessage/network/threads.py +++ /dev/null @@ -1 +0,0 @@ -../../../../network/threads.py \ No newline at end of file diff --git a/src/tests/mockbm/pybitmessage/queues.py b/src/tests/mockbm/pybitmessage/queues.py deleted file mode 120000 index 8c556015..00000000 --- a/src/tests/mockbm/pybitmessage/queues.py +++ /dev/null @@ -1 +0,0 @@ -../../../queues.py \ No newline at end of file diff --git a/src/tests/mockbm/pybitmessage/shutdown.py b/src/tests/mockbm/pybitmessage/shutdown.py deleted file mode 100644 index 08c885d8..00000000 --- a/src/tests/mockbm/pybitmessage/shutdown.py +++ /dev/null @@ -1,11 +0,0 @@ -# pylint: disable=invalid-name -"""shutdown function""" - -from pybitmessage import state - - -def doCleanShutdown(): - """ - Used to exit Kivy UI. - """ - state.shutdown = 1 diff --git a/src/tests/mockbm/pybitmessage/singleton.py b/src/tests/mockbm/pybitmessage/singleton.py deleted file mode 120000 index 5e112567..00000000 --- a/src/tests/mockbm/pybitmessage/singleton.py +++ /dev/null @@ -1 +0,0 @@ -../../../singleton.py \ No newline at end of file diff --git a/src/tests/mockbm/pybitmessage/state.py b/src/tests/mockbm/pybitmessage/state.py deleted file mode 120000 index 117203f5..00000000 --- a/src/tests/mockbm/pybitmessage/state.py +++ /dev/null @@ -1 +0,0 @@ -../../../state.py \ No newline at end of file diff --git a/src/tests/partial.py b/src/tests/partial.py deleted file mode 100644 index 870f6626..00000000 --- a/src/tests/partial.py +++ /dev/null @@ -1,41 +0,0 @@ -"""A test case for partial run class definition""" - -import os -import sys -import unittest - -from pybitmessage import pathmagic - - -class TestPartialRun(unittest.TestCase): - """ - A base class for test cases running some parts of the app, - e.g. separate threads or packages. - """ - - @classmethod - def setUpClass(cls): - # pylint: disable=import-outside-toplevel,unused-import - cls.dirs = (os.path.abspath(os.curdir), pathmagic.setup()) - - import bmconfigparser - import state - - from debug import logger # noqa:F401 pylint: disable=unused-variable - if sys.hexversion >= 0x3000000: - # pylint: disable=no-name-in-module,relative-import - from mockbm import network as network_mock - import network - network.stats = network_mock.stats - - state.shutdown = 0 - cls.state = state - bmconfigparser.config = cls.config = bmconfigparser.BMConfigParser() - cls.config.read() - - @classmethod - def tearDownClass(cls): - cls.state.shutdown = 1 - # deactivate pathmagic - os.chdir(cls.dirs[0]) - sys.path.remove(cls.dirs[1]) diff --git a/src/tests/samples.py b/src/tests/samples.py deleted file mode 100644 index e33c5f50..00000000 --- a/src/tests/samples.py +++ /dev/null @@ -1,75 +0,0 @@ -"""Various sample data""" - -from binascii import unhexlify - - -# 500 identical peers: -# 1626611891, 1, 1, 127.0.0.1, 8444 -sample_addr_data = unhexlify( - 'fd01f4' + ( - '0000000060f420b30000000' - '1000000000000000100000000000000000000ffff7f00000120fc' - ) * 500 -) - -# These keys are from addresses test script -sample_pubsigningkey = unhexlify( - '044a367f049ec16cb6b6118eb734a9962d10b8db59c890cd08f210c43ff08bdf09d' - '16f502ca26cd0713f38988a1237f1fc8fa07b15653c996dc4013af6d15505ce') -sample_pubencryptionkey = unhexlify( - '044597d59177fc1d89555d38915f581b5ff2286b39d022ca0283d2bdd5c36be5d3c' - 'e7b9b97792327851a562752e4b79475d1f51f5a71352482b241227f45ed36a9') -sample_privsigningkey = \ - b'93d0b61371a54b53df143b954035d612f8efa8a3ed1cf842c2186bfd8f876665' -sample_privencryptionkey = \ - b'4b0b73a54e19b059dc274ab69df095fe699f43b17397bca26fdf40f4d7400a3a' - -sample_ripe = b'003cd097eb7f35c87b5dc8b4538c22cb55312a9f' -# stream: 1, version: 2 -sample_address = 'BM-onkVu1KKL2UaUss5Upg9vXmqd3esTmV79' - -sample_factor = \ - 66858749573256452658262553961707680376751171096153613379801854825275240965733 -# G * sample_factor -sample_point = ( - 33567437183004486938355437500683826356288335339807546987348409590129959362313, - 94730058721143827257669456336351159718085716196507891067256111928318063085006 -) - -sample_seed = b'TIGER, tiger, burning bright. In the forests of the night' -# Deterministic addresses with stream 1 and versions 3, 4 -sample_deterministic_ripe = b'00cfb69416ae76f68a81c459de4e13460c7d17eb' -sample_deterministic_addr3 = 'BM-2DBPTgeSawWYZceFD69AbDT5q4iUWtj1ZN' -sample_deterministic_addr4 = 'BM-2cWzSnwjJ7yRP3nLEWUV5LisTZyREWSzUK' -sample_daddr3_512 = 18875720106589866286514488037355423395410802084648916523381 -sample_daddr4_512 = 25152821841976547050350277460563089811513157529113201589004 - -sample_statusbar_msg = 'new status bar message' -sample_inbox_msg_ids = [ - '27e644765a3e4b2e973ee7ccf958ea20', '51fc5531-3989-4d69-bbb5-68d64b756f5b', - '2c975c515f8b414db5eea60ba57ba455', 'bc1f2d8a-681c-4cc0-9a12-6067c7e1ac24'] -# second address in sample_subscription_addresses is -# for the announcement broadcast, but is it matter? -sample_subscription_addresses = [ - 'BM-2cWQLCBGorT9pUGkYSuGGVr9LzE4mRnQaq', - 'BM-GtovgYdgs7qXPkoYaRgrLFuFKz1SFpsw'] -sample_subscription_name = 'test sub' - - -sample_object_expires = 1712271487 -# from minode import structure -# obj = structure.Object( -# b'\x00' * 8, sample_object_expires, 42, 1, 2, b'HELLO') -# .. do pow and obj.to_bytes() -sample_object_data = unhexlify( - '00000000001be7fc00000000660f307f0000002a010248454c4c4f') - -sample_msg = unhexlify( - '0592a10584ffabf96539f3d780d776828c67da1ab5b169e9e8aed838aaecc9ed36d49ff' - '1423c55f019e050c66c6324f53588be88894fef4dcffdb74b98e2b200') -sample_sig = unhexlify( - '304402202302475351db6b822de15d922e29397541f10d8a19780ba2ca4a920b1035f075' - '02205e5bba40d5f07a24c23a89ba5f01a3828371dfbb685dd5375fa1c29095fd232b') -sample_sig_sha1 = unhexlify( - '30460221008ad234687d1bdc259932e28ea6ee091b88b0900d8134902aa8c2fd7f016b96e' - 'd022100dafb94e28322c2fa88878f9dcbf0c2d33270466ab3bbffaec3dca0a2d1ef9354') diff --git a/src/tests/sql/init_version_10.sql b/src/tests/sql/init_version_10.sql deleted file mode 100644 index b1764e76..00000000 --- a/src/tests/sql/init_version_10.sql +++ /dev/null @@ -1 +0,0 @@ -INSERT INTO `addressbook` VALUES ('test', "BM-2cWzMnxjJ7yRP3nLEWUV5LisTZyREWSxYz"), ('testone', "BM-2cWzMnxjJ7yRP3nLEWUV5LisTZyREWSxYz"); diff --git a/src/tests/sql/init_version_2.sql b/src/tests/sql/init_version_2.sql deleted file mode 100644 index 133284ec..00000000 --- a/src/tests/sql/init_version_2.sql +++ /dev/null @@ -1 +0,0 @@ -INSERT INTO `inventory` VALUES ('hash', 1, 1,1, 1,'test'); diff --git a/src/tests/sql/init_version_3.sql b/src/tests/sql/init_version_3.sql deleted file mode 100644 index 875d859d..00000000 --- a/src/tests/sql/init_version_3.sql +++ /dev/null @@ -1 +0,0 @@ -INSERT INTO `settings` VALUES ('version','3'); diff --git a/src/tests/sql/init_version_4.sql b/src/tests/sql/init_version_4.sql deleted file mode 100644 index ea3f1768..00000000 --- a/src/tests/sql/init_version_4.sql +++ /dev/null @@ -1 +0,0 @@ -INSERT INTO `pubkeys` VALUES ('hash', 1, 1, 1,'test'); diff --git a/src/tests/sql/init_version_5.sql b/src/tests/sql/init_version_5.sql deleted file mode 100644 index b894c038..00000000 --- a/src/tests/sql/init_version_5.sql +++ /dev/null @@ -1 +0,0 @@ -INSERT INTO `objectprocessorqueue` VALUES ('hash', 1); diff --git a/src/tests/sql/init_version_6.sql b/src/tests/sql/init_version_6.sql deleted file mode 100644 index 7cd30571..00000000 --- a/src/tests/sql/init_version_6.sql +++ /dev/null @@ -1 +0,0 @@ -INSERT INTO `inventory` VALUES ('hash', 1, 1, 1,'test','test'); diff --git a/src/tests/sql/init_version_7.sql b/src/tests/sql/init_version_7.sql deleted file mode 100644 index bd87f8d8..00000000 --- a/src/tests/sql/init_version_7.sql +++ /dev/null @@ -1,3 +0,0 @@ -INSERT INTO `sent` VALUES -(1,'BM-2cWzMnxjJ7yRP3nLEWUV5LisTZyREWSxYz',1,'BM-2cWzSnwjJ7yRP3nLEWUV5LisTZyREWSzUK','Test1 subject','message test 1','ackdata',1638176409,1638176409,1638176423,'msgqueued',1,'testfolder',1,2), -(2,'BM-2cWzMnxjJ7yRP3nLEWUV5LisTZyREWSxYz',1,'BM-2cWzSnwjJ7yRP3nLEWUV5LisTZyREWSzUK','Test2 subject','message test 2','ackdata',1638176423,1638176423,1638176423,'msgqueued',1,'testfolder',1,2); diff --git a/src/tests/sql/init_version_8.sql b/src/tests/sql/init_version_8.sql deleted file mode 100644 index 9d9b6f3a..00000000 --- a/src/tests/sql/init_version_8.sql +++ /dev/null @@ -1 +0,0 @@ -INSERT INTO `inbox` VALUES (1, "poland", "malasia", "test", "yes", "test message", "folder", 1, 1, 1); diff --git a/src/tests/sql/init_version_9.sql b/src/tests/sql/init_version_9.sql deleted file mode 100644 index 764634d2..00000000 --- a/src/tests/sql/init_version_9.sql +++ /dev/null @@ -1,2 +0,0 @@ -INSERT INTO `sent` VALUES -(1,'BM-2cWzMnxjJ7yRP3nLEWUV5LisTZyREWSxYz',1,'BM-2cWzSnwjJ7yRP3nLEWUV5LisTZyREWSzUK','Test1 subject','message test 1','ackdata',1638176409,1638176409,1638176423,'msgqueued',1,'testfolder',1,2); diff --git a/src/tests/test_addresses.py b/src/tests/test_addresses.py deleted file mode 100644 index 8f9c283d..00000000 --- a/src/tests/test_addresses.py +++ /dev/null @@ -1,61 +0,0 @@ - -import unittest -from binascii import unhexlify - -from pybitmessage import addresses - -from .samples import ( - sample_address, sample_daddr3_512, sample_daddr4_512, - sample_deterministic_addr4, sample_deterministic_addr3, - sample_deterministic_ripe, sample_ripe) - -sample_addr3 = sample_deterministic_addr3.split('-')[1] -sample_addr4 = sample_deterministic_addr4.split('-')[1] - - -class TestAddresses(unittest.TestCase): - """Test addresses manipulations""" - - def test_decode(self): - """Decode some well known addresses and check the result""" - self.assertEqual( - addresses.decodeAddress(sample_address), - ('success', 2, 1, unhexlify(sample_ripe))) - - status, version, stream, ripe1 = addresses.decodeAddress( - sample_deterministic_addr4) - self.assertEqual(status, 'success') - self.assertEqual(stream, 1) - self.assertEqual(version, 4) - status, version, stream, ripe2 = addresses.decodeAddress(sample_addr3) - self.assertEqual(status, 'success') - self.assertEqual(stream, 1) - self.assertEqual(version, 3) - self.assertEqual(ripe1, ripe2) - self.assertEqual(ripe1, unhexlify(sample_deterministic_ripe)) - - def test_encode(self): - """Encode sample ripe and compare the result to sample address""" - self.assertEqual( - sample_address, - addresses.encodeAddress(2, 1, unhexlify(sample_ripe))) - ripe = unhexlify(sample_deterministic_ripe) - self.assertEqual( - addresses.encodeAddress(3, 1, ripe), - 'BM-%s' % addresses.encodeBase58(sample_daddr3_512)) - - def test_base58(self): - """Check Base58 encoding and decoding""" - self.assertEqual(addresses.decodeBase58('1'), 0) - self.assertEqual(addresses.decodeBase58('!'), 0) - self.assertEqual( - addresses.decodeBase58(sample_addr4), sample_daddr4_512) - self.assertEqual( - addresses.decodeBase58(sample_addr3), sample_daddr3_512) - - self.assertEqual(addresses.encodeBase58(0), '1') - self.assertEqual(addresses.encodeBase58(-1), None) - self.assertEqual( - sample_addr4, addresses.encodeBase58(sample_daddr4_512)) - self.assertEqual( - sample_addr3, addresses.encodeBase58(sample_daddr3_512)) diff --git a/src/tests/test_addressgenerator.py b/src/tests/test_addressgenerator.py deleted file mode 100644 index 236b4d8b..00000000 --- a/src/tests/test_addressgenerator.py +++ /dev/null @@ -1,87 +0,0 @@ -"""Tests for AddressGenerator (with thread or not)""" - -from binascii import unhexlify - -from six.moves import queue - -from .partial import TestPartialRun -from .samples import ( - sample_seed, sample_deterministic_addr3, sample_deterministic_addr4, - sample_deterministic_ripe) - -TEST_LABEL = 'test' - - -class TestAddressGenerator(TestPartialRun): - """Test case for AddressGenerator thread""" - - @classmethod - def setUpClass(cls): - super(TestAddressGenerator, cls).setUpClass() - - import defaults - import queues - from class_addressGenerator import addressGenerator - - cls.state.enableGUI = False - - cls.command_queue = queues.addressGeneratorQueue - cls.return_queue = queues.apiAddressGeneratorReturnQueue - cls.worker_queue = queues.workerQueue - - cls.config.set( - 'bitmessagesettings', 'defaultnoncetrialsperbyte', - str(defaults.networkDefaultProofOfWorkNonceTrialsPerByte)) - cls.config.set( - 'bitmessagesettings', 'defaultpayloadlengthextrabytes', - str(defaults.networkDefaultPayloadLengthExtraBytes)) - - thread = addressGenerator() - thread.daemon = True - thread.start() - - def _execute(self, command, *args): - self.command_queue.put((command,) + args) - try: - return self.return_queue.get(timeout=30)[0] - except (IndexError, queue.Empty): - self.fail('Failed to execute command %s' % command) - - def test_deterministic(self): - """Test deterministic commands""" - self.command_queue.put(( - 'getDeterministicAddress', 3, 1, - TEST_LABEL, 1, sample_seed, False)) - self.assertEqual(sample_deterministic_addr3, self.return_queue.get()) - - self.assertEqual( - sample_deterministic_addr3, - self._execute( - 'createDeterministicAddresses', 3, 1, TEST_LABEL, 2, - sample_seed, False, 0, 0)) - - try: - self.assertEqual( - self.worker_queue.get(timeout=30), - ('sendOutOrStoreMyV3Pubkey', - unhexlify(sample_deterministic_ripe))) - - self.worker_queue.get(timeout=30) # get the next addr's task - except queue.Empty: - self.fail('No commands in the worker queue') - - self.assertEqual( - sample_deterministic_addr4, - self._execute('createChan', 4, 1, TEST_LABEL, sample_seed, True)) - try: - self.assertEqual( - self.worker_queue.get(), - ('sendOutOrStoreMyV4Pubkey', sample_deterministic_addr4)) - except queue.Empty: - self.fail('No commands in the worker queue') - self.assertEqual( - self.config.get(sample_deterministic_addr4, 'label'), TEST_LABEL) - self.assertTrue( - self.config.getboolean(sample_deterministic_addr4, 'chan')) - self.assertTrue( - self.config.getboolean(sample_deterministic_addr4, 'enabled')) diff --git a/src/tests/test_api.py b/src/tests/test_api.py deleted file mode 100644 index 2a4640fa..00000000 --- a/src/tests/test_api.py +++ /dev/null @@ -1,441 +0,0 @@ -""" -Tests using API. -""" - -import base64 -import json -import time - -from binascii import hexlify -from six.moves import xmlrpc_client # nosec - -import psutil - -from .samples import ( - sample_deterministic_addr3, sample_deterministic_addr4, sample_seed, - sample_inbox_msg_ids, - sample_subscription_addresses, sample_subscription_name -) - -from .test_process import TestProcessProto - - -class TestAPIProto(TestProcessProto): - """Test case logic for testing API""" - _process_cmd = ['pybitmessage', '-t'] - - @classmethod - def setUpClass(cls): - """Setup XMLRPC proxy for pybitmessage API""" - super(TestAPIProto, cls).setUpClass() - cls.addresses = [] - cls.api = xmlrpc_client.ServerProxy( - "http://username:password@127.0.0.1:8442/") - for _ in range(5): - if cls._get_readline('.api_started'): - return - time.sleep(1) - - -class TestAPIShutdown(TestAPIProto): - """Separate test case for API command 'shutdown'""" - def test_shutdown(self): - """Shutdown the pybitmessage""" - self.assertEqual(self.api.shutdown(), 'done') - try: - self.process.wait(20) - except psutil.TimeoutExpired: - self.fail( - '%s has not stopped in 20 sec' % ' '.join(self._process_cmd)) - - -# TODO: uncovered API commands -# disseminatePreEncryptedMsg -# disseminatePubkey -# getMessageDataByDestinationHash - - -class TestAPI(TestAPIProto): - """Main API test case""" - _seed = base64.encodestring(sample_seed) - - def _add_random_address(self, label): - addr = self.api.createRandomAddress(base64.encodestring(label)) - return addr - - def test_user_password(self): - """Trying to connect with wrong username/password""" - api_wrong = xmlrpc_client.ServerProxy( - "http://test:wrong@127.0.0.1:8442/") - with self.assertRaises(xmlrpc_client.ProtocolError): - api_wrong.clientStatus() - - def test_connection(self): - """API command 'helloWorld'""" - self.assertEqual( - self.api.helloWorld('hello', 'world'), - 'hello-world' - ) - - def test_arithmetic(self): - """API command 'add'""" - self.assertEqual(self.api.add(69, 42), 111) - - def test_invalid_method(self): - """Issuing nonexistent command 'test'""" - self.assertEqual( - self.api.test(), - 'API Error 0020: Invalid method: test' - ) - - def test_message_inbox(self): - """Test message inbox methods""" - self.assertEqual( - len(json.loads( - self.api.getAllInboxMessages())["inboxMessages"]), - 4, - # Custom AssertError message for details - json.loads(self.api.getAllInboxMessages())["inboxMessages"] - ) - self.assertEqual( - len(json.loads( - self.api.getAllInboxMessageIds())["inboxMessageIds"]), - 4 - ) - self.assertEqual( - len(json.loads( - self.api.getInboxMessageById( - hexlify(sample_inbox_msg_ids[2])))["inboxMessage"]), - 1 - ) - self.assertEqual( - len(json.loads( - self.api.getInboxMessagesByReceiver( - sample_deterministic_addr4))["inboxMessages"]), - 4 - ) - - def test_message_trash(self): - """Test message inbox methods""" - - messages_before_delete = len( - json.loads(self.api.getAllInboxMessageIds())["inboxMessageIds"]) - for msgid in sample_inbox_msg_ids[:2]: - self.assertEqual( - self.api.trashMessage(hexlify(msgid)), - 'Trashed message (assuming message existed).' - ) - self.assertEqual(len( - json.loads(self.api.getAllInboxMessageIds())["inboxMessageIds"] - ), messages_before_delete - 2) - for msgid in sample_inbox_msg_ids[:2]: - self.assertEqual( - self.api.undeleteMessage(hexlify(msgid)), - 'Undeleted message' - ) - self.assertEqual(len( - json.loads(self.api.getAllInboxMessageIds())["inboxMessageIds"] - ), messages_before_delete) - - def test_clientstatus_consistency(self): - """If networkStatus is notConnected networkConnections should be 0""" - status = json.loads(self.api.clientStatus()) - if status["networkStatus"] == "notConnected": - self.assertEqual(status["networkConnections"], 0) - else: - self.assertGreater(status["networkConnections"], 0) - - def test_listconnections_consistency(self): - """Checking the return of API command 'listConnections'""" - result = json.loads(self.api.listConnections()) - self.assertGreaterEqual(len(result["inbound"]), 0) - self.assertGreaterEqual(len(result["outbound"]), 0) - - def test_list_addresses(self): - """Checking the return of API command 'listAddresses'""" - self.assertEqual( - json.loads(self.api.listAddresses()).get('addresses'), - self.addresses - ) - - def test_decode_address(self): - """Checking the return of API command 'decodeAddress'""" - result = json.loads( - self.api.decodeAddress(sample_deterministic_addr4)) - self.assertEqual(result.get('status'), 'success') - self.assertEqual(result['addressVersion'], 4) - self.assertEqual(result['streamNumber'], 1) - - def test_create_deterministic_addresses(self): - """Test creation of deterministic addresses""" - self.assertEqual( - self.api.getDeterministicAddress(self._seed, 4, 1), - sample_deterministic_addr4) - self.assertEqual( - self.api.getDeterministicAddress(self._seed, 3, 1), - sample_deterministic_addr3) - self.assertRegexpMatches( - self.api.getDeterministicAddress(self._seed, 2, 1), - r'^API Error 0002:') - - # This is here until the streams will be implemented - self.assertRegexpMatches( - self.api.getDeterministicAddress(self._seed, 3, 2), - r'API Error 0003:') - self.assertRegexpMatches( - self.api.createDeterministicAddresses(self._seed, 1, 4, 2), - r'API Error 0003:') - - self.assertRegexpMatches( - self.api.createDeterministicAddresses('', 1), - r'API Error 0001:') - self.assertRegexpMatches( - self.api.createDeterministicAddresses(self._seed, 1, 2), - r'API Error 0002:') - self.assertRegexpMatches( - self.api.createDeterministicAddresses(self._seed, 0), - r'API Error 0004:') - self.assertRegexpMatches( - self.api.createDeterministicAddresses(self._seed, 1000), - r'API Error 0005:') - - addresses = json.loads( - self.api.createDeterministicAddresses(self._seed, 2, 4) - )['addresses'] - self.assertEqual(len(addresses), 2) - self.assertEqual(addresses[0], sample_deterministic_addr4) - for addr in addresses: - self.assertEqual(self.api.deleteAddress(addr), 'success') - - def test_create_random_address(self): - """API command 'createRandomAddress': basic BM-address validation""" - addr = self._add_random_address('random_1') - self.assertRegexpMatches(addr, r'^BM-') - self.assertRegexpMatches(addr[3:], r'[a-zA-Z1-9]+$') - # Whitepaper says "around 36 character" - self.assertLessEqual(len(addr[3:]), 40) - self.assertEqual(self.api.deleteAddress(addr), 'success') - - def test_addressbook(self): - """Testing API commands for addressbook manipulations""" - # Initially it's empty - self.assertEqual( - json.loads(self.api.listAddressBookEntries()).get('addresses'), - [] - ) - # Add known address - self.api.addAddressBookEntry( - sample_deterministic_addr4, - base64.encodestring('tiger_4') - ) - # Check addressbook entry - entries = json.loads( - self.api.listAddressBookEntries()).get('addresses')[0] - self.assertEqual( - entries['address'], sample_deterministic_addr4) - self.assertEqual( - base64.decodestring(entries['label']), 'tiger_4') - # Try sending to this address (#1898) - addr = self._add_random_address('random_2') - # TODO: it was never deleted - msg = base64.encodestring('test message') - msg_subject = base64.encodestring('test_subject') - result = self.api.sendMessage( - sample_deterministic_addr4, addr, msg_subject, msg) - self.assertNotRegexpMatches(result, r'^API Error') - self.api.deleteAddress(addr) - # Remove known address - self.api.deleteAddressBookEntry(sample_deterministic_addr4) - # Addressbook should be empty again - self.assertEqual( - json.loads(self.api.listAddressBookEntries()).get('addresses'), - [] - ) - - def test_subscriptions(self): - """Testing the API commands related to subscriptions""" - - self.assertEqual( - self.api.addSubscription( - sample_subscription_addresses[0], - sample_subscription_name.encode('base64')), - 'Added subscription.' - ) - - added_subscription = {'label': None, 'enabled': False} - # check_address - for sub in json.loads(self.api.listSubscriptions())['subscriptions']: - # special address, added when sqlThread starts - if sub['address'] == sample_subscription_addresses[0]: - added_subscription = sub - self.assertEqual( - base64.decodestring(sub['label']), sample_subscription_name - ) - self.assertTrue(sub['enabled']) - break - - self.assertEqual( - base64.decodestring(added_subscription['label']) - if added_subscription['label'] else None, - sample_subscription_name) - self.assertTrue(added_subscription['enabled']) - - for s in json.loads(self.api.listSubscriptions())['subscriptions']: - # special address, added when sqlThread starts - if s['address'] == sample_subscription_addresses[1]: - self.assertEqual( - base64.decodestring(s['label']), - 'Bitmessage new releases/announcements') - self.assertTrue(s['enabled']) - break - else: - self.fail( - 'Could not find Bitmessage new releases/announcements' - ' in subscriptions') - self.assertEqual( - self.api.deleteSubscription(sample_subscription_addresses[0]), - 'Deleted subscription if it existed.') - self.assertEqual( - self.api.deleteSubscription(sample_subscription_addresses[1]), - 'Deleted subscription if it existed.') - self.assertEqual( - json.loads(self.api.listSubscriptions())['subscriptions'], []) - - def test_send(self): - """Test message sending""" - addr = self._add_random_address('random_2') - msg = base64.encodestring('test message') - msg_subject = base64.encodestring('test_subject') - ackdata = self.api.sendMessage( - sample_deterministic_addr4, addr, msg_subject, msg) - try: - # Check ackdata and message status - int(ackdata, 16) - status = self.api.getStatus(ackdata) - if status == 'notfound': - raise KeyError - self.assertIn( - status, ( - 'msgqueued', 'awaitingpubkey', 'msgsent', 'ackreceived', - 'doingpubkeypow', 'doingmsgpow', 'msgsentnoackexpected' - )) - # Find the message in sent - for m in json.loads( - self.api.getSentMessagesByAddress(addr))['sentMessages']: - if m['ackData'] == ackdata: - sent_msg = m['message'] - break - else: - raise KeyError - except ValueError: - self.fail('sendMessage returned error or ackData is not hex') - except KeyError: - self.fail('Could not find sent message in sent messages') - else: - # Check found message - try: - self.assertEqual(sent_msg, msg.strip()) - except UnboundLocalError: - self.fail('Could not find sent message in sent messages') - # self.assertEqual(inbox_msg, msg.strip()) - self.assertEqual(json.loads( - self.api.getSentMessageByAckData(ackdata) - )['sentMessage'][0]['message'], sent_msg) - # Trash the message - self.assertEqual( - self.api.trashSentMessageByAckData(ackdata), - 'Trashed sent message (assuming message existed).') - # Empty trash - self.assertEqual(self.api.deleteAndVacuum(), 'done') - # The message should disappear - self.assertIsNone(json.loads( - self.api.getSentMessageByAckData(ackdata))) - finally: - self.assertEqual(self.api.deleteAddress(addr), 'success') - - def test_send_broadcast(self): - """Test broadcast sending""" - addr = self._add_random_address('random_2') - msg = base64.encodestring('test broadcast') - ackdata = self.api.sendBroadcast( - addr, base64.encodestring('test_subject'), msg) - - try: - int(ackdata, 16) - status = self.api.getStatus(ackdata) - if status == 'notfound': - raise KeyError - self.assertIn(status, ( - 'doingbroadcastpow', 'broadcastqueued', 'broadcastsent')) - - start = time.time() - while status != 'broadcastsent': - spent = int(time.time() - start) - if spent > 30: - self.fail('PoW is taking too much time: %ss' % spent) - time.sleep(1) # wait for PoW to get final msgid on next step - status = self.api.getStatus(ackdata) - - # Find the message and its ID in sent - for m in json.loads(self.api.getAllSentMessages())['sentMessages']: - if m['ackData'] == ackdata: - sent_msg = m['message'] - sent_msgid = m['msgid'] - break - else: - raise KeyError - except ValueError: - self.fail('sendBroadcast returned error or ackData is not hex') - except KeyError: - self.fail('Could not find sent broadcast in sent messages') - else: - # Check found message and its ID - try: - self.assertEqual(sent_msg, msg.strip()) - except UnboundLocalError: - self.fail('Could not find sent message in sent messages') - self.assertEqual(json.loads( - self.api.getSentMessageById(sent_msgid) - )['sentMessage'][0]['message'], sent_msg) - self.assertIn( - {'msgid': sent_msgid}, json.loads( - self.api.getAllSentMessageIds())['sentMessageIds']) - # Trash the message by ID - self.assertEqual( - self.api.trashSentMessage(sent_msgid), - 'Trashed sent message (assuming message existed).') - self.assertEqual(self.api.deleteAndVacuum(), 'done') - self.assertIsNone(json.loads( - self.api.getSentMessageById(sent_msgid))) - # Try sending from disabled address - self.assertEqual(self.api.enableAddress(addr, False), 'success') - result = self.api.sendBroadcast( - addr, base64.encodestring('test_subject'), msg) - self.assertRegexpMatches(result, r'^API Error 0014:') - finally: - self.assertEqual(self.api.deleteAddress(addr), 'success') - - # sending from an address without private key - # (Bitmessage new releases/announcements) - result = self.api.sendBroadcast( - 'BM-GtovgYdgs7qXPkoYaRgrLFuFKz1SFpsw', - base64.encodestring('test_subject'), msg) - self.assertRegexpMatches(result, r'^API Error 0013:') - - def test_chan(self): - """Testing chan creation/joining""" - # Create chan with known address - self.assertEqual( - self.api.createChan(self._seed), sample_deterministic_addr4) - # cleanup - self.assertEqual( - self.api.leaveChan(sample_deterministic_addr4), 'success') - # Join chan with addresses of version 3 or 4 - for addr in (sample_deterministic_addr4, sample_deterministic_addr3): - self.assertEqual(self.api.joinChan(self._seed, addr), 'success') - self.assertEqual(self.api.leaveChan(addr), 'success') - # Joining with wrong address should fail - self.assertRegexpMatches( - self.api.joinChan(self._seed, 'BM-2cWzSnwjJ7yRP3nLEW'), - r'^API Error 0008:' - ) diff --git a/src/tests/test_api_thread.py b/src/tests/test_api_thread.py deleted file mode 100644 index 5b004066..00000000 --- a/src/tests/test_api_thread.py +++ /dev/null @@ -1,94 +0,0 @@ -"""TestAPIThread class definition""" - -import sys -import time -from binascii import hexlify, unhexlify -from struct import pack - -from six.moves import queue, xmlrpc_client - -from pybitmessage import protocol -from pybitmessage.defaults import ( - networkDefaultProofOfWorkNonceTrialsPerByte, - networkDefaultPayloadLengthExtraBytes) - -from .partial import TestPartialRun -from .samples import sample_statusbar_msg, sample_object_data - - -class TestAPIThread(TestPartialRun): - """Test case running the API thread""" - - @classmethod - def setUpClass(cls): - super(TestAPIThread, cls).setUpClass() - - import helper_sql - import queues - - # pylint: disable=too-few-public-methods - class SqlReadyMock(object): - """Mock helper_sql.sql_ready event with dummy class""" - @staticmethod - def wait(): - """Don't wait, return immediately""" - return - - helper_sql.sql_ready = SqlReadyMock - cls.queues = queues - - cls.config.set('bitmessagesettings', 'apiusername', 'username') - cls.config.set('bitmessagesettings', 'apipassword', 'password') - cls.config.set('inventory', 'storage', 'filesystem') - - import api - cls.thread = api.singleAPI() - cls.thread.daemon = True - cls.thread.start() - time.sleep(3) - cls.api = xmlrpc_client.ServerProxy( - "http://username:password@127.0.0.1:8442/") - - def test_connection(self): - """API command 'helloWorld'""" - self.assertEqual( - self.api.helloWorld('hello', 'world'), 'hello-world') - - def test_statusbar(self): - """Check UISignalQueue after issuing the 'statusBar' command""" - self.queues.UISignalQueue.queue.clear() - self.assertEqual( - self.api.statusBar(sample_statusbar_msg), 'success') - try: - cmd, data = self.queues.UISignalQueue.get(block=False) - except queue.Empty: - self.fail('UISignalQueue is empty!') - - self.assertEqual(cmd, 'updateStatusBar') - self.assertEqual(data, sample_statusbar_msg) - - def test_client_status(self): - """Ensure the reply of clientStatus corresponds to mock""" - status = self.api.clientStatus() - if sys.hexversion >= 0x3000000: - self.assertEqual(status["networkConnections"], 4) - self.assertEqual(status["pendingDownload"], 0) - - def test_disseminate_preencrypted(self): - """Call disseminatePreEncryptedMsg API command and check inventory""" - import proofofwork - from inventory import Inventory - - proofofwork.init() - update_object = pack( - '>Q', int(time.time() + 7200)) + sample_object_data[16:] - invhash = unhexlify(self.api.disseminatePreEncryptedMsg( - hexlify(update_object).decode(), - networkDefaultProofOfWorkNonceTrialsPerByte, - networkDefaultPayloadLengthExtraBytes - )) - obj_type, obj_stream, obj_data = Inventory()[invhash][:3] - self.assertEqual(obj_type, 42) - self.assertEqual(obj_stream, 2) - self.assertEqual(sample_object_data[16:], obj_data[16:]) - self.assertTrue(protocol.isProofOfWorkSufficient(obj_data)) diff --git a/src/tests/test_config.py b/src/tests/test_config.py deleted file mode 100644 index 44db7c8a..00000000 --- a/src/tests/test_config.py +++ /dev/null @@ -1,135 +0,0 @@ -""" -Various tests for config -""" -import unittest - -from six import StringIO -from pybitmessage.bmconfigparser import BMConfigParser - -test_config = """[bitmessagesettings] -maxaddrperstreamsend = 100 -maxbootstrapconnections = 10 -maxdownloadrate = 0 -maxoutboundconnections = 8 -maxtotalconnections = 100 -maxuploadrate = 0 -apiinterface = 127.0.0.1 -apiport = 8442 -udp = True - -[threads] -receive = 3 - -[network] -bind = None -dandelion = 90 - -[inventory] -storage = sqlite -acceptmismatch = False - -[knownnodes] -maxnodes = 15000 - -[zlib] -maxsize = 1048576""" - - -# pylint: disable=protected-access -class TestConfig(unittest.TestCase): - """A test case for bmconfigparser""" - - def setUp(self): - self.config = BMConfigParser() - self.config.add_section('bitmessagesettings') - - def test_safeGet(self): - """safeGet retuns provided default for nonexistent option or None""" - self.assertIs( - self.config.safeGet('nonexistent', 'nonexistent'), None) - self.assertEqual( - self.config.safeGet('nonexistent', 'nonexistent', 42), 42) - - def test_safeGetBoolean(self): - """safeGetBoolean returns False for nonexistent option, no default""" - self.assertIs( - self.config.safeGetBoolean('nonexistent', 'nonexistent'), False) - # no arg for default - # pylint: disable=too-many-function-args - with self.assertRaises(TypeError): - self.config.safeGetBoolean('nonexistent', 'nonexistent', True) - - def test_safeGetInt(self): - """safeGetInt retuns provided default for nonexistent option or 0""" - self.assertEqual( - self.config.safeGetInt('nonexistent', 'nonexistent'), 0) - self.assertEqual( - self.config.safeGetInt('nonexistent', 'nonexistent', 42), 42) - - def test_safeGetFloat(self): - """ - safeGetFloat retuns provided default for nonexistent option or 0.0 - """ - self.assertEqual( - self.config.safeGetFloat('nonexistent', 'nonexistent'), 0.0) - self.assertEqual( - self.config.safeGetFloat('nonexistent', 'nonexistent', 42.0), 42.0) - - def test_set(self): - """Check exceptions in set()""" - with self.assertRaises(TypeError): - self.config.set('bitmessagesettings', 'any', 42) - with self.assertRaises(ValueError): - self.config.set( - 'bitmessagesettings', 'maxoutboundconnections', 'none') - with self.assertRaises(ValueError): - self.config.set( - 'bitmessagesettings', 'maxoutboundconnections', '9') - - def test_validate(self): - """Check validation""" - self.assertTrue( - self.config.validate('nonexistent', 'nonexistent', 'any')) - for val in range(9): - self.assertTrue(self.config.validate( - 'bitmessagesettings', 'maxoutboundconnections', str(val))) - - def test_setTemp(self): - """Set a temporary value and ensure it's returned by get()""" - self.config.setTemp('bitmessagesettings', 'connect', 'true') - self.assertIs( - self.config.safeGetBoolean('bitmessagesettings', 'connect'), True) - written_fp = StringIO('') - self.config.write(written_fp) - self.config._reset() - self.config.read_file(written_fp) - self.assertIs( - self.config.safeGetBoolean('bitmessagesettings', 'connect'), False) - - def test_addresses(self): - """Check the addresses() method""" - self.config.read() - for num in range(1, 4): - addr = 'BM-%s' % num - self.config.add_section(addr) - self.config.set(addr, 'label', 'account %s' % (4 - num)) - self.assertEqual(self.config.addresses(), ['BM-1', 'BM-2', 'BM-3']) - self.assertEqual(self.config.addresses(True), ['BM-3', 'BM-2', 'BM-1']) - - def test_reset(self): - """Some logic for testing _reset()""" - test_config_object = StringIO(test_config) - self.config.read_file(test_config_object) - self.assertEqual( - self.config.safeGetInt( - 'bitmessagesettings', 'maxaddrperstreamsend'), 100) - self.config._reset() - self.assertEqual(self.config.sections(), []) - - def test_defaults(self): - """Loading defaults""" - self.config.set('bitmessagesettings', 'maxaddrperstreamsend', '100') - self.config.read() - self.assertEqual( - self.config.safeGetInt( - 'bitmessagesettings', 'maxaddrperstreamsend'), 500) diff --git a/src/tests/test_config_address.py b/src/tests/test_config_address.py deleted file mode 100644 index b76df7ec..00000000 --- a/src/tests/test_config_address.py +++ /dev/null @@ -1,57 +0,0 @@ -""" -Various tests to Enable and Disable the identity -""" - -import unittest -from six import StringIO -from six.moves import configparser -from pybitmessage.bmconfigparser import BMConfigParser - - -address_obj = """[BM-enabled_identity] -label = Test_address_1 -enabled = true - -[BM-disabled_identity] -label = Test_address_2 -enabled = false -""" - - -# pylint: disable=protected-access -class TestAddressEnableDisable(unittest.TestCase): - """A test case for bmconfigparser""" - - def setUp(self): - self.config = BMConfigParser() - self.config.read_file(StringIO(address_obj)) - - def test_enable_enabled_identity(self): - """Test enabling already enabled identity""" - self.config.enable_address('BM-enabled_identity') - self.assertEqual(self.config.safeGet('BM-enabled_identity', 'enabled'), 'true') - - def test_enable_disabled_identity(self): - """Test enabling the Disabled identity""" - self.config.enable_address('BM-disabled_identity') - self.assertEqual(self.config.safeGet('BM-disabled_identity', 'enabled'), 'true') - - def test_enable_non_existent_identity(self): - """Test enable non-existent address""" - with self.assertRaises(configparser.NoSectionError): - self.config.enable_address('non_existent_address') - - def test_disable_disabled_identity(self): - """Test disabling already disabled identity""" - self.config.disable_address('BM-disabled_identity') - self.assertEqual(self.config.safeGet('BM-disabled_identity', 'enabled'), 'false') - - def test_disable_enabled_identity(self): - """Test Disabling the Enabled identity""" - self.config.disable_address('BM-enabled_identity') - self.assertEqual(self.config.safeGet('BM-enabled_identity', 'enabled'), 'false') - - def test_disable_non_existent_identity(self): - """Test dsiable non-existent address""" - with self.assertRaises(configparser.NoSectionError): - self.config.disable_address('non_existent_address') diff --git a/src/tests/test_config_process.py b/src/tests/test_config_process.py deleted file mode 100644 index 9322a2f0..00000000 --- a/src/tests/test_config_process.py +++ /dev/null @@ -1,39 +0,0 @@ -""" -Various tests for config -""" - -import os -import tempfile -from pybitmessage.bmconfigparser import config -from .test_process import TestProcessProto -from .common import skip_python3 - -skip_python3() - - -class TestProcessConfig(TestProcessProto): - """A test case for keys.dat""" - home = tempfile.mkdtemp() - - def test_config_defaults(self): - """Test settings in the generated config""" - self._stop_process() - self._kill_process() - config.read(os.path.join(self.home, 'keys.dat')) - - self.assertEqual(config.safeGetInt( - 'bitmessagesettings', 'settingsversion'), 10) - self.assertEqual(config.safeGetInt( - 'bitmessagesettings', 'port'), 8444) - # don't connect - self.assertTrue(config.safeGetBoolean( - 'bitmessagesettings', 'dontconnect')) - # API disabled - self.assertFalse(config.safeGetBoolean( - 'bitmessagesettings', 'apienabled')) - - # extralowdifficulty is false - self.assertEqual(config.safeGetInt( - 'bitmessagesettings', 'defaultnoncetrialsperbyte'), 1000) - self.assertEqual(config.safeGetInt( - 'bitmessagesettings', 'defaultpayloadlengthextrabytes'), 1000) diff --git a/src/tests/test_crypto.py b/src/tests/test_crypto.py deleted file mode 100644 index b3f2484e..00000000 --- a/src/tests/test_crypto.py +++ /dev/null @@ -1,100 +0,0 @@ -""" -Test the alternatives for crypto primitives -""" - -import hashlib -import ssl -import unittest -from abc import ABCMeta, abstractmethod -from binascii import hexlify - -from pybitmessage import highlevelcrypto - - -try: - from Crypto.Hash import RIPEMD160 -except ImportError: - RIPEMD160 = None - -from .samples import ( - sample_msg, sample_pubsigningkey, sample_pubencryptionkey, - sample_privsigningkey, sample_privencryptionkey, sample_ripe, - sample_sig, sample_sig_sha1 -) - - -_sha = hashlib.new('sha512') -_sha.update(sample_pubsigningkey + sample_pubencryptionkey) - -pubkey_sha = _sha.digest() - - -class RIPEMD160TestCase(object): - """Base class for RIPEMD160 test case""" - # pylint: disable=too-few-public-methods,no-member - __metaclass__ = ABCMeta - - @abstractmethod - def _hashdigest(self, data): - """RIPEMD160 digest implementation""" - pass - - def test_hash_string(self): - """Check RIPEMD160 hash function on string""" - self.assertEqual(hexlify(self._hashdigest(pubkey_sha)), sample_ripe) - - -@unittest.skipIf( - ssl.OPENSSL_VERSION.startswith('OpenSSL 3'), 'no ripemd160 in openssl 3') -class TestHashlib(RIPEMD160TestCase, unittest.TestCase): - """RIPEMD160 test case for hashlib""" - @staticmethod - def _hashdigest(data): - hasher = hashlib.new('ripemd160') - hasher.update(data) - return hasher.digest() - - -@unittest.skipUnless(RIPEMD160, 'pycrypto package not found') -class TestCrypto(RIPEMD160TestCase, unittest.TestCase): - """RIPEMD160 test case for Crypto""" - @staticmethod - def _hashdigest(data): - return RIPEMD160.new(data).digest() - - -class TestHighlevelcrypto(unittest.TestCase): - """Test highlevelcrypto public functions""" - - def test_signatures(self): - """Verify sample signatures and newly generated ones""" - pubkey_hex = hexlify(sample_pubsigningkey) - # pregenerated signatures - self.assertTrue(highlevelcrypto.verify( - sample_msg, sample_sig, pubkey_hex, "sha256")) - self.assertFalse(highlevelcrypto.verify( - sample_msg, sample_sig, pubkey_hex, "sha1")) - self.assertTrue(highlevelcrypto.verify( - sample_msg, sample_sig_sha1, pubkey_hex, "sha1")) - self.assertTrue(highlevelcrypto.verify( - sample_msg, sample_sig_sha1, pubkey_hex)) - # new signatures - sig256 = highlevelcrypto.sign(sample_msg, sample_privsigningkey) - sig1 = highlevelcrypto.sign(sample_msg, sample_privsigningkey, "sha1") - self.assertTrue( - highlevelcrypto.verify(sample_msg, sig256, pubkey_hex)) - self.assertTrue( - highlevelcrypto.verify(sample_msg, sig256, pubkey_hex, "sha256")) - self.assertTrue( - highlevelcrypto.verify(sample_msg, sig1, pubkey_hex)) - - def test_privtopub(self): - """Generate public keys and check the result""" - self.assertEqual( - highlevelcrypto.privToPub(sample_privsigningkey), - hexlify(sample_pubsigningkey) - ) - self.assertEqual( - highlevelcrypto.privToPub(sample_privencryptionkey), - hexlify(sample_pubencryptionkey) - ) diff --git a/src/tests/test_helper_inbox.py b/src/tests/test_helper_inbox.py deleted file mode 100644 index a0b6de1b..00000000 --- a/src/tests/test_helper_inbox.py +++ /dev/null @@ -1,76 +0,0 @@ -"""Test cases for Helper Inbox""" - -import time -import unittest -from pybitmessage.helper_inbox import ( - insert, - trash, - delete, - isMessageAlreadyInInbox, - undeleteMessage, -) -from pybitmessage.helper_ackPayload import genAckPayload - -try: - # Python 3 - from unittest.mock import patch -except ImportError: - # Python 2 - from mock import patch - - -class TestHelperInbox(unittest.TestCase): - """Test class for Helper Inbox""" - - @patch("pybitmessage.helper_inbox.sqlExecute") - def test_insert(self, mock_sql_execute): # pylint: disable=no-self-use - """Test to perform an insert into the "inbox" table""" - mock_message_data = ( - "ruyv87bv", - "BM-2cUGaEcGz9Zft1SPAo8FJtfzyADTpEgU9U", - "BM-2cUGaEcGz9Zft1SPAo8FJtfzyADTp5g99U", - "Test subject", - int(time.time()), - "Test message", - "inbox", - 2, - 0, - "658gvjhtghv", - ) - insert(t=mock_message_data) - mock_sql_execute.assert_called_once() - - @patch("pybitmessage.helper_inbox.sqlExecute") - def test_trash(self, mock_sql_execute): # pylint: disable=no-self-use - """Test marking a message in the `inbox` as `trash`""" - mock_msg_id = "fefkosghsbse92" - trash(msgid=mock_msg_id) - mock_sql_execute.assert_called_once() - - @patch("pybitmessage.helper_inbox.sqlExecute") - def test_delete(self, mock_sql_execute): # pylint: disable=no-self-use - """Test for permanent deletion of message from trash""" - mock_ack_data = genAckPayload() - delete(mock_ack_data) - mock_sql_execute.assert_called_once() - - @patch("pybitmessage.helper_inbox.sqlExecute") - def test_undeleteMessage(self, mock_sql_execute): # pylint: disable=no-self-use - """Test for Undelete the message""" - mock_msg_id = "fefkosghsbse92" - undeleteMessage(msgid=mock_msg_id) - mock_sql_execute.assert_called_once() - - @patch("pybitmessage.helper_inbox.sqlQuery") - def test_isMessageAlreadyInInbox(self, mock_sql_query): - """Test for check for previous instances of this message""" - fake_sigHash = "h4dkn54546" - # if Message is already in Inbox - mock_sql_query.return_value = [(1,)] - result = isMessageAlreadyInInbox(sigHash=fake_sigHash) - self.assertTrue(result) - - # if Message is not in Inbox - mock_sql_query.return_value = [(0,)] - result = isMessageAlreadyInInbox(sigHash=fake_sigHash) - self.assertFalse(result) diff --git a/src/tests/test_helper_sent.py b/src/tests/test_helper_sent.py deleted file mode 100644 index 9227e43a..00000000 --- a/src/tests/test_helper_sent.py +++ /dev/null @@ -1,76 +0,0 @@ -"""Test cases for helper_sent class""" - -import unittest -from pybitmessage.helper_sent import insert, delete, trash, retrieve_message_details - -try: - # Python 3 - from unittest.mock import patch -except ImportError: - # Python 2 - from mock import patch - - -class TestHelperSent(unittest.TestCase): - """Test class for helper_sent""" - - @patch("pybitmessage.helper_sent.sqlExecute") - def test_insert_valid_address(self, mock_sql_execute): - """Test insert with valid address""" - VALID_ADDRESS = "BM-2cUGaEcGz9Zft1SPAo8FJtfzyADTpEgU9U" - ackdata = insert( - msgid="123456", - toAddress="[Broadcast subscribers]", - fromAddress=VALID_ADDRESS, - subject="Test Subject", - message="Test Message", - status="msgqueued", - sentTime=1234567890, - lastActionTime=1234567890, - sleeptill=0, - retryNumber=0, - encoding=2, - ttl=3600, - folder="sent", - ) - mock_sql_execute.assert_called_once() - self.assertIsNotNone(ackdata) - - def test_insert_invalid_address(self): - """Test insert with invalid address""" - INVALID_ADDRESS = "TEST@1245.780" - ackdata = insert(toAddress=INVALID_ADDRESS) - self.assertIsNone(ackdata) - - @patch("pybitmessage.helper_sent.sqlExecute") - def test_delete(self, mock_sql_execute): - """Test delete function""" - delete("ack_data") - self.assertTrue(mock_sql_execute.called) - mock_sql_execute.assert_called_once_with( - "DELETE FROM sent WHERE ackdata = ?", "ack_data" - ) - - @patch("pybitmessage.helper_sent.sqlQuery") - def test_retrieve_valid_message_details(self, mock_sql_query): - """Test retrieving valid message details""" - return_data = [ - ( - "to@example.com", - "from@example.com", - "Test Subject", - "Test Message", - "2022-01-01", - ) - ] - mock_sql_query.return_value = return_data - result = retrieve_message_details("12345") - self.assertEqual(result, return_data) - - @patch("pybitmessage.helper_sent.sqlExecute") - def test_trash(self, mock_sql_execute): - """Test marking a message as 'trash'""" - ackdata = "ack_data" - mock_sql_execute.return_value = 1 - rowcount = trash(ackdata) - self.assertEqual(rowcount, 1) diff --git a/src/tests/test_helper_sql.py b/src/tests/test_helper_sql.py deleted file mode 100644 index 036bd2c9..00000000 --- a/src/tests/test_helper_sql.py +++ /dev/null @@ -1,131 +0,0 @@ -"""Test cases for helper_sql""" - -import unittest - -try: - # Python 3 - from unittest.mock import patch -except ImportError: - # Python 2 - from mock import patch - -import pybitmessage.helper_sql as helper_sql - - -class TestHelperSql(unittest.TestCase): - """Test class for helper_sql""" - - @classmethod - def setUpClass(cls): - helper_sql.sql_available = True - - @patch("pybitmessage.helper_sql.sqlSubmitQueue.put") - @patch("pybitmessage.helper_sql.sqlReturnQueue.get") - def test_sqlquery_no_args(self, mock_sqlreturnqueue_get, mock_sqlsubmitqueue_put): - """Test sqlQuery with no additional arguments""" - mock_sqlreturnqueue_get.return_value = ("dummy_result", None) - result = helper_sql.sqlQuery( - "SELECT msgid FROM inbox where folder='inbox' ORDER BY received" - ) - self.assertEqual(mock_sqlsubmitqueue_put.call_count, 2) - self.assertEqual(result, "dummy_result") - - @patch("pybitmessage.helper_sql.sqlSubmitQueue.put") - @patch("pybitmessage.helper_sql.sqlReturnQueue.get") - def test_sqlquery_with_args(self, mock_sqlreturnqueue_get, mock_sqlsubmitqueue_put): - """Test sqlQuery with additional arguments""" - mock_sqlreturnqueue_get.return_value = ("dummy_result", None) - result = helper_sql.sqlQuery( - "SELECT address FROM addressbook WHERE address=?", "PB-5yfds868gbkj" - ) - self.assertEqual(mock_sqlsubmitqueue_put.call_count, 2) - self.assertEqual(result, "dummy_result") - - @patch("pybitmessage.helper_sql.sqlSubmitQueue.put") - @patch("pybitmessage.helper_sql.sqlReturnQueue.get") - def test_sqlexecute(self, mock_sqlreturnqueue_get, mock_sqlsubmitqueue_put): - """Test sqlExecute with valid arguments""" - mock_sqlreturnqueue_get.return_value = (None, 1) - rowcount = helper_sql.sqlExecute( - "UPDATE sent SET status = 'msgqueued'" - "WHERE ackdata = ? AND folder = 'sent'", - "1710652313", - ) - self.assertEqual(mock_sqlsubmitqueue_put.call_count, 3) - self.assertEqual(rowcount, 1) - - @patch("pybitmessage.helper_sql.SqlBulkExecute.execute") - def test_sqlexecute_script(self, mock_execute): - """Test sqlExecuteScript with a SQL script""" - helper_sql.sqlExecuteScript( - "CREATE TABLE test (id INTEGER); INSERT INTO test VALUES (1);" - ) - self.assertTrue(mock_execute.assert_called) - - @patch("pybitmessage.helper_sql.sqlSubmitQueue.put") - @patch( - "pybitmessage.helper_sql.sqlReturnQueue.get", - ) - def test_sqlexecute_chunked(self, mock_sqlreturnqueue_get, mock_sqlsubmitqueue_put): - """Test sqlExecuteChunked with valid arguments""" - # side_effect is list of return value (_, rowcount) - # of sqlReturnQueue.get for each chunk - CHUNK_COUNT = 6 - CHUNK_SIZE = 999 - ID_COUNT = CHUNK_COUNT * CHUNK_SIZE - CHUNKS_ROWCOUNT_LIST = [50, 29, 28, 18, 678, 900] - TOTAL_ROW_COUNT = sum(CHUNKS_ROWCOUNT_LIST) - mock_sqlreturnqueue_get.side_effect = [(None, rowcount) for rowcount in CHUNKS_ROWCOUNT_LIST] - args = [] - for i in range(0, ID_COUNT): - args.append("arg{}".format(i)) - total_row_count_return = helper_sql.sqlExecuteChunked( - "INSERT INTO table VALUES {}", ID_COUNT, *args - ) - self.assertEqual(TOTAL_ROW_COUNT, total_row_count_return) - self.assertTrue(mock_sqlsubmitqueue_put.called) - self.assertTrue(mock_sqlreturnqueue_get.called) - - @patch("pybitmessage.helper_sql.sqlSubmitQueue.put") - @patch("pybitmessage.helper_sql.sqlReturnQueue.get") - def test_sqlexecute_chunked_with_idcount_zero( - self, mock_sqlreturnqueue_get, mock_sqlsubmitqueue_put - ): - """Test sqlExecuteChunked with id count 0""" - ID_COUNT = 0 - args = list() - for i in range(0, ID_COUNT): - args.append("arg{}".format(i)) - total_row_count = helper_sql.sqlExecuteChunked( - "INSERT INTO table VALUES {}", ID_COUNT, *args - ) - self.assertEqual(total_row_count, 0) - self.assertFalse(mock_sqlsubmitqueue_put.called) - self.assertFalse(mock_sqlreturnqueue_get.called) - - @patch("pybitmessage.helper_sql.sqlSubmitQueue.put") - @patch("pybitmessage.helper_sql.sqlReturnQueue.get") - def test_sqlexecute_chunked_with_args_less( - self, mock_sqlreturnqueue_get, mock_sqlsubmitqueue_put - ): - """Test sqlExecuteChunked with length of args less than idcount""" - ID_COUNT = 12 - args = ["args0", "arg1"] - total_row_count = helper_sql.sqlExecuteChunked( - "INSERT INTO table VALUES {}", ID_COUNT, *args - ) - self.assertEqual(total_row_count, 0) - self.assertFalse(mock_sqlsubmitqueue_put.called) - self.assertFalse(mock_sqlreturnqueue_get.called) - - @patch("pybitmessage.helper_sql.sqlSubmitQueue.put") - @patch("pybitmessage.helper_sql.sqlSubmitQueue.task_done") - def test_sqlstored_procedure(self, mock_task_done, mock_sqlsubmitqueue_put): - """Test sqlStoredProcedure with a stored procedure name""" - helper_sql.sqlStoredProcedure("exit") - self.assertTrue(mock_task_done.called_once) - mock_sqlsubmitqueue_put.assert_called_with("terminate") - - @classmethod - def tearDownClass(cls): - helper_sql.sql_available = False diff --git a/src/tests/test_identicon.py b/src/tests/test_identicon.py deleted file mode 100644 index 4c6be32d..00000000 --- a/src/tests/test_identicon.py +++ /dev/null @@ -1,49 +0,0 @@ -"""Tests for qidenticon""" - -import atexit -import unittest - -try: - from PyQt5 import QtGui, QtWidgets - from xvfbwrapper import Xvfb - from pybitmessage import qidenticon -except ImportError: - Xvfb = None - # raise unittest.SkipTest( - # 'Skipping graphical test, because of no PyQt or xvfbwrapper') -else: - vdisplay = Xvfb(width=1024, height=768) - vdisplay.start() - atexit.register(vdisplay.stop) - - -sample_code = 0x3fd4bf901b9d4ea1394f0fb358725b28 -sample_size = 48 - - -@unittest.skipUnless( - Xvfb, 'Skipping graphical test, because of no PyQt or xvfbwrapper') -class TestIdenticon(unittest.TestCase): - """QIdenticon implementation test case""" - - @classmethod - def setUpClass(cls): - """Instantiate QtWidgets.QApplication""" - cls.app = QtWidgets.QApplication([]) - - def test_qidenticon_samples(self): - """Generate 4 qidenticon samples and check their properties""" - icon_simple = qidenticon.render_identicon(sample_code, sample_size) - self.assertIsInstance(icon_simple, QtGui.QPixmap) - self.assertEqual(icon_simple.height(), sample_size * 3) - self.assertEqual(icon_simple.width(), sample_size * 3) - self.assertFalse(icon_simple.hasAlphaChannel()) - - # icon_sample = QtGui.QPixmap() - # icon_sample.load('../images/qidenticon.png') - # self.assertFalse( - # icon_simple.toImage(), icon_sample.toImage()) - - icon_x = qidenticon.render_identicon( - sample_code, sample_size, opacity=0) - self.assertTrue(icon_x.hasAlphaChannel()) diff --git a/src/tests/test_inventory.py b/src/tests/test_inventory.py deleted file mode 100644 index b6d0cc85..00000000 --- a/src/tests/test_inventory.py +++ /dev/null @@ -1,58 +0,0 @@ -"""Tests for inventory""" - -import os -import shutil -import struct -import tempfile -import time -import unittest - -from pybitmessage.storage import storage -from pybitmessage.addresses import calculateInventoryHash - -from .partial import TestPartialRun - - -class TestFilesystemInventory(TestPartialRun): - """A test case for the inventory using filesystem backend""" - - @classmethod - def setUpClass(cls): - cls.home = os.environ['BITMESSAGE_HOME'] = tempfile.mkdtemp() - super(TestFilesystemInventory, cls).setUpClass() - - from inventory import create_inventory_instance - cls.inventory = create_inventory_instance('filesystem') - - def test_consistency(self): - """Ensure the inventory is of proper class""" - if os.path.isfile(os.path.join(self.home, 'messages.dat')): - # this will likely never happen - self.fail("Failed to configure filesystem inventory!") - - def test_appending(self): - """Add a sample message to the inventory""" - TTL = 24 * 60 * 60 - embedded_time = int(time.time() + TTL) - msg = struct.pack('>Q', embedded_time) + os.urandom(166) - invhash = calculateInventoryHash(msg) - self.inventory[invhash] = (2, 1, msg, embedded_time, b'') - - @classmethod - def tearDownClass(cls): - super(TestFilesystemInventory, cls).tearDownClass() - cls.inventory.flush() - shutil.rmtree(os.path.join(cls.home, cls.inventory.topDir)) - - -class TestStorageAbstract(unittest.TestCase): - """A test case for refactoring of the storage abstract classes""" - - def test_inventory_storage(self): - """Check inherited abstract methods""" - with self.assertRaisesRegexp( - TypeError, "^Can't instantiate abstract class.*" - "methods __contains__, __delitem__, __getitem__, __iter__," - " __len__, __setitem__" - ): # pylint: disable=abstract-class-instantiated - storage.InventoryStorage() diff --git a/src/tests/test_l10n.py b/src/tests/test_l10n.py deleted file mode 100644 index c6988827..00000000 --- a/src/tests/test_l10n.py +++ /dev/null @@ -1,24 +0,0 @@ -"""Tests for l10n module""" - -import re -import sys -import time -import unittest - -from pybitmessage import l10n - - -class TestL10n(unittest.TestCase): - """A test case for L10N""" - - def test_l10n_assumptions(self): - """Check the assumptions made while rewriting the l10n""" - self.assertFalse(re.search(r'\d', time.strftime("wrong"))) - timestring_type = type(time.strftime(l10n.DEFAULT_TIME_FORMAT)) - self.assertEqual(timestring_type, str) - if sys.version_info[0] == 2: - self.assertEqual(timestring_type, bytes) - - def test_getWindowsLocale(self): - """Check the getWindowsLocale() docstring example""" - self.assertEqual(l10n.getWindowsLocale("en_EN.UTF-8"), "english") diff --git a/src/tests/test_log.py b/src/tests/test_log.py deleted file mode 100644 index 4e74e50d..00000000 --- a/src/tests/test_log.py +++ /dev/null @@ -1,21 +0,0 @@ -"""Tests for logging""" - -import subprocess -import sys -import unittest - -from pybitmessage import proofofwork - - -class TestLog(unittest.TestCase): - """A test case for logging""" - - @unittest.skipIf( - sys.hexversion < 0x3000000, 'assertLogs is new in version 3.4') - def test_LogOutput(self): - """Use proofofwork.LogOutput to log output of a shell command""" - with self.assertLogs('default') as cm: # pylint: disable=no-member - with proofofwork.LogOutput('+'): - subprocess.call(['echo', 'HELLO']) - - self.assertEqual(cm.output, ['INFO:default:+: HELLO\n']) diff --git a/src/tests/test_logger.py b/src/tests/test_logger.py deleted file mode 100644 index d6bf33ed..00000000 --- a/src/tests/test_logger.py +++ /dev/null @@ -1,56 +0,0 @@ -""" -Testing the logger configuration -""" - -import os -import tempfile - -from .test_process import TestProcessProto - - -class TestLogger(TestProcessProto): - """A test case for logger configuration""" - - pattern = r' <===> ' - conf_template = ''' -[loggers] -keys=root - -[handlers] -keys=default - -[formatters] -keys=default - -[formatter_default] -format=%(asctime)s {1} %(message)s - -[handler_default] -class=FileHandler -level=NOTSET -formatter=default -args=({0!r}, 'w') - -[logger_root] -level=DEBUG -handlers=default -''' - - @classmethod - def setUpClass(cls): - cls.home = tempfile.mkdtemp() - cls._files = cls._files[2:] + ('logging.dat',) - cls.log_file = os.path.join(cls.home, 'debug.log') - - with open(os.path.join(cls.home, 'logging.dat'), 'wb') as dst: - dst.write(cls.conf_template.format(cls.log_file, cls.pattern)) - - super(TestLogger, cls).setUpClass() - - def test_fileConfig(self): - """Check that our logging.dat was used""" - - self._stop_process() - data = open(self.log_file).read() - self.assertRegexpMatches(data, self.pattern) - self.assertRegexpMatches(data, 'Loaded logger configuration') diff --git a/src/tests/test_msg.py b/src/tests/test_msg.py deleted file mode 100644 index cb586fa5..00000000 --- a/src/tests/test_msg.py +++ /dev/null @@ -1,39 +0,0 @@ -"""Tests for messagetypes module""" -import unittest - -from six import text_type - -from pybitmessage import messagetypes - -sample_data = {"": "message", "subject": "subject", "body": "body"} -invalid_data = {"": "message", "subject": b"\x01\x02\x03", "body": b"\x01\x02\x03\x04"} - - -class TestMessageTypes(unittest.TestCase): - """A test case for messagetypes""" - - def test_msg_encode(self): - """Test msg encode""" - msgObj = messagetypes.message.Message() - encoded_message = msgObj.encode(sample_data) - self.assertEqual(type(encoded_message), dict) - self.assertEqual(encoded_message["subject"], sample_data["subject"]) - self.assertEqual(encoded_message["body"], sample_data["body"]) - - def test_msg_decode(self): - """Test msg decode""" - msgObj = messagetypes.constructObject(sample_data) - self.assertEqual(msgObj.subject, sample_data["subject"]) - self.assertEqual(msgObj.body, sample_data["body"]) - - def test_invalid_data_type(self): - """Test invalid data type""" - msgObj = messagetypes.constructObject(invalid_data) - self.assertTrue(isinstance(msgObj.subject, text_type)) - self.assertTrue(isinstance(msgObj.body, text_type)) - - def test_msg_process(self): - """Test msg process""" - msgObj = messagetypes.constructObject(sample_data) - self.assertTrue(isinstance(msgObj, messagetypes.message.Message)) - self.assertIsNone(msgObj.process()) diff --git a/src/tests/test_multiqueue.py b/src/tests/test_multiqueue.py deleted file mode 100644 index 87149d56..00000000 --- a/src/tests/test_multiqueue.py +++ /dev/null @@ -1,65 +0,0 @@ -"""Test cases for multiqueue""" - -import unittest -from pybitmessage.multiqueue import MultiQueue - - -class TestMultiQueue(unittest.TestCase): - """Test cases for multiqueue""" - - def test_queue_creation(self): - """Check if the queueCount matches the specified value""" - mqsize = 3 - multiqueue = MultiQueue(count=mqsize) - self.assertEqual(multiqueue.queueCount, mqsize) - - def test_empty_queue(self): - """Check for empty queue""" - multiqueue = MultiQueue(count=5) - self.assertEqual(multiqueue.totalSize(), 0) - - def test_put_get_count(self): - """check if put & get count is equal""" - multiqueue = MultiQueue(count=5) - put_count = 6 - for i in range(put_count): - multiqueue.put(i) - - get_count = 0 - while multiqueue.totalSize() != 0: - if multiqueue.qsize() > 0: - multiqueue.get() - get_count += 1 - multiqueue.iterate() - - self.assertEqual(get_count, put_count) - - def test_put_and_get(self): - """Testing Put and Get""" - item = 400 - multiqueue = MultiQueue(count=3) - multiqueue.put(item) - result = None - for _ in multiqueue.queues: - if multiqueue.qsize() > 0: - result = multiqueue.get() - break - multiqueue.iterate() - self.assertEqual(result, item) - - def test_iteration(self): - """Check if the iteration wraps around correctly""" - mqsize = 3 - iteroffset = 1 - multiqueue = MultiQueue(count=mqsize) - for _ in range(mqsize + iteroffset): - multiqueue.iterate() - self.assertEqual(multiqueue.iter, iteroffset) - - def test_total_size(self): - """Check if the total size matches the expected value""" - multiqueue = MultiQueue(count=3) - put_count = 5 - for i in range(put_count): - multiqueue.put(i) - self.assertEqual(multiqueue.totalSize(), put_count) diff --git a/src/tests/test_network.py b/src/tests/test_network.py deleted file mode 100644 index 08cd95ce..00000000 --- a/src/tests/test_network.py +++ /dev/null @@ -1,96 +0,0 @@ -"""Test network module""" - -import threading -import time - -from .common import skip_python3 -from .partial import TestPartialRun - -skip_python3() - - -class TestNetwork(TestPartialRun): - """A test case for running the network subsystem""" - - @classmethod - def setUpClass(cls): - super(TestNetwork, cls).setUpClass() - - cls.state.maximumNumberOfHalfOpenConnections = 4 - - cls.config.set('bitmessagesettings', 'sendoutgoingconnections', 'True') - cls.config.set('bitmessagesettings', 'udp', 'True') - - # config variable is still used inside of the network ): - import network - from network import connectionpool, stats - - # beware of singleton - connectionpool.config = cls.config - cls.pool = network.BMConnectionPool() - cls.stats = stats - - network.start(cls.config, cls.state) - - def test_threads(self): - """Ensure all the network threads started""" - threads = { - "AddrBroadcaster", "Announcer", "Asyncore", "Downloader", - "InvBroadcaster", "Uploader"} - extra = self.config.getint('threads', 'receive') - for thread in threading.enumerate(): - try: - threads.remove(thread.name) - except KeyError: - extra -= thread.name.startswith("ReceiveQueue_") - - self.assertEqual(len(threads), 0) - self.assertEqual(extra, 0) - - def test_stats(self): - """Check that network starts connections and updates stats""" - pl = 0 - for _ in range(30): - if pl == 0: - pl = len(self.pool) - if ( - self.stats.receivedBytes() > 0 and self.stats.sentBytes() > 0 - and pl > 0 - # and len(self.stats.connectedHostsList()) > 0 - ): - break - time.sleep(1) - else: - self.fail('Have not started any connection in 30 sec') - - def test_udp(self): - """Invoke AnnounceThread.announceSelf() and check discovered peers""" - for _ in range(20): - if self.pool.udpSockets: - break - time.sleep(1) - else: - self.fail('No UDP sockets found in 20 sec') - - for _ in range(10): - try: - self.state.announceThread.announceSelf() - except AttributeError: - self.fail('state.announceThread is not set properly') - time.sleep(1) - try: - peer = self.state.discoveredPeers.popitem()[0] - except KeyError: - continue - else: - self.assertEqual(peer.port, 8444) - break - else: - self.fail('No self in discovered peers') - - @classmethod - def tearDownClass(cls): - super(TestNetwork, cls).tearDownClass() - for thread in threading.enumerate(): - if thread.name == "Asyncore": - thread.stopThread() diff --git a/src/tests/test_openclpow.py b/src/tests/test_openclpow.py deleted file mode 100644 index 341beec9..00000000 --- a/src/tests/test_openclpow.py +++ /dev/null @@ -1,31 +0,0 @@ -""" -Tests for openclpow module -""" -import hashlib -import unittest -from struct import pack, unpack -from pybitmessage import openclpow - - -class TestOpenClPow(unittest.TestCase): - """ - Main opencl test case - """ - - @classmethod - def setUpClass(cls): - openclpow.initCL() - - @unittest.skipUnless(openclpow.enabledGpus, "No GPUs found / enabled") - def test_openclpow(self): - """Check the working of openclpow module""" - target_ = 54227212183 - initialHash = ( - "3758f55b5a8d902fd3597e4ce6a2d3f23daff735f65d9698c270987f4e67ad590" - "b93f3ffeba0ef2fd08a8dc2f87b68ae5a0dc819ab57f22ad2c4c9c8618a43b3" - ).decode("hex") - nonce = openclpow.do_opencl_pow(initialHash.encode("hex"), target_) - trialValue, = unpack( - '>Q', hashlib.sha512(hashlib.sha512( - pack('>Q', nonce) + initialHash).digest()).digest()[0:8]) - self.assertLess((nonce - trialValue), target_) diff --git a/src/tests/test_packets.py b/src/tests/test_packets.py deleted file mode 100644 index 9dfb1d23..00000000 --- a/src/tests/test_packets.py +++ /dev/null @@ -1,87 +0,0 @@ -"""Test packets creation and parsing""" - -from binascii import unhexlify -from struct import pack - -from pybitmessage import addresses, protocol - -from .samples import ( - sample_addr_data, sample_object_data, sample_object_expires) -from .test_protocol import TestSocketInet - - -class TestSerialize(TestSocketInet): - """Test serializing and deserializing packet data""" - - def test_varint(self): - """Test varint encoding and decoding""" - data = addresses.encodeVarint(0) - self.assertEqual(data, b'\x00') - data = addresses.encodeVarint(42) - self.assertEqual(data, b'*') - data = addresses.encodeVarint(252) - self.assertEqual(data, unhexlify('fc')) - data = addresses.encodeVarint(253) - self.assertEqual(data, unhexlify('fd00fd')) - data = addresses.encodeVarint(100500) - self.assertEqual(data, unhexlify('fe00018894')) - data = addresses.encodeVarint(65535) - self.assertEqual(data, unhexlify('fdffff')) - data = addresses.encodeVarint(4294967295) - self.assertEqual(data, unhexlify('feffffffff')) - data = addresses.encodeVarint(4294967296) - self.assertEqual(data, unhexlify('ff0000000100000000')) - data = addresses.encodeVarint(18446744073709551615) - self.assertEqual(data, unhexlify('ffffffffffffffffff')) - - with self.assertRaises(addresses.varintEncodeError): - addresses.encodeVarint(18446744073709551616) - - value, length = addresses.decodeVarint(b'\xfeaddr') - self.assertEqual(value, protocol.OBJECT_ADDR) - self.assertEqual(length, 5) - value, length = addresses.decodeVarint(b'\xfe\x00tor') - self.assertEqual(value, protocol.OBJECT_ONIONPEER) - self.assertEqual(length, 5) - - def test_packet(self): - """Check the packet created by protocol.CreatePacket()""" - head = unhexlify(b'%x' % protocol.magic) - self.assertEqual( - protocol.CreatePacket(b'ping')[:len(head)], head) - - def test_decode_obj_parameters(self): - """Check parameters decoded from a sample object""" - objectType, toStreamNumber, expiresTime = \ - protocol.decodeObjectParameters(sample_object_data) - self.assertEqual(objectType, 42) - self.assertEqual(toStreamNumber, 2) - self.assertEqual(expiresTime, sample_object_expires) - - def test_encodehost(self): - """Check the result of protocol.encodeHost()""" - self.assertEqual( - protocol.encodeHost('127.0.0.1'), - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF' - + pack('>L', 2130706433)) - self.assertEqual( - protocol.encodeHost('191.168.1.1'), - unhexlify('00000000000000000000ffffbfa80101')) - self.assertEqual( - protocol.encodeHost('1.1.1.1'), - unhexlify('00000000000000000000ffff01010101')) - self.assertEqual( - protocol.encodeHost('0102:0304:0506:0708:090A:0B0C:0D0E:0F10'), - unhexlify('0102030405060708090a0b0c0d0e0f10')) - self.assertEqual( - protocol.encodeHost('quzwelsuziwqgpt2.onion'), - unhexlify('fd87d87eeb438533622e54ca2d033e7a')) - - def test_assemble_addr(self): - """Assemble addr packet and compare it to pregenerated sample""" - self.assertEqual( - sample_addr_data, - protocol.assembleAddrMessage([ - (1, protocol.Peer('127.0.0.1', 8444), 1626611891) - for _ in range(500) - ])[protocol.Header.size:]) diff --git a/src/tests/test_pattern/knownnodes.dat b/src/tests/test_pattern/knownnodes.dat deleted file mode 100644 index a78a4434..00000000 --- a/src/tests/test_pattern/knownnodes.dat +++ /dev/null @@ -1,104 +0,0 @@ -(dp0 -I1 -(dp1 -ccopy_reg -_reconstructor -p2 -(cstate -Peer -p3 -c__builtin__ -tuple -p4 -(S'85.180.139.241' -p5 -I8444 -tp6 -tp7 -Rp8 -I1608398841 -sg2 -(g3 -g4 -(S'158.222.211.81' -p9 -I8080 -tp10 -tp11 -Rp12 -I1608398841 -sg2 -(g3 -g4 -(S'178.62.12.187' -p13 -I8448 -tp14 -tp15 -Rp16 -I1608398841 -sg2 -(g3 -g4 -(S'109.147.204.113' -p17 -I1195 -tp18 -tp19 -Rp20 -I1608398841 -sg2 -(g3 -g4 -(S'5.45.99.75' -p21 -I8444 -tp22 -tp23 -Rp24 -I1608398841 -sg2 -(g3 -g4 -(S'178.11.46.221' -p25 -I8444 -tp26 -tp27 -Rp28 -I1608398841 -sg2 -(g3 -g4 -(S'95.165.168.168' -p29 -I8444 -tp30 -tp31 -Rp32 -I1608398841 -sg2 -(g3 -g4 -(S'24.188.198.204' -p33 -I8111 -tp34 -tp35 -Rp36 -I1608398841 -sg2 -(g3 -g4 -(S'75.167.159.54' -p37 -I8444 -tp38 -tp39 -Rp40 -I1608398841 -ssI2 -(dp41 -sI3 -(dp42 -s. \ No newline at end of file diff --git a/src/tests/test_process.py b/src/tests/test_process.py deleted file mode 100644 index 37b34541..00000000 --- a/src/tests/test_process.py +++ /dev/null @@ -1,229 +0,0 @@ -""" -Common reusable code for tests and tests for pybitmessage process. -""" - -import os -import signal -import subprocess # nosec -import sys -import tempfile -import time -import unittest - -import psutil - -from .common import cleanup, put_signal_file, skip_python3 - - -skip_python3() - - -class TestProcessProto(unittest.TestCase): - """Test case implementing common logic for external testing: - it starts pybitmessage in setUpClass() and stops it in tearDownClass() - """ - _process_cmd = ['pybitmessage', '-d'] - _threads_count_min = 15 - _threads_count_max = 16 - _threads_names = [ - 'PyBitmessage', - 'addressGenerato', - 'singleWorker', - 'SQL', - 'objectProcessor', - 'singleCleaner', - 'singleAPI', - 'Asyncore', - 'ReceiveQueue_0', - 'ReceiveQueue_1', - 'ReceiveQueue_2', - 'Announcer', - 'InvBroadcaster', - 'AddrBroadcaster', - 'Downloader', - 'Uploader' - ] - _files = ( - 'keys.dat', 'debug.log', 'messages.dat', 'knownnodes.dat', - '.api_started', 'unittest.lock' - ) - home = None - - @classmethod - def setUpClass(cls): - """Setup environment and start pybitmessage""" - cls.flag = False - if not cls.home: - cls.home = tempfile.gettempdir() - cls._cleanup_files() - os.environ['BITMESSAGE_HOME'] = cls.home - put_signal_file(cls.home, 'unittest.lock') - starttime = int(time.time()) - 0.5 - cls.process = psutil.Popen( - cls._process_cmd, stderr=subprocess.STDOUT) # nosec - - pidfile = os.path.join(cls.home, 'singleton.lock') - for _ in range(10): - time.sleep(1) - try: - pstat = os.stat(pidfile) - if starttime <= pstat.st_mtime and pstat.st_size > 0: - break # the pidfile is suitable - except OSError: - continue - - try: - pid = int(cls._get_readline('singleton.lock')) - cls.process = psutil.Process(pid) - time.sleep(5) - except (psutil.NoSuchProcess, TypeError): - cls.flag = True - - def setUp(self): - if self.flag: - self.fail("%s is not started ):" % self._process_cmd) - - @classmethod - def _get_readline(cls, pfile): - pfile = os.path.join(cls.home, pfile) - try: - with open(pfile, 'rb') as p: - return p.readline().strip() - except (OSError, IOError): - pass - - @classmethod - def _stop_process(cls, timeout=5): - cls.process.send_signal(signal.SIGTERM) - try: - cls.process.wait(timeout) - except psutil.TimeoutExpired: - return False - return True - - @classmethod - def _kill_process(cls, timeout=5): - try: - cls.process.send_signal(signal.SIGKILL) - cls.process.wait(timeout) - # Windows or already dead - except (AttributeError, psutil.NoSuchProcess): - return True - # except psutil.TimeoutExpired propagates, it means something is very - # wrong - return True - - @classmethod - def _cleanup_files(cls): - cleanup(cls.home, cls._files) - - @classmethod - def tearDownClass(cls): - """Ensures that pybitmessage stopped and removes files""" - try: - if not cls._stop_process(10): - processes = cls.process.children(recursive=True) - processes.append(cls.process) - for p in processes: - try: - p.kill() - except psutil.NoSuchProcess: - pass - except psutil.NoSuchProcess: - pass - finally: - cls._cleanup_files() - - def _test_threads(self): - """Test number and names of threads""" - - # pylint: disable=invalid-name - self.longMessage = True - - try: - # using ps for posix platforms - # because of https://github.com/giampaolo/psutil/issues/613 - thread_names = subprocess.check_output([ - "ps", "-L", "-o", "comm=", "--pid", - str(self.process.pid) - ]).split() - except subprocess.CalledProcessError: - thread_names = [] - except: # noqa:E722 - thread_names = [] - - running_threads = len(thread_names) - if 0 < running_threads < 30: # adequacy check - extra_threads = [] - missing_threads = [] - for thread_name in thread_names: - if thread_name not in self._threads_names: - extra_threads.append(thread_name) - for thread_name in self._threads_names: - if thread_name not in thread_names: - missing_threads.append(thread_name) - - msg = "Missing threads: {}, Extra threads: {}".format( - ",".join(missing_threads), ",".join(extra_threads)) - else: - running_threads = self.process.num_threads() - if sys.platform.startswith('win'): - running_threads -= 1 # one extra thread on Windows! - msg = "Unexpected running thread count" - - self.assertGreaterEqual( - running_threads, - self._threads_count_min, - msg) - - self.assertLessEqual( - running_threads, - self._threads_count_max, - msg) - - -class TestProcessShutdown(TestProcessProto): - """Separate test case for SIGTERM""" - def test_shutdown(self): - """Send to pybitmessage SIGTERM and ensure it stopped""" - # longer wait time because it's not a benchmark - self.assertTrue( - self._stop_process(20), - '%s has not stopped in 20 sec' % ' '.join(self._process_cmd)) - - -class TestProcess(TestProcessProto): - """A test case for pybitmessage process""" - @unittest.skipIf(sys.platform[:5] != 'linux', 'probably needs prctl') - def test_process_name(self): - """Check PyBitmessage process name""" - self.assertEqual(self.process.name(), 'PyBitmessage') - - @unittest.skipIf(psutil.version_info < (4, 0), 'psutil is too old') - def test_home(self): - """Ensure BITMESSAGE_HOME is used by process""" - self.assertEqual( - self.process.environ().get('BITMESSAGE_HOME'), self.home) - - @unittest.skipIf( - os.getenv('WINEPREFIX'), "process.connections() doesn't work on wine") - def test_listening(self): - """Check that pybitmessage listens on port 8444""" - for c in self.process.connections(): - if c.status == 'LISTEN': - self.assertEqual(c.laddr[1], 8444) - break - - def test_files(self): - """Check existence of PyBitmessage files""" - for pfile in self._files: - if pfile.startswith('.'): - continue - self.assertIsNot( - self._get_readline(pfile), None, - 'Failed to read file %s' % pfile - ) - - def test_threads(self): - """Testing PyBitmessage threads""" - self._test_threads() diff --git a/src/tests/test_protocol.py b/src/tests/test_protocol.py deleted file mode 100644 index e3137b25..00000000 --- a/src/tests/test_protocol.py +++ /dev/null @@ -1,113 +0,0 @@ -""" -Tests for common protocol functions -""" - -import sys -import unittest - -from pybitmessage import protocol, state -from pybitmessage.helper_startup import fixSocket - - -class TestSocketInet(unittest.TestCase): - """Base class for test cases using protocol.encodeHost()""" - - @classmethod - def setUpClass(cls): - """Execute fixSocket() before start. Only for Windows?""" - fixSocket() - - -class TestProtocol(TestSocketInet): - """Main protocol test case""" - - def test_checkIPv4Address(self): - """Check the results of protocol.checkIPv4Address()""" - token = 'HELLO' - # checking protocol.encodeHost()[12:] - self.assertEqual( # 127.0.0.1 - token, protocol.checkIPv4Address(b'\x7f\x00\x00\x01', token, True)) - self.assertFalse( - protocol.checkIPv4Address(b'\x7f\x00\x00\x01', token)) - self.assertEqual( # 10.42.43.1 - token, protocol.checkIPv4Address(b'\n*+\x01', token, True)) - self.assertFalse( - protocol.checkIPv4Address(b'\n*+\x01', token, False)) - self.assertEqual( # 192.168.0.254 - token, protocol.checkIPv4Address(b'\xc0\xa8\x00\xfe', token, True)) - self.assertEqual( # 172.31.255.254 - token, protocol.checkIPv4Address(b'\xac\x1f\xff\xfe', token, True)) - # self.assertEqual( # 169.254.1.1 - # token, protocol.checkIPv4Address(b'\xa9\xfe\x01\x01', token, True)) - # self.assertEqual( # 254.128.1.1 - # token, protocol.checkIPv4Address(b'\xfe\x80\x01\x01', token, True)) - self.assertFalse( # 8.8.8.8 - protocol.checkIPv4Address(b'\x08\x08\x08\x08', token, True)) - - def test_checkIPv6Address(self): - """Check the results of protocol.checkIPv6Address()""" - test_ip = '2001:db8::ff00:42:8329' - self.assertEqual( - 'test', protocol.checkIPv6Address( - protocol.encodeHost(test_ip), 'test')) - self.assertFalse( - protocol.checkIPv6Address( - protocol.encodeHost(test_ip), 'test', True)) - for test_ip in ('fe80::200:5aee:feaa:20a2', 'fdf8:f53b:82e4::53'): - self.assertEqual( - 'test', protocol.checkIPv6Address( - protocol.encodeHost(test_ip), 'test', True)) - self.assertFalse( - protocol.checkIPv6Address( - protocol.encodeHost(test_ip), 'test')) - - def test_check_local(self): - """Check the logic of TCPConnection.local""" - self.assertTrue( - protocol.checkIPAddress(protocol.encodeHost('127.0.0.1'), True)) - self.assertTrue( - protocol.checkIPAddress(protocol.encodeHost('192.168.0.1'), True)) - self.assertTrue( - protocol.checkIPAddress(protocol.encodeHost('10.42.43.1'), True)) - self.assertTrue( - protocol.checkIPAddress(protocol.encodeHost('172.31.255.2'), True)) - self.assertFalse(protocol.checkIPAddress( - protocol.encodeHost('2001:db8::ff00:42:8329'), True)) - - globalhost = protocol.encodeHost('8.8.8.8') - self.assertFalse(protocol.checkIPAddress(globalhost, True)) - self.assertEqual(protocol.checkIPAddress(globalhost), '8.8.8.8') - - @unittest.skipIf( - sys.hexversion >= 0x3000000, 'this is still not working with python3') - def test_check_local_socks(self): - """The SOCKS part of the local check""" - self.assertTrue( - not protocol.checkSocksIP('127.0.0.1') - or state.socksIP) - - def test_network_group(self): - """Test various types of network groups""" - - test_ip = '1.2.3.4' - self.assertEqual(b'\x01\x02', protocol.network_group(test_ip)) - - test_ip = '127.0.0.1' - self.assertEqual('IPv4', protocol.network_group(test_ip)) - - self.assertEqual( - protocol.network_group('8.8.8.8'), - protocol.network_group('8.8.4.4')) - self.assertNotEqual( - protocol.network_group('1.1.1.1'), - protocol.network_group('8.8.8.8')) - - test_ip = '0102:0304:0506:0708:090A:0B0C:0D0E:0F10' - self.assertEqual( - b'\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C', - protocol.network_group(test_ip)) - - for test_ip in ( - 'bootstrap8444.bitmessage.org', 'quzwelsuziwqgpt2.onion', None): - self.assertEqual( - test_ip, protocol.network_group(test_ip)) diff --git a/src/tests/test_randomtrackingdict.py b/src/tests/test_randomtrackingdict.py deleted file mode 100644 index 2db3c423..00000000 --- a/src/tests/test_randomtrackingdict.py +++ /dev/null @@ -1,49 +0,0 @@ -""" -Tests for RandomTrackingDict Class -""" -import random -import unittest - -from time import time - - -class TestRandomTrackingDict(unittest.TestCase): - """ - Main protocol test case - """ - - @staticmethod - def randString(): - """helper function for tests, generates a random string""" - retval = '' - for _ in range(32): - retval += chr(random.randint(0, 255)) - return retval - - def test_check_randomtrackingdict(self): - """Check the logic of RandomTrackingDict class""" - from pybitmessage.randomtrackingdict import RandomTrackingDict - a = [] - k = RandomTrackingDict() - - a.append(time()) - for i in range(50000): - k[self.randString()] = True - a.append(time()) - - while k: - retval = k.randomKeys(1000) - if not retval: - self.fail("error getting random keys") - - try: - k.randomKeys(100) - self.fail("bad") - except KeyError: - pass - for i in retval: - del k[i] - a.append(time()) - - for x in range(len(a) - 1): - self.assertLess(a[x + 1] - a[x], 10) diff --git a/src/tests/test_sqlthread.py b/src/tests/test_sqlthread.py deleted file mode 100644 index a612df3a..00000000 --- a/src/tests/test_sqlthread.py +++ /dev/null @@ -1,44 +0,0 @@ -"""Tests for SQL thread""" -# flake8: noqa:E402 -import os -import tempfile -import threading -import unittest - -from .common import skip_python3 - -skip_python3() - -os.environ['BITMESSAGE_HOME'] = tempfile.gettempdir() - -from pybitmessage.helper_sql import ( - sqlQuery, sql_ready, sqlStoredProcedure) # noqa:E402 -from pybitmessage.class_sqlThread import sqlThread # noqa:E402 -from pybitmessage.addresses import encodeAddress # noqa:E402 - - -class TestSqlThread(unittest.TestCase): - """Test case for SQL thread""" - - @classmethod - def setUpClass(cls): - # Start SQL thread - sqlLookup = sqlThread() - sqlLookup.daemon = True - sqlLookup.start() - sql_ready.wait() - - @classmethod - def tearDownClass(cls): - sqlStoredProcedure('exit') - for thread in threading.enumerate(): - if thread.name == "SQL": - thread.join() - - def test_create_function(self): - """Check the result of enaddr function""" - encoded_str = encodeAddress(4, 1, "21122112211221122112") - - query = sqlQuery('SELECT enaddr(4, 1, "21122112211221122112")') - self.assertEqual( - query[0][-1], encoded_str, "test case fail for create_function") diff --git a/src/threads.py b/src/threads.py deleted file mode 100644 index ac8bf7a6..00000000 --- a/src/threads.py +++ /dev/null @@ -1,48 +0,0 @@ -""" -PyBitmessage does various tasks in separate threads. Most of them inherit -from `.network.StoppableThread`. There are `addressGenerator` for -addresses generation, `objectProcessor` for processing the network objects -passed minimal validation, `singleCleaner` to periodically clean various -internal storages (like inventory and knownnodes) and do forced garbage -collection, `singleWorker` for doing PoW, `sqlThread` for querying sqlite -database. - -There are also other threads in the `.network` package. - -:func:`set_thread_name` is defined here for the threads that don't inherit from -:class:`.network.StoppableThread` -""" - -import threading - -from class_addressGenerator import addressGenerator -from class_objectProcessor import objectProcessor -from class_singleCleaner import singleCleaner -from class_singleWorker import singleWorker -from class_sqlThread import sqlThread - -try: - import prctl -except ImportError: - def set_thread_name(name): - """Set a name for the thread for python internal use.""" - threading.current_thread().name = name -else: - def set_thread_name(name): - """Set the thread name for external use (visible from the OS).""" - prctl.set_name(name) - - def _thread_name_hack(self): - set_thread_name(self.name) - threading.Thread.__bootstrap_original__(self) - # pylint: disable=protected-access - threading.Thread.__bootstrap_original__ = threading.Thread._Thread__bootstrap - threading.Thread._Thread__bootstrap = _thread_name_hack - - -printLock = threading.Lock() - -__all__ = [ - "addressGenerator", "objectProcessor", "singleCleaner", "singleWorker", - "sqlThread", "printLock" -] diff --git a/src/tr.py b/src/tr.py deleted file mode 100644 index eec82c37..00000000 --- a/src/tr.py +++ /dev/null @@ -1,59 +0,0 @@ -""" -Translating text -""" -import os - -try: - import state -except ImportError: - from . import state - - -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""" - 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 - - -def _translate(context, text, disambiguation=None, encoding=None, n=None): - # pylint: disable=unused-argument - 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: - 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 - if n is None: - return QtGui.QApplication.translate(context, text) - return QtGui.QApplication.translate(context, text, None, QtCore.QCoreApplication.CodecForTr, n) - else: - if '%' in text: - return translateClass(context, text.replace('%', '', 1)) - return text diff --git a/src/translations/bitmessage.pro b/src/translations/bitmessage.pro deleted file mode 100644 index b131d8e5..00000000 --- a/src/translations/bitmessage.pro +++ /dev/null @@ -1,67 +0,0 @@ -SOURCES = ../addresses.py\ - ../bitmessagemain.py\ - ../class_addressGenerator.py\ - ../class_objectProcessor.py\ - ../class_singleCleaner.py\ - ../class_singleWorker.py\ - ../class_sqlThread.py\ - ../helper_msgcoding.py\ - ../helper_search.py\ - ../namecoin.py\ - ../proofofwork.py\ - ../upnp.py\ - ../bitmessageqt/__init__.py\ - ../bitmessageqt/account.py\ - ../bitmessageqt/address_dialogs.py\ - ../bitmessageqt/addressvalidator.py\ - ../bitmessageqt/bitmessageui.py\ - ../bitmessageqt/blacklist.py\ - ../bitmessageqt/dialogs.py\ - ../bitmessageqt/foldertree.py\ - ../bitmessageqt/languagebox.py\ - ../bitmessageqt/messagecompose.py\ - ../bitmessageqt/messageview.py\ - ../bitmessageqt/networkstatus.py\ - ../bitmessageqt/newchandialog.py\ - ../bitmessageqt/settings.py\ - ../bitmessageqt/support.py\ - ../plugins/indicator_libmessaging.py\ - ../plugins/menu_qrcode.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 - -TRANSLATIONS = \ - bitmessage_ar.ts \ - bitmessage_cs.ts \ - bitmessage_da.ts \ - bitmessage_de.ts \ - bitmessage_en.ts \ - bitmessage_en_pirate.ts \ - bitmessage_eo.ts \ - bitmessage_fr.ts \ - bitmessage_it.ts \ - 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 deleted file mode 100644 index 892f6160..00000000 Binary files a/src/translations/bitmessage_ar.qm and /dev/null differ diff --git a/src/translations/bitmessage_ar.ts b/src/translations/bitmessage_ar.ts deleted file mode 100644 index 6bf906d7..00000000 --- a/src/translations/bitmessage_ar.ts +++ /dev/null @@ -1,2127 +0,0 @@ - - - - AddAddressDialog - - - Add new entry - إضافة مدخل جديد - - - - Label - إسم مستعار - - - - Address - عنوان - - - - 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. - - - - - 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: - - - - - 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, -# 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. - - - - - - MainWindow - - - Reply to sender - - - - - Reply to channel - - - - - Add sender to your Address Book - إضافة جهة اتصال لدفتر العناوين - - - - Add sender to your Blacklist - - - - - Move to Trash - حذف إلى سلة المهملات - - - - Undelete - - - - - View HTML code as formatted text - إظهار نظام تشفير HTML كنص منسق - - - - Save message as... - حفظ الرسالة ك - - - - Mark Unread - وضع علامة غير مقروء - - - - New - جديد - - - - Enable - تفعيل - - - - Disable - تعطيل - - - - Set avatar... - تغيير الصورة الرمزية - - - - Copy address to clipboard - نسخ العنوان إلى الحافظة - - - - Special address behavior... - سلوك عنوان خاص - - - - Email gateway - - - - - Delete - حذف - - - - Send message to this address - أرسل رسالة لهذا العنوان - - - - Subscribe to this address - متابعة هذا العنوان - - - - Add New Address - إضافة جهة إتصال - - - - Copy destination address to clipboard - نسخ عنوان المرسل إليه إلى الحافظة - - - - Force send - إرسال قصري - - - - One of your addresses, %1, is an old version 1 address. Version 1 addresses are no longer supported. May we delete it now? - واحد من العناوين، %1، حاصل على رقم إصدار 1، العناوين ذات رقم الإصدار 1 غير مدعومه حالياً، هل باستطاعتنا حذفه الآن؟ - - - - Waiting for their encryption key. Will request it again soon. - - - - - Encryption key request queued. - تم إدراح طلب مفتاح التشفير بقائمة الإنتظار. - - - - Queued. - تم الإدراج بقائمة الانتظار - - - - Message sent. Waiting for acknowledgement. Sent at %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 - إظهار 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. - يمكنك إدارة مفاتيحك بواسطة تعديل ملف 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 قبل تعديل الملف. - - - - 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. - - - - - 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'. - تم تكوين زمرة بنجاح، لإتاحة الفرصة للأخرين بالإنضمام لمجموعتك أعطهم إسم الزمرة و هذا العنوان %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. - عنوان Bitmessage غير صحيح. - - - - 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. - - - - - 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 - خطأ: عناوين ال 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, Bitmessage لم يستطع فهم رقم إصدار العنوان %2، ربما يجب عليك تحديث برنامج Bitmessage لإصداره الأخير. - - - - Stream number - رقم المجرى - - - - Concerning the address %1, Bitmessage cannot handle stream numbers of %2. Perhaps upgrade Bitmessage to the latest version. - بالنظر إلى العنوان %1, Bitmessage لم يستطع فهم رقم إصدار العنوان %2، ربما يجب عليك تحديث برنامج Bitmessage لإصداره الأخير. - - - - Warning: You are currently not connected. Bitmessage will do the work necessary to send the message but it won't send until you connect. - تحذير: أنت غير متصل حالياً، Bitmessage سيقوم بالعمل اللازم لإرسال الرسالة و لكن لن يقوم بالإرسال حتى تصبح متصلاً. - - - - Message queued. - - - - - Your 'To' field is empty. - حقل "إلى" فارغ. - - - - Right click one or more entries in your address book and select 'Send message to this address'. - أنقر يميناً على واحد أو أكثر من جهات الاتصال في دفتر العناوين و اختر "إرسال رسالة لهذا العنوان". - - - - Fetched address from namecoin identity. - تم تحصيل العنوان من هوية namecoin. - - - - New Message - رسالة جديدة - - - - From - من - - - - Sending email gateway registration request - - - - - 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. - لتفعيل تغيير رقم نقطة العبور (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 - يمكنك تغييره في قائمة الضبط. - - - - Bitmessage - Bitmessage - - - - Identities - - - - - New Identity - - - - - Search - بحث - - - - All - الكل - - - - To - إلى - - - - From - من - - - - Subject - الموضوع - - - - Message - الرسالة - - - - Received - تاريخ الإستلام - - - - Messages - - - - - Address book - - - - - Address - العنوان - - - - Add Contact - - - - - Fetch Namecoin ID - إحضار هوية namecoin - - - - Subject: - الموضوع: - - - - From: - من: - - - - To: - إلى: - - - - Send ordinary Message - - - - - Send Message to your Subscribers - - - - - TTL: - - - - - Subscriptions - الإشتراكات - - - - Add new Subscription - إدخال إشتراك جديدة - - - - Chans - - - - - Add Chan - - - - - File - ملف - - - - Settings - الضبط - - - - Help - مساعدة - - - - Import keys - إدراج المفاتيح - - - - Manage keys - إدارة المفاتيح - - - - Ctrl+Q - Ctrl+Q - - - - F1 - F1 - - - - Contact support - - - - - About - عن - - - - Regenerate deterministic addresses - إعادة إنتاج عناوين حتمية - غير عشوائية - - - - Delete all trashed messages - حذف سلة المهملات - - - - Join / Create chan - إنضمام / تكوين زمرة - - - - All accounts - - - - - 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? - - - - - 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> - - - - - 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 - رقم المجرى: 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: - إسم القائمة البريدية المستعار: - - - - 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. - هذه نسخة تجريبة للبرنامج - - - - 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 - أسمح لي بتعديل ضبط الشبكة الخاص أولاً - - - - helpDialog - - - 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: - باعتبار أن برنامج Bitmessage هو مشروع تعاوني، يمكنك إيجاد المساعدة في Bitmessage Wiki: - - - - 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 سيعمل بشكل صحيح و لكن يمكنك مساعدة شبكة 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. - أن على اتصال بباقي الأقران (المشاركين) و تم تضبيط الجدار الناري بطريقة صحيحة. - - - - networkstatus - - - 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). - - - - - - - - - - - - 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> - - - - - 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: - عنوان الزمرة: - - - - 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 - خذ بعض دقائق إضافية من وقت الحساب لتقصير العنوان أو العناوين حرف أو حرفين - - - - 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) - تشغيل البرنامج في شريط المهام - - - - 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. - في النظام المتنقل تكون الرسائل و ملفات الضبط محفوظة في مجلد البرنامج نفسه على خلاف بيانات البرنامج العادي، و بذلك يسهل تشغيل البرنامج من 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: - - - - - Bandwidth limit - - - - - Maximum download rate (kB/s): [0: unlimited] - - - - - Maximum upload rate (kB/s): [0: unlimited] - - - - - Proxy server / 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. - هنا يمكنك تحديد الكمية القصوى من العمل الذي ترغب بأدائه عندما ترسل رسالة لشخص آخر، تصفير هذه القيم يدل على قبولك بأي قيمة. - - - - 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 - 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> - - - - - Give up after - توقف بعد - - - - and - و - - - - days - أيام - - - - months. - شهور. - - - - Resends Expire - إنتهاء صلاحية إعادة الإرسال - - - diff --git a/src/translations/bitmessage_cs.qm b/src/translations/bitmessage_cs.qm deleted file mode 100644 index c25ccafa..00000000 Binary files a/src/translations/bitmessage_cs.qm and /dev/null differ diff --git a/src/translations/bitmessage_cs.ts b/src/translations/bitmessage_cs.ts deleted file mode 100644 index 11ab163a..00000000 --- a/src/translations/bitmessage_cs.ts +++ /dev/null @@ -1,2106 +0,0 @@ - - - - AddAddressDialog - - - Add new entry - Přidat novou položku - - - - Label - Popiska - - - - Address - Adresa - - - - EmailGatewayDialog - - - Email gateway - Email brána - - - - 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: - - - - - 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, -# 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. - - - - - - 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é - - - - Enable - Zapnout - - - - Disable - Vypnout - - - - Set avatar... - Nastavit avatar... - - - - Copy address to clipboard - Zkopírovat adresu do clipboardu - - - - Special address behavior... - Speciální chování adresy... - - - - Email gateway - Email brána - - - - Delete - 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? - - - - Waiting for their encryption key. Will request it again soon. - Čekám na šifrovací klíč. Požadavek bude brzy vyslán znovu. - - - - Encryption key request queued. - Požadavek na šifrovací klíč zařazen do fronty. - - - - Queued. - Zařazeno do fronty. - - - - Message sent. Waiting for acknowledgement. Sent at %1 - Zpráva odeslána. Čekám na potvrzení. Odesláno v %1 - - - - Message sent. Sent at %1 - Zpráva odeslána. Odesláno v %1 - - - - Need to do work to send message. Work is queued. - Pro poslání zprávy musím provést práci. Práce byla zařazena do fronty. - - - - Acknowledgement of the message received %1 - Potvrzení o přijetí zprávy %1 - - - - Broadcast queued. - Rozeslání zařazeno do fronty. - - - - Broadcast on %1 - Rozesláno v %1 - - - - Problem: The work demanded by the recipient is more difficult than you are willing to do. %1 - Problém: Obtížnost práce požadovaná adresátem je vyšší než Vámi povolené maximum. %1 - - - - Problem: The recipient's encryption key is no good. Could not encrypt message. %1 - Problém: Šifrovací klíč adresáta je nepoužitelný. Zprávu nelze zašifrovat. %1 - - - - Forced difficulty override. Send should start soon. - Vyžádáno odeslání navzdory obtížnosti. Zpráva bude brzy odeslána. - - - - Unknown status: %1 %2 - Neznámý stav: %1 %2 - - - - Not Connected - Nepřipojeno - - - - Show Bitmessage - Ukázat Bitmessage - - - - Send - Poslat - - - - Subscribe - Přihlásit se k odběru - - - - Channel - - - - - Quit - Ukončit - - - - 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. - Své klíče můžete spravovat editováním souboru keys.dat, který najdete ve stejném adresáři jako tento program. Je důležité si tento soubor zazálohovat. - - - - You may manage your keys by editing the keys.dat file stored in - %1 -It is important that you back up this file. - Své klíče můžete spravovat editováním souboru keys.dat, který najdete zde: - %1 -Je důležité si tento soubor zazálohovat. - - - - Open keys.dat? - Otevřít soubor 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.) - Své klíče můžete spravovat editováním souboru keys.dat, který najdete ve stejném adresáři jako tento program. Je důležité si tento soubor zazálohovat. Přejete si tento soubor nyní otevřít? (Nezapomeňte zavřít Bitmessage předtím, než provedete jakékoli změny.) - - - - 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.) - Své klíče můžete spravovat editováním souboru keys.dat, který najdete zde: - %1 -Je důležité si tento soubor zazálohovat. Přejete si tento soubor nyní otevřít? (Nezapomeňte zavřít Bitmessage předtím, než provedete jakékoli změny.) - - - - Delete trash? - Smazat obsah koše? - - - - Are you sure you want to delete all trashed messages? - Opravdu chcete smazat všechny zprávy v koši? - - - - bad passphrase - špatné heslo - - - - You must type your passphrase. If you don't have one then this is not the form for you. - Musíte napsat své heslo. Pokud žádné nemáte, pak tento formulář není pro Vás. - - - - Bad address version number - Špatné číslo verze adresy - - - - Your address version number must be a number: either 3 or 4. - Verze Vaší adresy musí být číslo: buď 3 nebo 4. - - - - Your address version number must be either 3 or 4. - Verze Vaší adresy musí být buď 3 nebo 4. - - - - Chan name needed - Je třeba zadat jméno kanálu - - - - You didn't enter a chan name. - Nezadal(a) jste jméno kanálu. - - - - Address already present - Adresa je již přítomna - - - - Could not add chan because it appears to already be one of your identities. - Nelze přidat kanál. Zdá se, že ho již máte mezi svými identitami. - - - - Success - Úspěch - - - - 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'. - Kanál byl úspěšně vytvořen. Když chcete jiným lidem povolit připojit se k Vašemu kanálu, řekněte jim jméno kanálu a tuto adresu Bitmessage: %1. Tuto adresu také najdete v sekci "Vaše identity". - - - - Address too new - Adresa je pří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. - Tato adresa Bitmessage může být platná, je to však adresa vyšší verze, se kterou neumíme pracovat. Možná byste měl(a) aktualizovat Bitmessage. - - - - Address invalid - Adresa je neplatná - - - - That Bitmessage address is not valid. - Toto není platná adresa Bitmessage. - - - - Address does not match chan name - Adresa nepatří ke jménu kanálu - - - - Although the Bitmessage address you entered was valid, it doesn't match the chan name. - Adresa Bitmessage, kterou jste zadal(a), je sice platná, nepatří však ke kanálu s tímto jménem. - - - - Successfully joined chan. - Úspěšně jste se připojil(a) ke kanálu. - - - - Connection lost - Připojení ztraceno - - - - Connected - Připojeno - - - - Message trashed - Zpráva byla vyhozena do koše - - - - 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. - - - - - Message too long - Zpráva je příliš dlouhá - - - - 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. - Zpráva, kterou se snažíte poslat, je o %1 bajtů delší, než je dovoleno. (Maximum je 261644 bajtů). Zkuste ji prosím před odesláním zkrátit. - - - - 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 - Chyba: Adresy Bitmessage začínají na BM- Zkontroluje prosím %1 - - - - Error: The address %1 is not typed or copied correctly. Please check it. - Chyba: Adresa %1 nebyla správně opsána nebo zkopírována. Zkontrolujte ji prosím. - - - - Error: The address %1 contains invalid characters. Please check it. - Chyba: Adresa %1 obsahuje neplatné znaky. Zkontrolujte ji prosím. - - - - Error: The address version in %1 is too high. Either you need to upgrade your Bitmessage software or your acquaintance is being clever. - Chyba: Verze adresy %1 je příliš vysoká. Buď používáte starou verzi Bitmessage a je čas na aktualizaci, nebo si Váš známý dělá legraci. - - - - Error: Some data encoded in the address %1 is too short. There might be something wrong with the software of your acquaintance. - Chyba: Některá data zakódovaná v adrese %1 jsou příliš krátká. Možná je to chyba softwaru, který Váš známý používá. - - - - Error: Some data encoded in the address %1 is too long. There might be something wrong with the software of your acquaintance. - Chyba: Některá data zakódovaná v adrese %1 jsou příliš dlouhá. Možná je to chyba softwaru, který Váš známý používá. - - - - Error: Some data encoded in the address %1 is malformed. There might be something wrong with the software of your acquaintance. - Chyba: Některá data zakódovaná v adrese %1 mají neplatný formát. Možná je to chyba softwaru, který Váš známý používá. - - - - Error: Something is wrong with the address %1. - Chyba: Nastal problém s adresou %1. - - - - Error: You must specify a From address. If you don't have one, go to the 'Your Identities' tab. - Chyba: V poli "Od" musíte uvést adresu. Pokud žádnou nemáte, klikněte na kartu "Vaše identity". - - - - Address version number - Číslo verze adresy - - - - Concerning the address %1, Bitmessage cannot understand address version numbers of %2. Perhaps upgrade Bitmessage to the latest version. - Co se týče adresy %1, Bitmessage nerozumí jejímu číslu verze "%2". Možná byste měl(a) aktualizovat Bitmessage na nejnovější verzi. - - - - Stream number - Číslo proudu - - - - Concerning the address %1, Bitmessage cannot handle stream numbers of %2. Perhaps upgrade Bitmessage to the latest version. - Co se týče adresy %1, Bitmessage neumí zpracovat její číslo proudu "%2". Možná byste měl(a) aktualizovat Bitmessage na nejnovější verzi. - - - - Warning: You are currently not connected. Bitmessage will do the work necessary to send the message but it won't send until you connect. - Varování: Nyní nejste připojen(a). Bitmessage provede práci potřebnou k pro odeslání zprávy, ale neodešle ji, dokud se nepřipojíte. - - - - Message queued. - - - - - Your 'To' field is empty. - Pole "Komu" je prázdné. - - - - Right click one or more entries in your address book and select 'Send message to this address'. - 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 - - - - - Address is valid. - Adresa je platná. - - - - The address you entered was invalid. Ignoring it. - 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í). - - - - Bitmessage - Bitmessage - - - - Identities - - - - - New Identity - - - - - Search - Hledej - - - - All - Vše - - - - To - Komu - - - - From - Od - - - - Subject - Předmět - - - - Message - Zpráva - - - - Received - Doručeno - - - - Messages - - - - - Address book - - - - - Address - Adresa - - - - Add Contact - - - - - Fetch Namecoin ID - Načíst Namecoin ID - - - - Subject: - Předmět: - - - - From: - Od: - - - - To: - Komu: - - - - Send ordinary Message - - - - - Send Message to your Subscribers - - - - - TTL: - - - - - Subscriptions - Odběry - - - - Add new Subscription - Přidat nový odběr - - - - Chans - - - - - Add Chan - - - - - File - Soubor - - - - Settings - Nastavení - - - - Help - Nápověda - - - - Import keys - Importovat klíče - - - - Manage keys - Správa klíčů - - - - Ctrl+Q - Ctrl+Q - - - - F1 - F1 - - - - Contact support - - - - - About - O aplikaci - - - - Regenerate deterministic addresses - Obnovit deterministické adresy - - - - Delete all trashed messages - Smazat všechny zprávy v koši - - - - Join / Create chan - Připojit ke kanálu / Vytvořit kanál - - - - 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? - - - - - NewAddressDialog - - - Create new Address - Vytvořit novou 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: - Zde můžete vytvořit tolik adres, kolik budete chtít. Vytvářet nové adresy a opouštět staré se doporučuje. Adresy můžete generovat buď pomocí generátoru náhodných čísel, nebo pomocí hesla. Pokud použijete heslo, vytvořené adrese se říká "deterministická" adresa. -Možnost "Náhodné číslo" je nastavena jako výchozí, deterministické adresy mají několik 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;">Výhody:<br/></span>Adresy můžete znovu vytvořit na jakémkoli počítači z vlastní paměti. <br/>Nemusíte se starat o zálohování souboru keys.dat, stačí si pamatovat heslo. <br/><span style=" font-weight:600;">Nevýhody:<br/></span>Musíte si zapamatovat (nebo zapsat) své heslo, jinak nebudete moci obnovit klíče v případě ztráty. <br/>Spolu s heslem si musíte zapamatovat ještě číslo verze a číslo proudu. <br/>Pokud zvolíte slabé heslo, a někdo na Internetu ho prolomí, může si číst Vaše zprávy a posílat zprávy Vaším jménem.</p></body></html> - - - - Use a random number generator to make an address - Použít generátor náhodných čísel k vytvoření adresy - - - - Use a passphrase to make addresses - Použít k vytvoření adres heslo - - - - Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter - Přidat navíc několik minut počítání, a vytvořit tak adresu(y) o 1 nebo 2 znaky kratší - - - - Make deterministic addresses - Vytvořit deterministické adresy - - - - Address version number: 4 - Číslo verze adresy: 4 - - - - In addition to your passphrase, you must remember these numbers: - Kromě svého hesla si musíte zapamatovat i tato čísla: - - - - Passphrase - Heslo - - - - Number of addresses to make based on your passphrase: - Počet adres, které mají být odvozeny z Vašeho hesla: - - - - Stream number: 1 - Číslo proudu: 1 - - - - Retype passphrase - Napište heslo znovu - - - - Randomly generate address - Náhodně generovat adresu - - - - Label (not shown to anyone except you) - Popiska (neukáže se nikomu kromě Vás) - - - - Use the most available stream - Použít proud s nejlepší dostupností - - - - (best if this is the first of many addresses you will create) - (nejvhodnější, pokud je to první z mnoha adres, které budete vytvářet) - - - - Use the same stream as an existing address - Použít stejný proud jako pro existující adresu - - - - (saves you some bandwidth and processing power) - (šetří přenesená data a práci procesoru) - - - - NewSubscriptionDialog - - - Add new entry - Přidat novou položku - - - - Label - Popiska - - - - Address - Adresa - - - - Enter an address above. - Zadejte adresu výše. - - - - SpecialAddressBehaviorDialog - - - Special Address Behavior - Speciální chování adresy - - - - Behave as a normal address - Ať se chová jako normální adresa - - - - Behave as a pseudo-mailing-list address - Ať se chová jako adresa pseudo-mailing-listu - - - - Mail received to a pseudo-mailing-list address will be automatically broadcast to subscribers (and thus will be public). - Pošta, která přijde na adresu pseudo-mailing-listu, je automaticky rozesílána všem odběratelům (a stává se tedy veřejnou). - - - - Name of the pseudo-mailing-list: - 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. - - - - 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 - - - - - connectDialog - - - Bitmessage - Bitmessage - - - - Bitmessage won't connect to anyone until you let it. - Bitmessage se k nikomu nepřipojí, dokud to nedovolíte. - - - - Connect now - Připojit nyní - - - - Let me configure special network settings first - Chci napřed upravit speciální nastavení sítě - - - - helpDialog - - - Help - Nápověda - - - - <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: - Protože Bitmessage je společně tvořený projekt, nápovědu najdete na webu na Bitmessage Wiki: - - - - iconGlossaryDialog - - - Icon Glossary - Významy ikon - - - - You have no connections with other peers. - Nemáte žádná připojení k jiným uzlům. - - - - 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. - Úspěšně jste se připojil(a) k jednomu či více uzlům pomocí odchozího spojení, ale nepřijal(a) jste žádná příchozí spojení. Váš firewall nebo domácí router pravděpodobně nemá nakonfigurováno přeposílání příchozích TCP připojení na Váš počítač. Bitmessage bude fungovat i bez toho, síti Bitmessage ale můžete pomoci, pokud povolíte příchozí připojení a stanete tak se lépe propojeným uzlem. - - - - You are using TCP port ?. (This can be changed in the settings). - Používáte TCP port ?. (To lze změnit v nastavení). - - - - You do have connections with other peers and your firewall is correctly configured. - Máte připojení k jiným uzlům a Váš firewall je správně nastaven. - - - - networkstatus - - - 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). - - - - - - - - - newChanDialog - - - Dialog - Dialog - - - - Create a new chan - Vytvořit nový kanál - - - - Join a chan - Připojit se ke kanálu - - - - Create a chan - Vytvořit 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>Zadejte jméno pro Váš kanál. Pokud zvolíte dostatečně složité jméno (jako silné a jedinečné heslo), a nikdo z Vašich přátel ho nezveřejní, pak bude tento kanál bezpečný a soukromý. Pokud Vy a někdo další vytvoříte kanál se stejným jménem, je nyní velmi pravděpodobné, že to bude tentýž kanál.</p></body></html> - - - - Chan name: - Jméno 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 vzniká tak, že skupina lidí sdílí stejné dešifrovací klíče. Tyto klíče a adresa bitmessage, která patří k danému kanálu, jsou vygenerovány ze snadno zapamatovatelného slova nebo věty (jméno kanálu). Pokud chcete poslat zprávu všem na kanálu, pošlete normální osobní zprávu na adresu kanálu.</p><p>Kanály jsou experimentální a zcela nemoderovatelné.</p></body></html> - - - - Chan bitmessage address: - Bitmessage adresa kanálu: - - - - regenerateAddressesDialog - - - Regenerate Existing Addresses - Obnovit existující adresy - - - - Regenerate existing addresses - Obnovit existující adresy - - - - Passphrase - Heslo - - - - Number of addresses to make based on your passphrase: - Počet adres, které mají být odvozeny od Vašeho hesla: - - - - Address version number: - Číslo verze adresy: - - - - Stream number: - Číslo proudu: - - - - 1 - 1 - - - - Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter - Strávit několik minut počítáním navíc, a vytvořit tak adresu/adresy o 1 až 2 znaky kratší - - - - You must check (or not check) this box just like you did (or didn't) when you made your addresses the first time. - Toto políčko musí zaškrtnuté/nezaškrtnué stejně, jako když jste tyto adresy vytvářel(a) poprvé. - - - - 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. - Pokud jste dříve vytvořil(a) deterministické adresy, a ztratil(a) jste je při nějaké nehodě (jako třeba selhání harddisku), můžete je zde obnovit. Pokud jste k vytvoření adres použil(a) generátor náhodných čísel, pak Vám tento formulář nijak nepomůže. - - - - settingsDialog - - - Settings - Nastavení - - - - Start Bitmessage on user login - Spustit Bitmessage po přihlášení uživatele - - - - Tray - - - - - Start Bitmessage in the tray (don't show main window) - Spustit Bitmessage v liště (neukazovat hlavní okno) - - - - Minimize to tray - Minimalizovat na lištu - - - - Close to tray - - - - - Show notification when message received - Zobrazit upozornění na příchozí zprávu - - - - Run in Portable Mode - Spustit v přenosném režimu - - - - 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 přenosném režimu jsou zprávy a konfigurační soubory ukládány ve stejném adresáři jako program, namísto normálního adresáře pro data aplikací. To se hodí, když chcete Bitmessage spouštět z USB flashdisku. - - - - Willingly include unencrypted destination address when sending to a mobile device - Přiložit nezašifrovanou cílovou adresu při posílání zprávy na mobilní zařízení - - - - Use Identicons - Používat identikony - - - - Reply below Quote - Odpověď pod citací - - - - Interface Language - Jazyk pro rozhraní - - - - System Settings - system - Dle nastavení systému - - - - User Interface - Uživatelské rozhraní - - - - Listening port - Port pro naslouchání - - - - Listen for connections on port: - Naslouchat příchozím připojením na portu: - - - - UPnP: - - - - - Bandwidth limit - Omezení rychlosti - - - - Maximum download rate (kB/s): [0: unlimited] - Maximální rychlost stahování (kB/s): [0: bez omezení] - - - - Maximum upload rate (kB/s): [0: unlimited] - Maximální rychlost odesílání (kB/s): [0: bez omezení] - - - - Proxy server / Tor - Proxy server / Tor - - - - Type: - Typ: - - - - Server hostname: - Jméno serveru: - - - - Port: - Port: - - - - Authentication - Přihlášení - - - - Username: - Uživatelské jméno: - - - - Pass: - Heslo: - - - - Listen for incoming connections when using proxy - Naslouchat příchozím připojením při použití proxy - - - - none - žádný - - - - SOCKS4a - SOCKS4a - - - - SOCKS5 - SOCKS5 - - - - Network Settings - Nastavení sítě - - - - Total difficulty: - Celková obtížnost: - - - - The 'Total difficulty' affects the absolute amount of work the sender must complete. Doubling this value doubles the amount of work. - "Celková obtížnost" ovlivňuje množství práce, kterou musí odesilatel provést. Zdvojnásobením této hodnoty zdvojnásobíte množství práce. - - - - Small message difficulty: - Obtížnost pro malou zprávu: - - - - 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. - Když Vám někdo pošle zprávu, jeho počítač musí nejprve provést určitou práci. Výchozí obtížnost práce je 1. Tuto hodnotu můžete zvýšit tím, že zvýšíte zde uvedené hodnoty. Všechny nové adresy, které vytvoříte, budou po odesilatelích vyžadovat tuto vyšší obtížnost. Existuje jedna výjimka: když si kamaráda nebo známého přidáte do adresáře, při příští zprávě, kterou mu pošlete, ho Bitmessage upozorní, že mu od teď stačí provést minimální množství práce (obtížnost 1) když Vám chce poslat zprávu. - - - - 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. - "Obtížnost pro malou zprávu" ovlivňuje pouze obtížnost posílání malých zpráv. Pokud ji zdvojnásobíte, bude dvakrát obtížnější poslat malou zprávu, ale velké zprávy to nijak neovlivní. - - - - Demanded difficulty - Požadovaná obtížnost - - - - 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. - Zde můžete nastavit maximální množství práce, které je pro Vás přijatelné, když posíláte zprávu jiné osobě. Nastavení 0 znamená, že množství není omezeno. - - - - Maximum acceptable total difficulty: - Maximální přijatelná celková obtížnost: - - - - Maximum acceptable small message difficulty: - Maximální přijatelná obtížnost pro malou zprávu: - - - - Max acceptable difficulty - Maximální přijatelná obtížnost - - - - 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> - <html><head/><body><p>Bitmessage může použít jiný program založený na technologii Bitcoin, který se jmenuje Namecoin, a nahradit tak adresy lépe čitelnými jmény. Příklad: místo toho, abyste musel(a) kamarádovi diktovat svou dlouhou adresu Bitmessage, můžete mu jednoduše říct, ať pošle zprávu na jméno <span style=" font-style:italic;">test. </span></p><p>(Vložit svou adresu Bitmessage do Namecoin je zatím stále celkem složité).</p><p>Bitmessage může použít buď přímo namecoind, nebo běžící instanci nmcontrol.</p></body></html> - - - - Host: - Server: - - - - Password: - Heslo: - - - - Test - Zkouška - - - - Connect to: - Připojit k: - - - - Namecoind - Namecoind - - - - NMControl - NMControl - - - - Namecoin integration - Integrace s 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>Výchozí nastavení funguje tak, že když pošlete zprávu někomu, kdo je odpojen více než dva dny, Bitmessage tuto zprávu za další dva dny pošle znovu. To bude pokračovat, ale intervaly se budou exponenciálně prodlužovat; zprávy budou znovu poslány za 5, 10, 20 dní atd. dokud adresát nepotvrdí jejich přijetí. Zde můžete toto chování změnit, a nastavit, aby to Bitmessage vzdal, pokud zpráva nebude doručena do určitého počtu dní či měsíců.</p><p>Ponechte tato pole prázdná, pokud chcete použít výchozí chování. </p></body></html> - - - - Give up after - Vzdát to po - - - - and - a - - - - days - dnech - - - - months. - měsících. - - - - Resends Expire - Lhůta pro opětovné poslání - - - diff --git a/src/translations/bitmessage_da.qm b/src/translations/bitmessage_da.qm deleted file mode 100644 index e5588987..00000000 Binary files a/src/translations/bitmessage_da.qm and /dev/null differ diff --git a/src/translations/bitmessage_da.ts b/src/translations/bitmessage_da.ts deleted file mode 100644 index fcf80470..00000000 --- a/src/translations/bitmessage_da.ts +++ /dev/null @@ -1,2103 +0,0 @@ - - - - AddAddressDialog - - - Add new entry - Tilføj ny addresse - - - - Label - Navn - - - - Address - Adresse - - - - EmailGatewayDialog - - - Email gateway - Email gateway - - - - Register on email gateway - Registrér hos en email gateway - - - - Account status at email gateway - Status for konto hos email gateway - - - - Change account settings at email gateway - Ændr kontoindstillinger for email gateway - - - - Unregister from email gateway - Annullér registrering hos 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): - Ønskede mailadresse (inklusiv @mailchuck.com) - - - - 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: - - - - - Email gateway registration - Registrering hos email gateway - - - - 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 - - - # 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. - - - - - - 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 - - - - Enable - Aktiver - - - - Disable - Deaktiver - - - - Set avatar... - Sæt ikon... - - - - Copy address to clipboard - Kopiér adresse til udklipsholder - - - - Special address behavior... - Speciel addressefunktion... - - - - Email gateway - Email gateway - - - - Delete - 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? - - - - Waiting for their encryption key. Will request it again soon. - Venter på krypteringsnøgle. Vil snart efterspørge den igen. - - - - Encryption key request queued. - Efterspørgsel på krypteringsnøgle er sat i kø. - - - - Queued. - Sat i kø. - - - - Message sent. Waiting for acknowledgement. Sent at %1 - Besked afsendt. Afventer bekræftelse på modtagelse. Sendt %1 - - - - Message sent. Sent at %1 - Besked sendt. Sendt %1 - - - - Need to do work to send message. Work is queued. - Skal foretage beregning for at sende besked. Beregningen er sat i kø. - - - - Acknowledgement of the message received %1 - Bekræftelse på modtagelse er modtaget %1 - - - - Broadcast queued. - Afsendelse sat i kø. - - - - Broadcast on %1 - Afsendt %1 - - - - Problem: The work demanded by the recipient is more difficult than you are willing to do. %1 - Problem: Beregningen som kræves af modtageren er mere besværlig end du accepterer. %1 - - - - Problem: The recipient's encryption key is no good. Could not encrypt message. %1 - Problem: Modtagerens krypteringsnøgle virker ikke. Beskeden kunne ikke krypteres. %1 - - - - Forced difficulty override. Send should start soon. - Ændring af sværhedsgrad gennemtvunget. Afsendelse bør starte snart. - - - - Unknown status: %1 %2 - Ukendt status: %1 %2 - - - - Not Connected - Ikke forbundet - - - - Show Bitmessage - Vis Bitmessage - - - - Send - Send - - - - Subscribe - Abonnér - - - - Channel - Kanal - - - - Quit - Afslut - - - - 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. - Du kan administrere dine nøgler ved at redigere keys.dat-filen i samme mappe som dette program. Det er vigtigt at tage backup af denne fil. - - - - You may manage your keys by editing the keys.dat file stored in - %1 -It is important that you back up this file. - Du kan administrere dine nøgler ved at redigere keys.dat-filen i -%1 -Det er vigtigt at tage backup af denne fil. - - - - Open keys.dat? - Åbn 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.) - Du kan administrere dine nøgler ved at redigere keys.dat-filen i samme mappe som dette program. Det er vigtigt at tage backup af denne fil. Vil du åbne denne fil nu? (Sørg for at lukke Bitmessage før du foretager ændringer.) - - - - 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.) - Du kan administrere dine nøgler ved at redigere keys.dat-filen i -%1 -Det er vigtigt at tage backup af denne fil. (Sørg for at lukke Bitmessage før du foretager ændringer.) - - - - Delete trash? - Slet papirkurv? - - - - Are you sure you want to delete all trashed messages? - Er du sikker på at du vil slette alle beskeder i papirkurven? - - - - bad passphrase - ugyldigt kodeord - - - - You must type your passphrase. If you don't have one then this is not the form for you. - Du skal indtaste dit kodeord. Hvis du ikke har et så er dette ikke den rette dialogboks. - - - - Bad address version number - Ugyldig addresse-version - - - - Your address version number must be a number: either 3 or 4. - Din addresse-version skal være enten 3 eller 4. - - - - Your address version number must be either 3 or 4. - Din addresse-version skal være enten 3 eller 4. - - - - Chan name needed - Kanalnavnet er påkrævet - - - - You didn't enter a chan name. - Du indtastede ikke et kanalnavn. - - - - Address already present - Adressen eksisterer allerede - - - - Could not add chan because it appears to already be one of your identities. - Adressen kunne ikke tilføjes da det ser ud tilat den allerede er en af dine identiteter. - - - - Success - Succes - - - - 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'. - Ny kanal oprettet. For at andre kan blive medlem skal du oplyse dem kanalnavnet og denne Bitmessage-adresse: %1. Denne adresse vises også i 'Dine identiteter'. - - - - Address too new - Adressen er for ny - - - - Although that Bitmessage address might be valid, its version number is too new for us to handle. Perhaps you need to upgrade Bitmessage. - Selvom denne Bitmessage-adresse måske er gyldig, er dens versionsnummer for nyt. Måske bør du opgradere Bitmessage. - - - - Address invalid - Adressen er ugyldig - - - - That Bitmessage address is not valid. - Denne Bitmessage-adresse er ikke gyldig. - - - - Address does not match chan name - Adressen stemmer ikke overens med kanalnavnet - - - - Although the Bitmessage address you entered was valid, it doesn't match the chan name. - Selvom denne Bitmessage-adresse er gyldig stemmer den ikke overens med kanalnavnet. - - - - Successfully joined chan. - Du er nu medlem af kanalen - - - - Connection lost - Forbindelse afbrudt - - - - Connected - Forbundet - - - - Message trashed - Beskeden er flyttet til papirkurven - - - - 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. - - - - - Message too long - Beskeden er for 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. - Beskeden som du prøver at sende er %1 byte for lang. (Den maksimale størrelse er 261644 byte). Prøv at gøre den kortere før afsendelsen. - - - - 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 - Fejl: Bitmessage-adresser starter med BM- Check %1 - - - - Error: The address %1 is not typed or copied correctly. Please check it. - Fejl: Adressen %1 er skrever eller kopieret forkert. Tjek den venligst. - - - - Error: The address %1 contains invalid characters. Please check it. - Fejl: Adressen %1 indeholder ugyldige tegn. Tjek den venligst. - - - - 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. - Fejl: Der er noget galt med adressen %1. - - - - Error: You must specify a From address. If you don't have one, go to the 'Your Identities' tab. - Fejl: Du skal angive en afsenderadresse. Hvis du ikke har nogen skal du gå til fanen 'Dine Identiteter'. - - - - Address version number - Adresseversion - - - - Concerning the address %1, Bitmessage cannot understand address version numbers of %2. Perhaps upgrade Bitmessage to the latest version. - Vedrørende adressen %1, Bitmessage forstår ikke addreseversion %2. Måske bør du opgradere Bitmessage til den nyeste version. - - - - Stream number - Flodversion - - - - Concerning the address %1, Bitmessage cannot handle stream numbers of %2. Perhaps upgrade Bitmessage to the latest version. - Vedrørende adressen %1, Bitmessage kan ikke håndtere flod nummer %2. Måske bør du opgradere Bitmessage til den nyeste 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. - Advarsel: Du har ingen forbindelse. Bitmessage vil foretage nødvendige beregninger for at afsende beskeder, men de vil først blive afsendt når du opretter forbindelse. - - - - Message queued. - Besked sat i kø. - - - - Your 'To' field is empty. - Du har ikke angivet en modtager. - - - - Right click one or more entries in your address book and select 'Send message to this address'. - 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 - - - - Address is valid. - Adressen er gyldig. - - - - The address you entered was invalid. Ignoring it. - 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? - Hvis du sletter dette abonnement, vil beskeder som du allerede har modtaget blive utilgængelige. Måske bør du hellere slå adressen fra. Abonnementer der er slået fra modtager ikke nye beskeder, men du kan stadigvæk se de beskeder du allerede har modtaget. - -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? - Hvis du sletter denne kanal, vil beskeder som du allerede har modtaget blive utilgængelige. Måske bør du hellere slå adressen fra. Kanaler der er slået fra modtager ikke nye beskeder, men du kan stadigvæk se de beskeder du allerede har modtaget. - -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). - - - - Bitmessage - Bitmessage - - - - Identities - Identiteter - - - - New Identity - Ny identitet - - - - Search - Søg - - - - All - Alle - - - - To - Til - - - - From - Fra - - - - Subject - Emne - - - - Message - Besked - - - - Received - Modtaget - - - - Messages - Beskeder - - - - Address book - Adressebog - - - - Address - Adresse - - - - Add Contact - Tilføj Kontakt - - - - Fetch Namecoin ID - Hent Namecoin ID - - - - Subject: - Emne: - - - - From: - Fra: - - - - To: - Til: - - - - Send ordinary Message - Send almindelig besked - - - - Send Message to your Subscribers - Send besked til dine abonnenter - - - - TTL: - TTL: - - - - Subscriptions - Abonnementer - - - - Add new Subscription - Tilføj nyt abonnement - - - - Chans - Kanaler - - - - Add Chan - TIlføj kanal - - - - File - Filer - - - - Settings - Indstillinger - - - - Help - Hjælp - - - - Import keys - Importer nøgler - - - - Manage keys - Administrér nøgler - - - - Ctrl+Q - Ctrl+Q - - - - F1 - F1 - - - - Contact support - Kontakt support - - - - About - Om - - - - Regenerate deterministic addresses - Regenerér deterministiske addresser - - - - Delete all trashed messages - Tøm papirkurv - - - - Join / Create chan - Opret/bliv medlem af en kanal - - - - All accounts - Alle konti - - - - Zoom level %1% - Zoom %1% - - - - 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? - - - - - NewAddressDialog - - - Create new Address - Opret ny 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: - Her kan du generere så mange adresser som du vil. Det er helt fint at oprette adresser som kun bruges kortvarigt. Du kan generere adresser enten ud fra tilfældige tal eller ud fra et kodeord. Hvis du bruger et kodeord, kaldes det en "deterministisk" adresse. -Som standard er tilfældige tal valgt, men der er både fordele og ulemper ved at benytte deterministiske adresser i stedet. - - - - <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 - Brug en tilfældighedsgenerator til at generere adresser - - - - Use a passphrase to make addresses - Brug et kodeord til at generere adresser - - - - Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter - Brug flere minutters ekstra beregningstid på at gøre adresserne 1-2 tegn kortere - - - - Make deterministic addresses - Opret deterministiske adresser - - - - Address version number: 4 - Adresse-version: 4 - - - - In addition to your passphrase, you must remember these numbers: - Udover dit kodeord skal du også huske disse tal. - - - - Passphrase - Kodeord - - - - Number of addresses to make based on your passphrase: - Antal adresser der skal genereres ud fra kodeordet: - - - - Stream number: 1 - Flod-nummer: 1 - - - - Retype passphrase - Gentag kodeord - - - - Randomly generate address - Generer adresse tilfældigt - - - - Label (not shown to anyone except you) - Navn (ikke vist til andre end dig selv) - - - - Use the most available stream - Brug den mest tilgængelige flod - - - - (best if this is the first of many addresses you will create) - (anbefales hvis dette er den første ud af mange adresser som du ønsker at oprette) - - - - Use the same stream as an existing address - Brug den samme flod som en eksisterende addresse - - - - (saves you some bandwidth and processing power) - (sparer noget båndbredde og nogle beregninger) - - - - NewSubscriptionDialog - - - Add new entry - Tilføj ny addresse - - - - Label - Navn - - - - Address - Adresse - - - - Enter an address above. - Vælg en adresse ovenfor. - - - - SpecialAddressBehaviorDialog - - - Special Address Behavior - Speciel adressefunktionalitet - - - - Behave as a normal address - Fungér som en almindelig adresse - - - - Behave as a pseudo-mailing-list address - Fungér som en pseudo-mailing-liste - - - - Mail received to a pseudo-mailing-list address will be automatically broadcast to subscribers (and thus will be public). - Beskeder som modtages af en pseudo-mailing-liste vil automatisk blive videresendt til alle abonnenter (og vil derved være offentlige). - - - - Name of the pseudo-mailing-list: - 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. - - - - 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 - - - - - connectDialog - - - Bitmessage - Bitmessage - - - - Bitmessage won't connect to anyone until you let it. - Bitmessage opretter ikke forbindelse til nogen før du beder om det. - - - - Connect now - Opret forbindelse nu - - - - Let me configure special network settings first - Lad mig først konfigurere specielle netværksindstillinger - - - - helpDialog - - - Help - Hjælp - - - - <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: - Da Bitmessage er et samarbejdsprojekt, kan du finde hjælp online på Bitmessage-wikien: - - - - iconGlossaryDialog - - - Icon Glossary - - - - - You have no connections with other peers. - Du har ingen forbindelser til andre computere. - - - - 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). - Du bruger TCP-port ?. (Dette kan ændres i indstillingerne). - - - - You do have connections with other peers and your firewall is correctly configured. - Du har forbindelser til andre computere og din firewall er konfigureret korrekt. - - - - networkstatus - - - 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). - - - - - - - - newChanDialog - - - Dialog - Dialogboks - - - - Create a new chan - Opret en ny kanal - - - - Join a chan - Bliv medlem af en kanal - - - - Create a chan - Opret en kanal - - - - <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: - Kanalnavn: - - - - <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: - Bitmessage adresse for kanalen: - - - - regenerateAddressesDialog - - - Regenerate Existing Addresses - Regenerér Eksisterende Adresser - - - - Regenerate existing addresses - Regenerér eksisterende adresser - - - - Passphrase - Kodeord - - - - Number of addresses to make based on your passphrase: - Antal adresser som skal genereres ud fra dit kodeord: - - - - Address version number: - Adresseversion: - - - - Stream number: - Flodversion: - - - - 1 - 1 - - - - Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter - Brug flere minutters ekstra beregningstid på at gøre adresserne 1-2 tegn kortere - - - - You must check (or not check) this box just like you did (or didn't) when you made your addresses the first time. - Det er vigtigt at du vælger (eller ikke vælger) dette, ligesom du gjorde (eller ikke gjorde) første gang. - - - - 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. - Hvis du tidligere har genereret deterministiske adresser, men du har mistet dem på grund af et uheld (f.eks. harddiskfejl), kan du genskabe dem her. Hvis du brugte tilfældighedsgeneratoren til at lave adresser kan denne dialogboks ikke hjælpe dig. - - - - settingsDialog - - - Settings - Indstillinger - - - - Start Bitmessage on user login - Start Bitmessage når der logges ind - - - - Tray - Systembakke - - - - Start Bitmessage in the tray (don't show main window) - - - - - Minimize to tray - Minimér til systembakken - - - - Close to tray - Minimér til systembakken når hovedvinduet lukkes - - - - Show notification when message received - Vis notifikationer når en besked modtages - - - - Run in Portable Mode - Kør i 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. - I Portable Mode gemmes beskeder og konfigurationsfiler i samme mappe som programmet, i stedet for i den almindelige mappe til applikationsdata. Dette gør det nemt at køre Bitmessage fra et USB-stick. - - - - Willingly include unencrypted destination address when sending to a mobile device - Inkludér en ukrypteret destinationsaddresse når der sendes til mobile enheder - - - - Use Identicons - Brud identicons - - - - Reply below Quote - Besvar under citat - - - - Interface Language - Grænsefladesprog - - - - System Settings - system - Systemindstillinger - - - - User Interface - Brugergrænseflade - - - - Listening port - Indgående portnummer - - - - Listen for connections on port: - Tillad indgående forbindelser på port: - - - - UPnP: - UPnP: - - - - Bandwidth limit - Maksimal overførselshastighed - - - - Maximum download rate (kB/s): [0: unlimited] - Maksimal downloadhastighed (kB/s): [0: ubegrænset] - - - - Maximum upload rate (kB/s): [0: unlimited] - Maksimal uploadhastighed (kB/s): [0: ubegrænset] - - - - Proxy server / Tor - Proxyserver / Tor - - - - Type: - Type: - - - - Server hostname: - Servernavn: - - - - Port: - Port: - - - - Authentication - Autentifikation - - - - Username: - Brugernavn: - - - - Pass: - Kodeord: - - - - Listen for incoming connections when using proxy - Accepter indgående forbindelser når der benyttes en proxyserver - - - - none - ingen - - - - SOCKS4a - SOCKS4a - - - - SOCKS5 - SOCKS5 - - - - Network Settings - Netværksindstillinger - - - - Total difficulty: - Total sværhedsgrad: - - - - The 'Total difficulty' affects the absolute amount of work the sender must complete. Doubling this value doubles the amount of work. - - - - - Small message difficulty: - Små-beskeds-sværhedsgrad: - - - - 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. - Når nogen sender dig en besked skal deres computer først foretage nogen tunge beregninger. Sværhedsgraden er som standard 1. Du kan hæve sværhedsgraden for nye adresser ved at ændre værdierne her. Alle nye adresser vil kræve at afsenderen foretager beregninger med den nye sværhedsgrad. Der er dog en undtagelse: hvis du tilføjer en ven eller bekendt til din adressebog vil Bitmessage automatisk gøre dem opmærksom på at de kun skal foretage beregninger med en sværhedsgrad på 1, så snart du sender dem en besked. - - - - 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 - Krævet sværhedsgrad - - - - 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: - Maksimal acceptabel total sværhedsgrad - - - - Maximum acceptable small message difficulty: - Maksimal acceptabel småbeskeds-sværhedsgrad - - - - Max acceptable difficulty - Maks. acceptabel sværhedsgrad - - - - Hardware GPU acceleration (OpenCL) - 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> - <html><head/><body><p>Bitmessage kan benytte et andet Bitcoin-baseret program som hedder Namecoin til at gøre adresserne brugervenlige. For eksempel kan du bede din ven om at sende en besked til <span style=" font-style:italic;">test</span> i stedet for din lange Bitmessage-adresse.</p><p>(At få sin Bitmessage-adresse ind i namecoin er stadig rimelig kompliceret).</p><p>Bitmessage kan enten bruge namecoind direkte, eller bruge nmcontrol.</p></body></html> - - - - Host: - Vært: - - - - Password: - Kodeord: - - - - Test - Test - - - - Connect to: - Forbind til: - - - - Namecoind - Namecoind - - - - NMControl - NMControl - - - - Namecoin integration - Namcoin 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 - Giv op efter - - - - and - og - - - - days - dage - - - - months. - måneder. - - - - Resends Expire - Forsøg på genafsendelse stopper efter - - - diff --git a/src/translations/bitmessage_de.qm b/src/translations/bitmessage_de.qm deleted file mode 100644 index ef443a61..00000000 Binary files a/src/translations/bitmessage_de.qm and /dev/null differ diff --git a/src/translations/bitmessage_de.ts b/src/translations/bitmessage_de.ts deleted file mode 100644 index 69cdd2a8..00000000 --- a/src/translations/bitmessage_de.ts +++ /dev/null @@ -1,2599 +0,0 @@ - - - AddAddressDialog - - - Add new entry - Neuen Eintrag erstellen - - - - Label - Name oder Bezeichnung - - - - Address - Adresse - - - - EmailGatewayDialog - - - Email gateway - E-Mail Schnittstelle - - - - Register on email gateway - An E-Mail Schnittstelle registrieren - - - - Account status at email gateway - Statusanfrage der E-Mail Schnittstelle - - - - Change account settings at email gateway - Einstellungen der E-Mail Schnittstelle ändern - - - - Unregister from email gateway - Von der E-Mail Schnittstelle abmelden - - - - Email gateway allows you to communicate with email users. Currently, only the Mailchuck email gateway (@mailchuck.com) is available. - Die E-Mail Schnittstelle ermöglicht es, mit anderen E-Mail Nutzern zu kommunizieren. Zur Zeit ist nur die Mailchuck-E-Mail Schnittstelle (@mailchuck.com) verfügbar. - - - - Desired email address (including @mailchuck.com): - Gewünschte E-Mailaddresse (inkl. @mailchuck.com): - - - - @mailchuck.com - @mailchuck.com - - - - Registration failed: - Registrierung fehlgeschlagen: - - - - The requested email address is not available, please try a new one. - Die gewünschte E-Mailaddresse ist nicht verfügbar, bitte probieren Sie eine neue. - - - - Sending email gateway registration request - Der Registrierungsantrag für die E-Mail Schnittstelle wird versandt. - - - - Sending email gateway unregistration request - E-Mail Schnittestellen-Abmeldeantrag wird versandt - - - - Sending email gateway status request - E-Mail Schnittestellen Statusantrag wird versandt - - - - EmailGatewayRegistrationDialog - - - Registration failed: - - - - - 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: - - - - - 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, -# 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. - - # Diese Nachricht können Sie für die Änderung Ihrer Einstellungen an der -# E-Mail Schnittstelle verwenden. Unkommentieren Sie die Einstellung, die Sie -# ändern möchten. Dies sind die verfügbaren Einstellungen: -# -# pgp: server -# Die E-Mail-Schnittstelle wird für Sie PGP-Schlüssel erzeugen, und die -# Nachrichten für Sie unterschreiben, Unterschriften überprüfen, ver- und -# entschlüsseln. Verwenden Sie diese Option, wenn Sie PGP verwenden möchten -# aber es manuell zu umständig finden. Erfordert Abonnement. -# -# pgp: local -# Die E-Mail-Schnittstelle wird keine PGP-verarbeitung für Sie machen. -# Sie können auf die Verwendung von PGP entweder ganz verzichten, oder es lokal machen. -# -# attachments: yes -# Eingehende Dateianhänge von E-Mails werden zu MEGA.nz hochgeladen, und Sie -# können diese über einen Link in der Nachricht herunterladen. Erfordert -# Abonnement. -# -# attachments: no -# Anhänge werden ignoriert. -# -# archive: yes -# Ihre eingehende E-Mails werden auf dem Server archiviert. Nutzen Sie dies, -# wenn Sie Hilfe bei Debugging benötigen, oder eine Bestätigung von dritter -# Partei über die E-Mails benötigen. Diese Einstellung bedeutet jedoch, dass -# der Betreiber der Dienstleistung Ihre E-Mails auch lesen kann nachdem sie -# diese erhalten haben. -# -# archive: no -# Eingehende E-Mails werden gleich nach dem Übertragen zu Ihnen bei dem -# Schnittstellenbetreiber gelöscht. -# -# masterpubkey_btc: BIP44-xpub-Schlüssel oder electrum v1 Seed -# offset_btc: Ganzzahl (Voreingestellt auf 0) -# feeamount: Zahl mit bis zu 8 Nachkommastellen -# feecurrency: BTC, XBT, USD, EUR oder GBP -# Nutzen Sie diese Variablen, wenn Sie von den Absendern eine Zahlung -# verlangen. Wenn diese Option eingeschaltet ist und Sie eine E-Mail von einer -# noch nicht bekannten E-Mail-Addresse erhalten, wird die E-Mail abgelehnt und -# der Absender erhällt eine Zahlungsaufforderung in der spezifizierten Höhe. Da -# diese Methode deterministische öffentliche Schlüssel verwendet, erhalten Sie -# die Zahlungen direkt. Um die Funktion wieder auszuschalten, ändern Sie die -# "feeamount"-Variable auf 0. Erfordert Abonnement. - - - - - 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. - - - - - Queued. - In Warteschlange. - - - - Message sent. Waiting for acknowledgement. Sent at %1 - Nachricht gesendet. Warte auf Bestätigung. Zeitpunkt der Sendung: %1 - - - - Message sent. Sent at %1 - Nachricht gesendet. Zeitpunkt der Sendung: %1 - - - - Need to do work to send message. Work is queued. - - - - - 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. - - - - 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. - - - - 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.) - - - - 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? -(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 - - - - - 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 - 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 - more work your computer must do to send the message. A Time-To-Live of four or five days is often appropriate. - 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. - - - - 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. - 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 - - - - - Sending email gateway registration request - - - - - 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 - - - - - Sending email gateway status request - - - - - 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. - - - - 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. - - - - - 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? - Wenn Sie das Abonnement löschen, werden bereits erhaltene Nachrichten unaufrufbar. Vielleicht deaktivieren Sie das Abonnement lieber? Deaktivierte Abonnements erhalten keine neue Nachrichten, aber Sie können die bereits erhaltene aufrufen. - -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? - Wenn Sie das Chan löschen, die bereits erhaltene Nachrichten werden unaufrufbar. Vielleicht deaktivieren Sie den Chan lieber?. Deaktivierte Chans erhalten keine neue Nachrichten, aber Sie können die bereits erhaltene aufrufen. - -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. - - - - - 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. - - - - 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. - - - - Some data encoded in the address is too long. - Die in der Adresse kodierten Daten sind zu lang. - - - - Some data encoded in the address is malformed. - Einige in der Adresse kodierten Daten sind ungültig. - - - - Enter an address above. - - - - - 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). - - - - - 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: - - - - Subscriptions - Abonnements - - - - Add new Subscription - Neues Abonnement anlegen - - - - Chans - Chans - - - - Add Chan - Chan hinzufügen - - - - File - Datei - - - - Settings - Einstellungen - - - - Help - Hilfe - - - - Import keys - Schlüssel importieren - - - - Manage keys - Schlüssel verwalten - - - - Ctrl+Q - Strg+Q - - - - F1 - F1 - - - - Contact support - Unterstützung anfordern - - - - About - Über - - - - Regenerate deterministic addresses - Deterministische Adressen neu generieren - - - - Delete all trashed messages - Alle Nachrichten im Papierkorb löschen - - - - Join / Create chan - Chan beitreten / erstellen - - - - All accounts - Alle Identitäten - - - - 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. - - - - - New version of PyBitmessage is available: %1. Download it from https://github.com/Bitmessage/PyBitmessage/releases/latest - Neue Version von PyBitmessage steht zur Verfügung: %1. Sie können sie von https://github.com/Bitmessage/PyBitmessage/releases/latest herunterladen. - - - - Waiting for PoW to finish... %1% - Warte auf Abschluss von Berechnungen (PoW)... %1% - - - - Shutting down Pybitmessage... %1% - PyBitmessage wird beendet... %1% - - - - Waiting for objects to be sent... %1% - Warte auf Versand von Objekten... %1% - - - - Saving settings... %1% - Einstellungen werden gespeichert... %1% - - - - Shutting down core... %1% - Kern wird beendet... %1% - - - - 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 - - - - - 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. - - - - Error! Could not find sender address (your address) in the keys.dat file. - Fehler! Konnte die Absenderadresse (Ihre Adresse) in der keys.dat-Datei nicht finden. - - - - Doing work necessary to send broadcast... - Arbeit wird verrichtet, um Rundruf zu verschicken... - - - - Broadcast sent on %1 - Rundruf verschickt um %1 - - - - Encryption key was requested earlier. - Verschlüsselungscode wurde früher angefordert. - - - - Sending a request for the recipient's encryption key. - Anfrage nach dem Verschlüsselungscode des Empfängers wird versendet. - - - - Looking up the receiver's public key - Suche nach dem öffentlichen Schlüssel des Empfängers - - - - Problem: Destination is a mobile device who requests that the destination be included in the message but this is disallowed in your settings. %1 - Problem: Der Empfänger benutzt ein mobiles Gerät und erfordert eine unverschlüsselte Empfängeraddresse. Dies ist in Ihren Einstellungen jedoch nicht zulässig. 1% - - - - Doing work necessary to send message. -There is no required difficulty for version 2 addresses like this. - Arbeit für Nachrichtenversand wird verrichtet. -Version-2-Addressen wie die des Empfängers haben keine Schweirigkeitserforderungen. - - - - Doing work necessary to send message. -Receiver's required difficulty: %1 and %2 - Arbeit für Nachrichtenversand wird errichtet. Vom Empfänger geforderte Schwierigkeit: %1 und %2 - - - - Problem: The work demanded by the recipient (%1 and %2) is more difficult than you are willing to do. %3 - Problem: Die vom Empfänger verlangte Arbeit (%1 und %2) ist schwieriger, als Sie in den Einstellungen erlaubt haben. %3 - - - - Problem: You are trying to send a message to yourself or a chan but your encryption key could not be found in the keys.dat file. Could not encrypt message. %1 - Problem: Sie versuchen, eine Nachricht an sich zu versenden, aber Ihr Schlüssel befindet sich nicht in der keys.dat-Datei. Die Nachricht kann nicht verschlüsselt werden. 1% - - - - Doing work necessary to send message. - Arbeit wird verrichtet, um die Nachricht zu verschicken. - - - - Message sent. Waiting for acknowledgement. Sent on %1 - Nachricht gesendet. Auf Bestätigung wird gewartet. Zeitpunkt der Sendung: %1 - - - - Doing work necessary to request encryption key. - Arbeit wird verrichtet, um den Schlüssel nachzufragen... - - - - Broadcasting the public key request. This program will auto-retry if they are offline. - Anfrage nach dem öffentlichen Schlüssel läuft. Wenn der Besitzer nicht mit dem Netzwerk verbunden ist, wird ein Wiederholungsversuch unternommen. - - - - Sending public key request. Waiting for reply. Requested at %1 - Nachfrage nach dem öffentlichen Schlüssel läuft, auf Antwort wird gewartet. Nachgefragt am %1 - - - - UPnP port mapping established on port %1 - UPnP Port-Mapping eingerichtet auf Port %1 - - - - UPnP port mapping removed - UPnP Port-Mapping entfernt - - - - Mark all messages as read - Alle Nachrichten als gelesen markieren - - - - Are you sure you would like to mark all messages read? - Sind Sie sicher, dass Sie alle Nachrichten als gelesen markieren möchten? - - - - Doing work necessary to send broadcast. - Führe Arbeit aus, die notwendig ist zum Senden des Rundspruches. - - - - Proof of work pending - Arbeitsbeweis wird berechnet - - - - %n object(s) pending proof of work - %n Objekt wartet auf Berechnungen%n Objekte warten auf Berechnungen - - - - %n object(s) waiting to be distributed - %n Objekt wartet darauf, verteilt zu werden%n Objekte warten darauf, verteilt zu werden - - - - Wait until these tasks finish? - Warten bis diese Aufgaben erledigt sind? - - - - Problem communicating with proxy: %1. Please check your network settings. - Kommunikationsfehler mit dem Proxy: %1. Bitte überprüfen Sie Ihre Netzwerkeinstellungen. - - - - SOCKS5 Authentication problem: %1. Please check your SOCKS5 settings. - SOCKS5-Authentizierung fehlgeschlagen: %1. Bitte überprüfen Sie Ihre SOCKS5-Einstellungen. - - - - The time on your computer, %1, may be wrong. Please verify your settings. - Die Uhrzeit ihres Computers, %1, ist möglicherweise falsch. Bitte überprüfen Sie Ihre einstellungen. - - - - The name %1 was not found. - Der Name %1 wurde nicht gefunden. - - - - The namecoin query failed (%1) - Namecoin-abfrage fehlgeschlagen (%1) - - - - The namecoin query failed. - Namecoin-abfrage fehlgeschlagen. - - - - The name %1 has no valid JSON data. - Der Name %1 beinhaltet keine gültige JSON-Daten. - - - - The name %1 has no associated Bitmessage address. - Der Name %1 hat keine zugewiesene Bitmessageaddresse. - - - - Success! Namecoind version %1 running. - Erfolg! Namecoind Version %1 läuft. - - - - Success! NMControll is up and running. - Erfolg! NMControl läuft. - - - - Couldn't understand NMControl. - Kann NMControl nicht verstehen. - - - - Your GPU(s) did not calculate correctly, disabling OpenCL. Please report to the developers. - Ihre Grafikkarte hat inkorrekt berechnet, OpenCL wird deaktiviert. Bitte benachrichtigen Sie die Entwickler. - - - - Set notification sound... - Benachrichtigungsklang einstellen ... - - - - - Welcome to easy and secure Bitmessage - * send messages to other people - * send broadcast messages like twitter or - * discuss in chan(nel)s with other people - - -Willkommen zu einfachem und sicherem Bitmessage -* senden Sie Nachrichten an andere Leute -* senden Sie Rundrufe wie bei Twitter oder -* diskutieren Sie mit anderen Leuten in Chans - - - - not recommended for chans - für Chans nicht empfohlen - - - - Quiet Mode - stiller Modus - - - - Problems connecting? Try enabling UPnP in the Network Settings - Verbindungsprobleme? Versuchen Sie UPnP in den Netzwerkeinstellungen einzuschalten - - - - You are trying to send an email instead of a bitmessage. This requires registering with a gateway. Attempt to register? - Sie versuchen, eine E-Mail anstelle einer Bitmessage zu senden. Dies erfordert eine Registrierung bei einer Schnittstelle. Registrierung versuchen? - - - - Error: Bitmessage addresses start with BM- Please check the recipient address %1 - Fehler: Bitmessage Adressen starten mit BM- Bitte überprüfen Sie die Empfängeradresse %1 - - - - Error: The recipient address %1 is not typed or copied correctly. Please check it. - Fehler: Die Empfängeradresse %1 wurde nicht korrekt getippt oder kopiert. Bitte überprüfen. - - - - Error: The recipient address %1 contains invalid characters. Please check it. - Fehler: Die Empfängeradresse %1 beinhaltet ungültig Zeichen. Bitte überprüfen. - - - - Error: The version of the recipient address %1 is too high. Either you need to upgrade your Bitmessage software or your acquaintance is being clever. - Fehler: Die Empfängerdresseversion von %1 ist zu hoch. Entweder Sie müssen Ihre Bitmessage Software aktualisieren oder Ihr Bekannter ist sehr clever. - - - - Error: Some data encoded in the recipient address %1 is too short. There might be something wrong with the software of your acquaintance. - Fehler: Einige Daten die in der Empfängerdresse %1 codiert sind, sind zu kurz. Es könnte sein, dass etwas mit der Software Ihres Bekannten nicht stimmt. - - - - Error: Some data encoded in the recipient address %1 is too long. There might be something wrong with the software of your acquaintance. - Fehler: Einige Daten die in der Empfängeradresse %1 codiert sind, sind zu lang. Es könnte sein, dass etwas mit der Software Ihres Bekannten nicht stimmt. - - - - Error: Some data encoded in the recipient address %1 is malformed. There might be something wrong with the software of your acquaintance. - Fehler: Einige codierte Daten in der Empfängeradresse %1 sind ungültig. Es könnte etwas mit der Software Ihres Bekannten sein. - - - - Error: Something is wrong with the recipient address %1. - Fehler: Mit der Empfängeradresse %1 stimmt etwas nicht. - - - - Error: %1 - Fehler: %1 - - - - From %1 - Von %1 - - - - Synchronisation pending - Synchronisierung läuft - - - - Bitmessage hasn't synchronised with the network, %n object(s) to be downloaded. If you quit now, it may cause delivery delays. Wait until the synchronisation finishes? - Bitmessage ist nicht synchronisiert, %n Objekt wurde noch nicht heruntergeladen. Wenn Sie das Programm jetzt beenden, kann es zu Zustellverzögerungen kommen. Warten bis die Synchronisierung abgeschlossen ist?Bitmessage ist nicht synchronisiert, %n Objekte wurden noch nicht heruntergeladen. Wenn Sie das Programm jetzt beenden, kann es zu Zustellverzögerungen kommen. Warten bis die Synchronisierung abgeschlossen ist? - - - - Not connected - Nicht verbunden - - - - Bitmessage isn't connected to the network. If you quit now, it may cause delivery delays. Wait until connected and the synchronisation finishes? - Bitmessage ist nicht mit dem Netzwerk verbunden. Wenn Sie das Programm jetzt beenden, kann es zu Zustellverzögerungen kommen. Warten bis eine Verbindung besteht und die Synchronisierung abgeschlossen ist? - - - - Waiting for network connection... - Warte auf Netzwerkverbindung... - - - - Waiting for finishing synchronisation... - Warte auf Synchronisationsabschluss... - - - - You have already set a notification sound for this address book entry. Do you really want to overwrite it? - Sie haben bereits einen Benachrichtigungsklang für diesen Adressbucheintrag gesetzt. Möchten Sie ihn wirklich überschreiben? - - - - Error occurred: could not load message from disk. - Fehler: Nachricht konnte nicht geladen werden. - - - - Display the %n recent broadcast(s) from this address. - Den letzten %1 Rundruf von dieser Addresse anzeigen.Die letzten %1 Rundrufe von dieser Addresse anzeigen. - - - - Go online - Mit dem Netzwerk verbinden - - - - Go offline - Trennen vom Netzwerk - - - - Clear - Löschen - - - - inbox - Eingang - - - - new - Neu - - - - sent - - - - - trash - - - - - MessageView - - - Follow external link - Externen Link folgen - - - - The link "%1" will open in a browser. It may be a security risk, it could de-anonymise you or download malicious data. Are you sure? - Der Link "%1" wird in Browser geöffnet. Es kann ein Sicherheitsrisiko darstellen, es könnte Sie de-anonymisieren oder schädliche Aktivitäten durchführen. Sind Sie sicher? - - - - HTML detected, click here to display - HTML gefunden, klicken Sie hier um anzuzeigen - - - - Click here to disable HTML - Klicken Sie hier um HTML-Anzeige zu deaktivieren - - - - 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: - - - - <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) - - - - NewSubscriptionDialog - - - Add new entry - Neuen Eintrag erstellen - - - - Label - Name oder Bezeichnung - - - - Address - Adresse - - - - Enter an address above. - Bitte geben Sie oben eine Adresse ein. - - - - 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 - - - - - version ? - - - - - <html><head/><body><p>Distributed under the MIT/X11 software license; see <a href="http://www.opensource.org/licenses/mit-license.php"><span style=" text-decoration: underline; color:#0000ff;">http://www.opensource.org/licenses/mit-license.php</span></a></p></body></html> - <html><head/><body><p>Veröffentlicht unter der MIT/X11 Software-Lizenz, siehe unter <a href="http://www.opensource.org/licenses/mit-license.php"><span style=" text-decoration: underline; color:#0000ff;">http://www.opensource.org/licenses/mit-license.php</span></a></p></body></html> - - - - This is Beta software. - Dies ist Beta-Software. - - - - <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) - Liste als Blacklist verwenden (Erlaubt alle eingehenden Nachrichten, außer von Adressen auf der Blacklist) - - - - Use a Whitelist (Block all incoming messages except those on the Whitelist) - Liste als Whitelist verwenden (Erlaubt keine eingehenden Nachrichten, außer von Adressen auf der Whitelist) - - - - Add new entry - Neuen Eintrag erstellen - - - - Name or Label - Name oder Bezeichnung - - - - Address - Adresse - - - - Blacklist - Blacklist (Liste gesperrter Adressen) - - - - Whitelist - Whitelist (Liste zugelassener Adressen) - - - - connectDialog - - - Bitmessage - Bitmessage - - - - Bitmessage won't connect to anyone until you let it. - Bitmessage wird sich nicht verbinden, wenn Sie es nicht möchten. - - - - Connect now - Jetzt verbinden - - - - Let me configure special network settings first - Zunächst spezielle Netzwerkeinstellungen vornehmen - - - - Work offline - Nicht verbinden - - - - helpDialog - - - Help - Hilfe - - - - <a href="https://bitmessage.org/wiki/PyBitmessage_Help">https://bitmessage.org/wiki/PyBitmessage_Help</a> - <a href="https://bitmessage.org/wiki/PyBitmessage_Help">https://bitmessage.org/wiki/PyBitmessage_Help</a> - - - - As Bitmessage is a collaborative project, help can be found online in the Bitmessage Wiki: - Bitmessage ist ein kollaboratives Projekt. Hilfe finden Sie online im Bitmessage-Wiki: - - - - iconGlossaryDialog - - - Icon Glossary - Icon Glossar - - - - You have no connections with other peers. - Sie haben keine Verbindung mit anderen Netzwerkteilnehmern. - - - - You have made at least one connection to a peer using an outgoing connection but you have not yet received any incoming connections. Your firewall or home router probably isn't configured to forward incoming TCP connections to your computer. Bitmessage will work just fine but it would help the Bitmessage network if you allowed for incoming connections and will help you be a better-connected node. - Sie haben mindestes eine Verbindung mit einem Netzwerkteilnehmer über eine ausgehende Verbindung, aber Sie haben noch keine eingehende Verbindung. Ihre Firewall oder Ihr Router ist vermutlich nicht richtig konfiguriert, um eingehende TCP-Verbindungen an Ihren Computer weiterzuleiten. Bitmessage wird gut funktionieren, jedoch helfen Sie dem Netzwerk, wenn Sie eingehende Verbindungen erlauben. Es hilft auch Ihnen, schneller und mehr Verbindungen ins Netzwerk aufzubauen. - - - - You are using TCP port ?. (This can be changed in the settings). - - - - - 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 - - - Total connections: - 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 - - - - - 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 - - - - - 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: - - - - - 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 - - - - 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: - - - - 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 - - - - You must check (or not check) this box just like you did (or didn't) when you made your addresses the first time. - Sie müssen diese Option auswählen (oder nicht auswählen) wie Sie es gemacht haben, als Sie Ihre Adresse das erste Mal erstellt haben. - - - - If you have previously made deterministic addresses but lost them due to an accident (like hard drive failure), you can regenerate them here. If you used the random number generator to make your addresses then this form will be of no use to you. - Wenn Sie bereits deterministische Adressen erstellt haben, aber diese durch einen Unfall (zum Beispiel durch eine defekte Festplatte) verloren haben, können Sie sie hier regenerieren. Dies funktioniert nur dann, wenn Sie bei der erstmaligen Erstellung Ihrer Adressen nicht den Zufallsgenerator verwendet haben. - - - - 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 - - - - Close to tray - Schliessen in den Infobereich (Tray) - - - - 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 - - - - User Interface - Benutzerinterface - - - - Listening port - Empfangender 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) - - - - - <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> - - - - 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): - - - \ No newline at end of file diff --git a/src/translations/bitmessage_en.qm b/src/translations/bitmessage_en.qm deleted file mode 100644 index 4751f4ca..00000000 Binary files a/src/translations/bitmessage_en.qm and /dev/null differ diff --git a/src/translations/bitmessage_en.ts b/src/translations/bitmessage_en.ts deleted file mode 100644 index 05e9cc4b..00000000 --- a/src/translations/bitmessage_en.ts +++ /dev/null @@ -1,2175 +0,0 @@ - - - - AddAddressDialog - - - Add new entry - Add new entry - - - - Label - Label - - - - Address - Address - - - - EmailGatewayDialog - - - Email gateway - Email gateway - - - - Register on email gateway - Register on email gateway - - - - Account status at email gateway - Account status at email gateway - - - - Change account settings at email gateway - Change account settings at email gateway - - - - Unregister from 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. - 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): - Desired email address (including @mailchuck.com): - - - - 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: - - - - Email gateway registration - 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: - 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 - - - # 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. - - # 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. - - - - - 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 - - - - Enable - Enable - - - - Disable - Disable - - - - Set avatar... - Set avatar... - - - - Copy address to clipboard - Copy address to clipboard - - - - Special address behavior... - Special address behavior... - - - - Email gateway - Email gateway - - - - Delete - 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. - - - - 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. - - - - 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 - - - - Send - Send - - - - Subscribe - Subscribe - - - - Channel - Channel - - - - Quit - 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. - 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? - 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.) - 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? - 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 - more work your computer must do to send the message. A Time-To-Live of four or five days is often appropriate. - 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. - - - - 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 - - - - Address is valid. - Address is valid. - - - - The address you entered was invalid. Ignoring it. - 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? - 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? - 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? - 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). - - - - Bitmessage - Bitmessage - - - - Identities - Identities - - - - New Identity - New Identity - - - - Search - Search - - - - All - All - - - - To - To - - - - From - From - - - - Subject - Subject - - - - Message - Message - - - - Received - Received - - - - Messages - Messages - - - - Address book - Address book - - - - Address - Address - - - - Add Contact - Add Contact - - - - Fetch Namecoin ID - Fetch Namecoin ID - - - - Subject: - Subject: - - - - From: - From: - - - - To: - To: - - - - Send ordinary Message - Send ordinary Message - - - - Send Message to your Subscribers - Send Message to your Subscribers - - - - TTL: - TTL: - - - - Subscriptions - Subscriptions - - - - Add new Subscription - Add new Subscription - - - - Chans - Chans - - - - Add Chan - Add Chan - - - - File - File - - - - Settings - Settings - - - - Help - Help - - - - Import keys - Import keys - - - - Manage keys - Manage keys - - - - Ctrl+Q - Ctrl+Q - - - - F1 - F1 - - - - Contact support - Contact support - - - - About - About - - - - Regenerate deterministic addresses - Regenerate deterministic addresses - - - - Delete all trashed messages - Delete all trashed messages - - - - Join / Create chan - Join / Create chan - - - - All accounts - All accounts - - - - Zoom level %1% - Zoom level %1% - - - - Error: You cannot add the same address to your list twice. Perhaps rename the existing one if you want. - Error: You cannot add the same address to your list twice. Perhaps rename the existing one if you want. - - - - Add new entry - 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 day(s) - - %n day - %n days - - - - - Shutting down PyBitmessage... %1% - Shutting down PyBitmessage... %1% - - - - Sent - Sent - - - - Generating one new address - Generating one new address - - - - Done generating address. Doing work necessary to broadcast it... - Done generating address. Doing work necessary to broadcast it... - - - - Generating %1 new addresses. - Generating %1 new addresses. - - - - %1 is already in 'Your Identities'. Not adding it again. - %1 is already in 'Your Identities'. Not adding it again. - - - - Done generating address - 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. - - - - Error! Could not find sender address (your address) in the keys.dat file. - 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 - - - - Encryption key was requested earlier. - Encryption key was requested earlier. - - - - Sending a request for the recipient's encryption key. - Sending a request for the recipient's encryption key. - - - - Looking up the receiver's public 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 - 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. -There is no required difficulty for version 2 addresses like this. - - - - Doing work necessary to send message. -Receiver's required difficulty: %1 and %2 - 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: 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 - 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. - - - - Message sent. Waiting for acknowledgement. Sent on %1 - Message sent. Waiting for acknowledgement. Sent on %1 - - - - Doing work necessary to request encryption key. - Doing work necessary to request encryption key. - - - - Broadcasting the public key request. This program will auto-retry if they are offline. - Broadcasting the public key request. This program will auto-retry if they are offline. - - - - Sending public key request. Waiting for reply. Requested at %1 - 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 - - - Create new Address - 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: - 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;">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 random number generator to make an address - - - - Use a passphrase to make addresses - Use a passphrase to make addresses - - - - Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter - Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter - - - - Make deterministic addresses - Make deterministic addresses - - - - Address version number: 4 - Address version number: 4 - - - - In addition to your passphrase, you must remember these numbers: - In addition to your passphrase, you must remember these numbers: - - - - Passphrase - Passphrase - - - - Number of addresses to make based on your passphrase: - Number of addresses to make based on your passphrase: - - - - Stream number: 1 - Stream number: 1 - - - - Retype passphrase - Retype passphrase - - - - Randomly generate address - Randomly generate address - - - - Label (not shown to anyone except you) - Label (not shown to anyone except you) - - - - Use the most available stream - Use the most available stream - - - - (best if this is the first of many addresses you will create) - (best if this is the first of many addresses you will create) - - - - Use the same stream as an existing address - Use the same stream as an existing address - - - - (saves you some bandwidth and processing power) - (saves you some bandwidth and processing power) - - - - NewSubscriptionDialog - - - Add new entry - Add new entry - - - - Label - Label - - - - Address - Address - - - - Enter an address above. - Enter an address above. - - - - SpecialAddressBehaviorDialog - - - Special Address Behavior - Special Address Behavior - - - - Behave as a normal address - Behave as a normal address - - - - Behave as a pseudo-mailing-list 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). - 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: - Name of the pseudo-mailing-list: - - - - 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> - - - - blacklist - - - Use a Blacklist (Allow all incoming messages except those on the 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) - Use a Whitelist (Block all incoming messages except those on the Whitelist) - - - - Add new entry - Add new entry - - - - Name or Label - Name or Label - - - - Address - Address - - - - Blacklist - Blacklist - - - - Whitelist - Whitelist - - - - connectDialog - - - Bitmessage - Bitmessage - - - - Bitmessage won't connect to anyone until you let it. - Bitmessage won't connect to anyone until you let it. - - - - Connect now - Connect now - - - - Let me configure special network settings first - Let me configure special network settings first - - - - helpDialog - - - Help - 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: - As Bitmessage is a collaborative project, help can be found online in the Bitmessage Wiki: - - - - iconGlossaryDialog - - - Icon Glossary - Icon Glossary - - - - You have no connections with other peers. - 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 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 are using TCP port ?. (This can be changed in the settings). - - - - You do have connections with other peers and your firewall is correctly configured. - You do have connections with other peers and your firewall is correctly configured. - - - - networkstatus - - - Total connections: - Total connections: - - - - Since startup: - Since startup: - - - - Processed 0 person-to-person messages. - Processed 0 person-to-person messages. - - - - Processed 0 public keys. - Processed 0 public keys. - - - - Processed 0 broadcasts. - Processed 0 broadcasts. - - - - Inventory lookups per second: 0 - Inventory lookups per second: 0 - - - - Objects to be synced: - Objects to be synced: - - - - Stream # - Stream # - - - - Connections - Connections - - - - Since startup on %1 - Since startup on %1 - - - - Down: %1/s Total: %2 - Down: %1/s Total: %2 - - - - Up: %1/s Total: %2 - Up: %1/s Total: %2 - - - - Total Connections: %1 - Total Connections: %1 - - - - Inventory lookups per second: %1 - Inventory lookups per second: %1 - - - - Up: 0 kB/s - Up: 0 kB/s - - - - Down: 0 kB/s - Down: 0 kB/s - - - - Network Status - Network Status - - - - byte(s) - - byte - bytes - - - - - Object(s) to be synced: %n - - Object to be synced: %n - Objects 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 broadcast message(s). - - Processed %n broadcast message. - Processed %n broadcast messages. - - - - - Processed %n public key(s). - - Processed %n public key. - Processed %n public keys. - - - - - newChanDialog - - - Dialog - Dialog - - - - Create a new chan - Create a new chan - - - - Join a chan - Join a chan - - - - Create 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>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: - 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>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: - Chan bitmessage address: - - - - regenerateAddressesDialog - - - Regenerate Existing Addresses - Regenerate Existing Addresses - - - - Regenerate existing addresses - Regenerate existing addresses - - - - Passphrase - Passphrase - - - - Number of addresses to make based on your passphrase: - Number of addresses to make based on your passphrase: - - - - Address version number: - Address version number: - - - - Stream number: - Stream number: - - - - 1 - 1 - - - - Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter - 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. - 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. - 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 - Settings - - - - Start Bitmessage on user login - Start Bitmessage on user login - - - - Tray - Tray - - - - Start Bitmessage in the tray (don't show main window) - Start Bitmessage in the tray (don't show main window) - - - - Minimize to tray - Minimize to tray - - - - Close to tray - Close to tray - - - - Show notification when message received - Show notification when message received - - - - Run in Portable Mode - 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. - 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 - Willingly include unencrypted destination address when sending to a mobile device - - - - Use Identicons - Use Identicons - - - - Reply below Quote - Reply below Quote - - - - Interface Language - Interface Language - - - - System Settings - system - System Settings - - - - User Interface - User Interface - - - - Listening port - Listening port - - - - Listen for connections on port: - Listen for connections on port: - - - - UPnP: - UPnP: - - - - Bandwidth limit - Bandwidth limit - - - - Maximum download rate (kB/s): [0: unlimited] - Maximum download rate (kB/s): [0: unlimited] - - - - Maximum upload rate (kB/s): [0: unlimited] - Maximum upload rate (kB/s): [0: unlimited] - - - - Proxy server / Tor - Proxy server / Tor - - - - Type: - Type: - - - - Server hostname: - Server hostname: - - - - Port: - Port: - - - - Authentication - Authentication - - - - Username: - Username: - - - - Pass: - Pass: - - - - Listen for incoming connections when using proxy - Listen for incoming connections when using proxy - - - - none - none - - - - SOCKS4a - SOCKS4a - - - - SOCKS5 - SOCKS5 - - - - Network Settings - Network Settings - - - - Total difficulty: - Total difficulty: - - - - The 'Total difficulty' affects the absolute amount of work the sender must complete. Doubling this value doubles the amount of work. - The 'Total difficulty' affects the absolute amount of work the sender must complete. Doubling this value doubles the amount of work. - - - - Small message difficulty: - 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. - 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. - 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 - 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. - 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 total difficulty: - - - - Maximum acceptable small message difficulty: - Maximum acceptable small message difficulty: - - - - Max acceptable difficulty - Max acceptable difficulty - - - - Hardware GPU acceleration (OpenCL) - 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> - <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: - Host: - - - - Password: - Password: - - - - Test - Test - - - - Connect to: - Connect to: - - - - 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>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 - Give up after - - - - and - and - - - - days - days - - - - months. - months. - - - - Resends Expire - Resends Expire - - - diff --git a/src/translations/bitmessage_en_pirate.qm b/src/translations/bitmessage_en_pirate.qm deleted file mode 100644 index 69e6bde8..00000000 Binary files a/src/translations/bitmessage_en_pirate.qm and /dev/null differ diff --git a/src/translations/bitmessage_en_pirate.ts b/src/translations/bitmessage_en_pirate.ts deleted file mode 100644 index 69642a96..00000000 --- a/src/translations/bitmessage_en_pirate.ts +++ /dev/null @@ -1,2095 +0,0 @@ - - - - AddAddressDialog - - - Add new entry - Add yee new entry - - - - Label - Label - - - - Address - Address - - - - 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. - - - - - 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: - - - - - 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, -# 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. - - - - - - 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 - - - - - Save message as... - - - - - Mark Unread - - - - - New - - - - - Enable - - - - - Disable - - - - - Set avatar... - - - - - Copy address to clipboard - - - - - Special address behavior... - - - - - Email gateway - - - - - Delete - - - - - Send message to this address - - - - - Subscribe to this address - - - - - Add New Address - - - - - Copy destination address to clipboard - - - - - Force send - - - - - One of your addresses, %1, is an old version 1 address. Version 1 addresses are no longer supported. May we delete it now? - - - - - 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 - - - - - 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. - - - - - 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... - - - - - 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 - Address - - - - 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 - Settings - - - - Help - Help - - - - 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? - - - - - 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: - Here yee may gen'rate arrrs many arrddresses as yee like. Indeed, crrreatin' and abandonin' arrddresses be encouraged. Yee may generrrate arrddresses by usin' either random numbers or by usin' a passphrase. If you be usin' a passphrase, t' arrddress be called a "deterministic" arrddress. -T' 'Random Number' option be selected by default but deterministic arrddresses 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;">Pros:<br/></span>Yee may recreate your arrddresses on any computer from memory. <br/>Yee shant worry about backing up yee keys.dat file as long as yee shall remember t' passphrase. <br/><span style=" font-weight:600;">Cons:<br/></span>Yee must remember (or scribe down) t' passphrase if yee expect t' be able to recreate your keys if they be lost. <br/>Yee must remember t' arrddress version number and t' stream number along with yee passphrase. <br/>If yee choose a weak passphrase and someone on t' great see of internet can brute-force it like a real pirate, they can read yee messages and send messages as you.</p></body></html> - - - - Use a random number generator to make an address - Use yee random number generator to make an arrddress - - - - Use a passphrase to make addresses - - - - - Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter - Spend several minutes of extra computing time to make t' arrddress(es) 1 or 2 charrracters sharter. - - - - Make deterministic addresses - Make deterministic arrddresses - - - - Address version number: 4 - - - - - In addition to your passphrase, you must remember these numbers: - In addition to yee passphrase, yee must rememberrr these numbers: - - - - Passphrase - Passphrase - - - - Number of addresses to make based on your passphrase: - Number of addresses t' make based on yee passphrase: - - - - Stream number: 1 - Stream number: 1 - - - - Retype passphrase - RRRetype passphrase matey: - - - - Randomly generate address - RRRandomly generate address - - - - Label (not shown to anyone except you) - Label (not shown t' any sailors 'cept you) - - - - Use the most available stream - Use t' most available stream - - - - (best if this is the first of many addresses you will create) - (best if this be t' first of many arrddresses you be creatin') - - - - Use the same stream as an existing address - Use t' same stream as an existing arrddress - - - - (saves you some bandwidth and processing power) - (saves yee some bandwidth and processing powerrr) - - - - NewSubscriptionDialog - - - Add new entry - Add yee new entry - - - - Label - Label - - - - Address - Address - - - - Enter an address above. - - - - - SpecialAddressBehaviorDialog - - - Special Address Behavior - Special Arrddress Behavior - - - - Behave as a normal address - Behave yee as normal arrddress - - - - Behave as a pseudo-mailing-list address - Behave yee as pseudo-mailing-list arrddress - - - - Mail received to a pseudo-mailing-list address will be automatically broadcast to subscribers (and thus will be public). - Mail rrreceived to yee pseudo-mailing-list arrddress be automatically broadcast to subscribers (all sailors in public be able to see yee message). - - - - Name of the pseudo-mailing-list: - 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. - - - - - 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 - Add yee new entry - - - - Name or Label - - - - - Address - Address - - - - Blacklist - - - - - Whitelist - - - - - connectDialog - - - Bitmessage - - - - - Bitmessage won't connect to anyone until you let it. - - - - - Connect now - - - - - Let me configure special network settings first - - - - - helpDialog - - - Help - 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: - Bitmessage be project of many, a pirates help can be found online in the Bitmessage Wiki: - - - - iconGlossaryDialog - - - Icon Glossary - Symbol-Glossar - - - - You have no connections with other peers. - Sie haben keine Verbindung zu anderen Teilnehmern. - - - - 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 be usin' TCP port ?. (This be changed in settings). - - - - You do have connections with other peers and your firewall is correctly configured. - Yee have connections with other peers and pirates,yee firewall is correctly configured. - - - - networkstatus - - - 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). - - - - - - - - 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> - - - - - 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: - - - - - regenerateAddressesDialog - - - Regenerate Existing Addresses - Regenerate Existin' Arrddresses - - - - Regenerate existing addresses - Regenerate existin' addresses - - - - Passphrase - Passphrase - - - - Number of addresses to make based on your passphrase: - Number of arrddresses to make based on yee passphrase: - - - - Address version number: - - - - - Stream number: - Stream numberrr: - - - - 1 - 1 - - - - Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter - Spend several minutes extra computin' time to make yee address(es) 1 arr 2 characters sharter - - - - You must check (or not check) this box just like you did (or didn't) when you made your addresses the first time. - Yee must check (arr not check) this box just like yee did (or didn't) when yee made your arrddresses 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. - If yee have previously made deterministic arrddresses but yee lost them due to an accident (like losin' yee pirate ship), yee can regenerate them here. If yee used t' random number generator to make yee addresses then this form be of no use to you. - - - - settingsDialog - - - Settings - Settings - - - - Start Bitmessage on user login - Start yee Bitmessage on userrr login - - - - Tray - - - - - Start Bitmessage in the tray (don't show main window) - Start yee Bitmessage in t' tray (don't show main window) - - - - Minimize to tray - Minimize to yee tray - - - - Close to tray - - - - - Show notification when message received - Show yee a notification when message received - - - - Run in Portable Mode - Run in yee 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. - In Portable Mode, messages and config files are stored in t' same directory as yee program rather than t' normal application-data folder. This makes it convenient to run Bitmessage from a USB thumb drive or wooden leg. - - - - Willingly include unencrypted destination address when sending to a mobile device - - - - - Use Identicons - - - - - Reply below Quote - - - - - Interface Language - - - - - System Settings - system - - - - - User Interface - User Interface - - - - Listening port - Listenin' port - - - - Listen for connections on port: - Listen for connections on yee port: - - - - UPnP: - - - - - Bandwidth limit - - - - - Maximum download rate (kB/s): [0: unlimited] - - - - - Maximum upload rate (kB/s): [0: unlimited] - - - - - Proxy server / Tor - Proxy server / Tor - - - - Type: - Type: - - - - Server hostname: - Server hostname: - - - - Port: - Port: - - - - Authentication - Authentication - - - - Username: - Username: - - - - Pass: - Pass: - - - - Listen for incoming connections when using proxy - - - - - none - none - - - - SOCKS4a - SOCKS4a - - - - SOCKS5 - SOCKS5 - - - - Network Settings - Network Settings - - - - Total difficulty: - Total difficulty: - - - - The 'Total difficulty' affects the absolute amount of work the sender must complete. Doubling this value doubles the amount of work. - T' 'Total difficulty' affects the absolute amount of work yee sender must complete. Doubling this value be doublin' t' amount of work. - - - - Small message difficulty: - 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. - When a pirate sends yee a message, their computer must first complete a load of work. T' difficulty of t' work, by default, is 1. Yee may raise this default for new arrddresses yee create by changin' the values here. Any new arrddresses you be createin' will require senders to meet t' higher difficulty. There be one exception: if yee add a friend or pirate to yee arrddress book, Bitmessage be automatically notifyin' them when yee next send a message that they needin' be only complete t' 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. - T' 'Small message difficulty' mostly only affects t' difficulty of sending small messages. Doubling this value be makin' it almost twice as difficult to send a small message but doesn't really affect large messages. - - - - Demanded difficulty - 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: - - - - - 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 - - - - diff --git a/src/translations/bitmessage_eo.qm b/src/translations/bitmessage_eo.qm deleted file mode 100644 index 77c20edf..00000000 Binary files a/src/translations/bitmessage_eo.qm and /dev/null differ diff --git a/src/translations/bitmessage_eo.ts b/src/translations/bitmessage_eo.ts deleted file mode 100644 index 5707a390..00000000 --- a/src/translations/bitmessage_eo.ts +++ /dev/null @@ -1,2637 +0,0 @@ - - - AddAddressDialog - - - Add new entry - Aldoni novan elementon - - - - Label - Etikedo - - - - Address - Adreso - - - - EmailGatewayDialog - - - Email gateway - Retpoŝta kluzo - - - - Register on email gateway - Registri ĉe retpoŝta kluzo - - - - Account status at email gateway - Stato de retpoŝt-kluza konto - - - - Change account settings at email gateway - Ŝanĝu agordojn de konto ĉe retpoŝta kluzo - - - - Unregister from email gateway - Malregistri de retpoŝta kluzo - - - - Email gateway allows you to communicate with email users. Currently, only the Mailchuck email gateway (@mailchuck.com) is available. - Retpoŝta kluzo ebligas al vi komunikadi kun retpoŝtaj uzantoj. Nuntempe, nur la retpoŝta kluzo de Mailchuck (@mailchuck.com) estas disponebla. - - - - Desired email address (including @mailchuck.com): - Dezirata retpoŝta adreso (kune kun @mailchuck.com): - - - - @mailchuck.com - @mailchuck.com - - - - Registration failed: - Registrado malsukcesis: - - - - The requested email address is not available, please try a new one. - La dezirata retpoŝtadreso ne estas disponebla, bonvolu provi alian. - - - - Sending email gateway registration request - Sendado de peto pri registrado ĉe retpoŝta kluzo - - - - Sending email gateway unregistration request - Sendado de peto pri malregistrado de retpoŝta kluzo - - - - Sending email gateway status request - Sendado de peto pri stato de retpoŝta kluzo - - - - EmailGatewayRegistrationDialog - - - Registration failed: - - - - - 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: - - - - - 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, -# 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. - - - - - - # 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: -# -# 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. -# -# pgp: local -# La retpoŝta kluzo ne faros PGP-operaciojn kiel vi. Vi povas aŭ ne uzi PGP-on -# ĝenerale aŭ uzi ĝin loke. -# -# 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. -# -# attachments: no -# Kunsendaĵoj estos ignorataj. -# -# 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 -# retmesaĝoj. Tamen tio signifas, ke la manipulisto de servo eblos legi viajn -# 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. -# -# masterpubkey_btc: BIP44 xpub ŝlosilo aŭ electrum v1 publika fontsendo (seed) -# offset_btc: entjera (integer) datumtipo (implicite 0) -# feeamount: nombro kun maksimume 8 decimalaj lokoj -# 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. - - - - - 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 - - - - Move to Trash - Movi al rubujo - - - - Undelete - Malforigi - - - - View HTML code as formatted text - Montri HTML-n kiel aranĝitan tekston - - - - Save message as... - Konservi mesaĝon kiel… - - - - Mark Unread - Marki kiel nelegitan - - - - New - Nova - - - - Enable - Ŝalti - - - - Disable - Malŝalti - - - - Set avatar... - Agordi avataron… - - - - Copy address to clipboard - Kopii adreson al tondejo - - - - Special address behavior... - Speciala sinteno de adreso… - - - - Email gateway - Retpoŝta kluzo - - - - Delete - Forigi - - - - Send message to this address - Sendi mesaĝon al tiu adreso - - - - Subscribe to this address - Aboni tiun adreson - - - - Add New Address - Aldoni novan adreson - - - - Copy destination address to clipboard - Kopii cel-adreson al tondejo - - - - Force send - Devigi sendadon - - - - One of your addresses, %1, is an old version 1 address. Version 1 addresses are no longer supported. May we delete it now? - Iu de viaj adresoj, %1, estas malnova versio 1 adreso. Versioj 1 adresoj ne estas jam subtenataj. Ĉu ni povas forigi ĝin? - - - - Waiting for their encryption key. Will request it again soon. - Atendado je ilia ĉifroŝlosilo. Baldaŭ petos ĝin denove. - - - - Encryption key request queued. - - - - - Queued. - En atendovico. - - - - Message sent. Waiting for acknowledgement. Sent at %1 - Mesaĝo sendita. Atendado je konfirmo. Sendita je %1 - - - - Message sent. Sent at %1 - Mesaĝo sendita. Sendita je %1 - - - - Need to do work to send message. Work is queued. - - - - - 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. - - - - 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 -%1. -Estas grava, ke vi faru sekurkopion de tiu dosiero. - - - - Open keys.dat? - Ĉu malfermi keys.dat? - - - - You may manage your keys by editing the keys.dat file stored in the same directory as this program. It is important that you back up this file. Would you like to open the file now? (Be sure to close Bitmessage before making any changes.) - Vi povas administri viajn ŝlosilojn per redakti la dosieron “keys.dat” en la sama dosierujo kiel tiu programo. Estas grava ke vi faru sekurkopion de tiu dosiero. Ĉu vi volas malfermi la dosieron nun? (Bonvolu certigi ke Bitmesaĝo estas fermita antaŭ fari ŝanĝojn.) - - - - 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 -%1. -Estas grava, ke vi faru sekurkopion de tiu dosiero. Ĉu vi volas malfermi la dosieron nun? (Bonvolu certigi ke Bitmesaĝo estas fermita antaŭ fari ŝanĝojn.) - - - - Delete trash? - Ĉu malplenigi rubujon? - - - - Are you sure you want to delete all trashed messages? - Ĉu vi certe volas forviŝi ĉiujn mesaĝojn el la rubujo? - - - - bad passphrase - malprava pasvorto - - - - You must type your passphrase. If you don't have one then this is not the form for you. - Vi devas tajpi vian pasvorton. Se vi ne havas pasvorton, tiu ĉi ne estas la prava formularo por vi. - - - - Bad address version number - Erara numero de adresversio - - - - Your address version number must be a number: either 3 or 4. - Via numero de adresversio devas esti: aŭ 3 aŭ 4. - - - - Your address version number must be either 3 or 4. - Via numero de adresversio devas esti: aŭ 3 aŭ 4. - - - - 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 - 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. - - - - Message too long - Mesaĝo tro longa - - - - The message that you are trying to send is too long by %1 bytes. (The maximum is 261644 bytes). Please cut it down before sending. - La mesaĝon kiun vi provis sendi estas tro longa je %1 bitokoj. (La maksimumo estas 261644 bitokoj.) Bonvolu mallongigi ĝin antaŭ sendado. - - - - Error: Your account wasn't registered at an email gateway. Sending registration now as %1, please wait for the registration to be processed before retrying sending. - Eraro: via konto ne estas registrita je retpoŝta kluzo. Registranta nun kiel %1, bonvolu atendi ĝis la registrado finos antaŭ vi reprovos sendi iun ajn. - - - - 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. - Eraro: Vi devas elekti sendontan adreson. Se vi ne havas iun, iru al langeto 'Viaj identigoj'. - - - - Address version number - Numero de adresversio - - - - Concerning the address %1, Bitmessage cannot understand address version numbers of %2. Perhaps upgrade Bitmessage to the latest version. - Dum prilaborado de adreso adreso %1, Bitmesaĝo ne povas kompreni numerojn %2 de adresversioj. Eble ĝisdatigu Bitmesaĝon al la plej nova versio. - - - - Stream number - Fluo numero - - - - Concerning the address %1, Bitmessage cannot handle stream numbers of %2. Perhaps upgrade Bitmessage to the latest version. - Dum prilaborado de adreso %1, Bitmesaĝo ne povas priservi %2 fluojn numerojn. Eble ĝisdatigu Bitmesaĝon al la plej nova versio. - - - - Warning: You are currently not connected. Bitmessage will do the work necessary to send the message but it won't send until you connect. - Atentu: vi ne estas nun konektita. Bitmesaĝo faros necesan laboron por sendi mesaĝon, tamen ĝi ne sendos ĝin antaŭ vi konektos. - - - - Message queued. - Mesaĝo envicigita. - - - - Your 'To' field is empty. - Via "Ricevonto"-kampo malplenas. - - - - Right click one or more entries in your address book and select 'Send message to this address'. - Dekstre alklaku kelka(j)n elemento(j)n en via adresaro kaj elektu 'Sendi mesaĝon al tiu adreso'. - - - - Fetched address from namecoin identity. - Venigis adreson de namecoin-a identigo. - - - - New Message - Nova mesaĝo - - - - From - - - - - Sending email gateway registration request - - - - - Address is valid. - Adreso estas ĝusta. - - - - The address you entered was invalid. Ignoring it. - La adreso kiun vi enmetis estas malĝusta. Ignoras ĝin. - - - - Error: You cannot add the same address to your address book twice. Try renaming the existing one if you want. - Eraro: Vi ne povas duoble aldoni la saman adreson al via adresaro. Provu renomi la ekzistan se vi volas. - - - - Error: You cannot add the same address to your subscriptions twice. Perhaps rename the existing one if you want. - Eraro: Vi ne povas aldoni duoble la saman adreson al viaj abonoj. Eble renomi la ekzistan se vi volas. - - - - Restart - Restartigi - - - - You must restart Bitmessage for the port number change to take effect. - Vi devas restartigi Bitmesaĝon por ke ŝanĝo de numero de pordo efektivigu. - - - - Bitmessage will use your proxy from now on but you may want to manually restart Bitmessage now to close existing connections (if any). - Bitmesaĝo uzos retperanton (proxy) ekde nun, sed eble vi volas permane restartigi Bitmesaĝon nun, por ke ĝi fermu eblajn ekzistajn konektojn. - - - - Number needed - Numero bezonata - - - - Your maximum download and upload rate must be numbers. Ignoring what you typed. - Maksimumaj elŝutrapido kaj alŝutrapido devas esti numeroj. Ignoras kion vi enmetis. - - - - Will not resend ever - Resendos neniam - - - - Note that the time limit you entered is less than the amount of time Bitmessage waits for the first resend attempt therefore your messages will never be resent. - Rimarku, ke la templimon vi enmetis estas pli malgranda ol tempo dum kiu Bitmesaĝo atendas por resendi unuafoje, do viaj mesaĝoj estos senditaj neniam. - - - - Sending email gateway unregistration request - - - - - Sending email gateway status request - - - - - Passphrase mismatch - Pasfrazoj malsamas - - - - The passphrase you entered twice doesn't match. Try again. - Entajpitaj pasfrazoj malsamas. Provu denove. - - - - Choose a passphrase - Elektu pasfrazon - - - - You really do need a passphrase. - Vi ja vere bezonas pasfrazon. - - - - Address is gone - Adreso foriris - - - - Bitmessage cannot find your address %1. Perhaps you removed it? - Bitmesaĝo ne povas trovi vian adreson %1. Ĉu eble vi forviŝis ĝin? - - - - Address disabled - Adreso malŝaltita - - - - Error: The address from which you are trying to send is disabled. You'll have to enable it on the 'Your Identities' tab before using it. - Eraro: la adreso kun kiu vi provas sendi estas malŝaltita. Vi devos ĝin ŝalti en la langeto 'Viaj identigoj' antaŭ uzi ĝin. - - - - Entry added to the Address Book. Edit the label to your liking. - - - - - Entry added to the blacklist. Edit the label to your liking. - Aldonis elementon al la listo de blokitoj. 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. - - - - Moved items to trash. - Movis elementojn al rubujo. - - - - Undeleted item. - Malforigis elementon. - - - - Save As... - Konservi kiel… - - - - Write error. - Skriberaro. - - - - No addresses selected. - Neniu adreso elektita. - - - - If you delete the subscription, messages that you already received will become inaccessible. Maybe you can consider disabling the subscription instead. Disabled subscriptions will not receive new messages, but you can still view messages you already received. - -Are you sure you want to delete the subscription? - Se vi forigos abonon, mesaĝojn kiujn vi jam ricevis, igos neatingeblajn. Eble vi devus anstataŭ malaktivigi abonon. Malaktivaj abonoj ne ricevos novajn mesaĝojn, tamen vi plu povos legi mesaĝojn kiujn ci jam ricevis. - -Ĉu vi certe volas forigi la abonon? - - - - If you delete the channel, messages that you already received will become inaccessible. Maybe you can consider disabling the channel instead. Disabled channels will not receive new messages, but you can still view messages you already received. - -Are you sure you want to delete the channel? - Se vi forigos kanalon, mesaĝojn kiujn vi jam ricevis, igos neatingeblajn. Eble vi devus anstataŭ malaktivigi kanalon. Malaktivaj kanaloj ne ricevos novajn mesaĝojn, tamen vi plu povos legi mesaĝojn kiujn ci jam ricevis. - -Ĉu vi certe volas forigi la kanalon? - - - - Do you really want to remove this avatar? - Ĉu vi certe volas forviŝi tiun ĉi avataron? - - - - You have already set an avatar for this address. Do you really want to overwrite it? - Vi jam agordis avataron por tiu ĉi adreso. Ĉu vi vere volas superskribi ĝin? - - - - Start-on-login not yet supported on your OS. - Starto-dum-ensaluto ne estas ankoraŭ ebla en via operaciumo. - - - - Minimize-to-tray not yet supported on your OS. - Plejetigo al taskopleto ne estas ankoraŭ ebla en via operaciumo. - - - - Tray notifications not yet supported on your OS. - Taskopletaj sciigoj ne estas ankoraŭ eblaj en via operaciumo. - - - - Testing... - Testado… - - - - This is a chan address. You cannot use it as a pseudo-mailing list. - - - - - The address should start with ''BM-'' - La adreso komencu kun “BM-” - - - - The address is not typed or copied correctly (the checksum failed). - La adreso ne estis ĝuste tajpita aŭ kopiita (kontrolsumo malsukcesis). - - - - The version number of this address is higher than this software can support. Please upgrade Bitmessage. - La numero de adresversio estas pli alta ol tiu, kiun la programo poveblas subteni. Bonvolu ĝisdatigi Bitmesaĝon. - - - - The address contains invalid characters. - La adreso enhavas malpermesitajn simbolojn. - - - - Some data encoded in the address is too short. - Iuj datumoj koditaj en la adreso estas tro mallongaj. - - - - Some data encoded in the address is too long. - Iuj datumoj koditaj en la adreso estas tro longaj. - - - - Some data encoded in the address is malformed. - Iuj datumoj koditaj en la adreso estas misformitaj. - - - - Enter an address above. - - - - - Address is an old type. We cannot display its past broadcasts. - Malnova tipo de adreso. Ne povas montri ĝiajn antaŭajn elsendojn. - - - - There are no recent broadcasts from this address to display. - Neniaj lastatempaj elsendoj de tiu ĉi adreso por montri. - - - - You are using TCP port %1. (This can be changed in the settings). - - - - - Bitmessage - Bitmesaĝo - - - - Identities - Identigoj - - - - New Identity - Nova identigo - - - - Search - Serĉi - - - - All - Ĉio - - - - To - Al - - - - From - De - - - - Subject - Temo - - - - Message - Mesaĝo - - - - Received - Ricevita je - - - - Messages - Mesaĝoj - - - - Address book - Etikedo - - - - Address - Adreso - - - - Add Contact - Aldoni kontakton - - - - Fetch Namecoin ID - Venigi Namecoin ID - - - - Subject: - Temo: - - - - From: - De: - - - - To: - Al: - - - - Send ordinary Message - Sendi ordinaran mesaĝon - - - - Send Message to your Subscribers - Sendi mesaĝon al viaj abonantoj - - - - TTL: - Vivdaŭro: - - - - Subscriptions - Abonoj - - - - Add new Subscription - Aldoni novan abonon - - - - Chans - Kanaloj - - - - Add Chan - Aldoni kanalon - - - - File - Dosiero - - - - Settings - Agordoj - - - - Help - Helpo - - - - Import keys - Enporti ŝlosilojn - - - - Manage keys - Administri ŝlosilojn - - - - Ctrl+Q - Stir+Q - - - - F1 - F1 - - - - Contact support - Peti pri helpo - - - - About - Pri - - - - Regenerate deterministic addresses - Regeneri antaŭkalkuleblan adreson - - - - Delete all trashed messages - Forviŝi ĉiujn mesaĝojn el rubujo - - - - Join / Create chan - Anigi / krei kanalon - - - - All accounts - Ĉiuj kontoj - - - - Zoom level %1% - Pligrandigo: %1 - - - - Error: You cannot add the same address to your list twice. Perhaps rename the existing one if you want. - Eraro: Vi ne povas aldoni duoble la saman adreson al via listo. Eble renomi la jaman se vi volas. - - - - Add new entry - Aldoni novan elementon - - - - Display the %1 recent broadcast(s) from this address. - - - - - New version of PyBitmessage is available: %1. Download it from https://github.com/Bitmessage/PyBitmessage/releases/latest - La nova versio de PyBitmessage estas disponebla: %1. Elŝutu ĝin de https://github.com/Bitmessage/PyBitmessage/releases/latest - - - - Waiting for PoW to finish... %1% - Atendado ĝis laborpruvo finiĝos… %1% - - - - Shutting down Pybitmessage... %1% - Fermado de PyBitmessage… %1% - - - - Waiting for objects to be sent... %1% - Atendado ĝis objektoj estos senditaj… %1% - - - - Saving settings... %1% - Konservado de agordoj… %1% - - - - Shutting down core... %1% - Fermado de kerno… %1% - - - - Stopping notifications... %1% - Haltigado de sciigoj… %1% - - - - Shutdown imminent... %1% - Fermado tuj… %1% - - - - %n hour(s) - %n horo%n horoj - - - - %n day(s) - %n tago%n tagoj - - - - Shutting down PyBitmessage... %1% - Fermado de PyBitmessage… %1% - - - - Sent - Senditaj - - - - Generating one new address - Kreado de unu nova adreso - - - - Done generating address. Doing work necessary to broadcast it... - Adreso kreita. Kalkulado de laborpruvo, kiu endas por elsendi ĝin… - - - - Generating %1 new addresses. - Kreado de %1 novaj adresoj. - - - - %1 is already in 'Your Identities'. Not adding it again. - %1 jam estas en ‘Viaj Identigoj’. Ĝi ne estos aldonita ree. - - - - Done generating address - Ĉiuj adresoj estas kreitaj - - - - SOCKS5 Authentication problem: %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… - - - - Broadcast sent on %1 - Elsendo sendita je %1 - - - - Encryption key was requested earlier. - Peto pri ĉifroŝlosilo jam sendita. - - - - Sending a request for the recipient's encryption key. - Sendado de peto pri ĉifroŝlosilo de ricevonto. - - - - Looking up the receiver's public key - Serĉado de publika ĉifroŝlosilo de ricevonto - - - - Problem: Destination is a mobile device who requests that the destination be included in the message but this is disallowed in your settings. %1 - Eraro: celadreso estas portebla aparato kiu necesas, ke la celadreso estu enhavita en la mesaĝo, sed tio estas malpermesita ne viaj agordoj. %1 - - - - Doing work necessary to send message. -There is no required difficulty for version 2 addresses like this. - Kalkulado de laborpruvo, kiu endas por sendi mesaĝon. -Malfacilaĵo ne estas bezonata por adresoj versioj 2, kiel tiu ĉi adreso. - - - - Doing work necessary to send message. -Receiver's required difficulty: %1 and %2 - Kalkulado de laborpruvo, kiu endas por sendi mesaĝon. -Ricevonto postulas malfacilaĵon: %1 kaj %2 - - - - Problem: The work demanded by the recipient (%1 and %2) is more difficult than you are willing to do. %3 - Eraro: la demandita laboro de la ricevonto (%1 kaj %2) estas pli malfacila ol vi pretas fari. %3 - - - - Problem: You are trying to send a message to yourself or a chan but your encryption key could not be found in the keys.dat file. Could not encrypt message. %1 - Eraro: Vi provis sendi mesaĝon al vi mem aŭ al kanalo, tamen via ĉifroŝlosilo ne estas trovebla en la dosiero keys.dat. Mesaĝo ne povis esti ĉifrita. %1 - - - - Doing work necessary to send message. - Kalkulado de laborpruvo, kiu endas por sendi mesaĝon. - - - - Message sent. Waiting for acknowledgement. Sent on %1 - Mesaĝo sendita. Atendado je konfirmo. Sendita je %1 - - - - Doing work necessary to request encryption key. - Kalkulado de laborpruvo, kiu endas por peti pri ĉifroŝlosilo. - - - - Broadcasting the public key request. This program will auto-retry if they are offline. - Elsendado de peto pri publika ĉifroŝlosilo. La programo reprovos se ili estas eksterrete. - - - - Sending public key request. Waiting for reply. Requested at %1 - Sendado de peto pri publika ĉifroŝlosilo. Atendado je respondo. Petis je %1 - - - - UPnP port mapping established on port %1 - UPnP pord-mapigo farita je pordo %1 - - - - UPnP port mapping removed - UPnP pord-mapigo forigita - - - - Mark all messages as read - Marki ĉiujn mesaĝojn kiel legitajn - - - - Are you sure you would like to mark all messages read? - Ĉu vi certe volas marki ĉiujn mesaĝojn kiel legitajn? - - - - Doing work necessary to send broadcast. - Kalkulado de laborpruvo, kiu endas por sendi elsendon. - - - - Proof of work pending - Laborpruvo haltigita - - - - %n object(s) pending proof of work - Haltigis laborpruvon por %n objektoHaltigis laborpruvon por %n objektoj - - - - %n object(s) waiting to be distributed - %n objekto atendas je sendato%n objektoj atendas je sendato - - - - Wait until these tasks finish? - Ĉu atendi ĝis tiujn taskojn finos? - - - - 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 - - - - 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: - - - - <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> - - - - 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 - - - - Make deterministic addresses - Fari antaŭkalkuleblan adreson - - - - Address version number: 4 - Numero de adresversio: 4 - - - - In addition to your passphrase, you must remember these numbers: - Kune kun vian pasfrazon vi devas memori jenajn numerojn: - - - - Passphrase - Pasfrazo - - - - Number of addresses to make based on your passphrase: - Nombro da farotaj adresoj bazante sur via pasfrazo: - - - - Stream number: 1 - Numero de fluo: 1 - - - - Retype passphrase - Pasfrazo ree - - - - Randomly generate address - Hazardnombra adreso - - - - Label (not shown to anyone except you) - Etikedo (ne montrata al iu ajn escepte de vi) - - - - Use the most available stream - Uzi la plej disponeblan fluon - - - - (best if this is the first of many addresses you will create) - (plej bone se ĝi estas la unua de ĉiuj adresojn vi kreos) - - - - Use the same stream as an existing address - Uzi la saman fluon kiel ekzistan adreson - - - - (saves you some bandwidth and processing power) - (konservas iomete da ret-trafiko kaj komput-povo) - - - - NewSubscriptionDialog - - - Add new entry - Aldoni novan elementon - - - - Label - Etikedo - - - - Address - Adreso - - - - Enter an address above. - Entajpu adreson supre. - - - - SpecialAddressBehaviorDialog - - - Special Address Behavior - Speciala sinteno de adreso - - - - Behave as a normal address - Sintenadi kiel normala adreso - - - - Behave as a pseudo-mailing-list address - Sintenadi kiel kvazaŭ-elsendlista adreso - - - - Mail received to a pseudo-mailing-list address will be automatically broadcast to subscribers (and thus will be public). - Mesaĝoj alvenintaj al kvazaŭ-dissendlisto estos aŭtomate dissenditaj al abonantoj (do ili estos publikaj). - - - - Name of the pseudo-mailing-list: - Nomo de kvazaŭ-dissendlisto: - - - - This is a chan address. You cannot use it as a pseudo-mailing list. - Tio ĉi estas kanaladreso. Vi ne povas ĝin uzi kiel kvazaŭ-dissendolisto. - - - - aboutDialog - - - About - Pri - - - - PyBitmessage - - - - - 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. - - - - <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>Kopirajto © 2012-2016 Jonathan WARREN<br/>Kopirajto © 2013-2017 La Programistoj de Bitmesaĝo</p></body></html> - - - - blacklist - - - 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) - - - - 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) - - - - Add new entry - Aldoni novan elementon - - - - Name or Label - Nomo aŭ etikedo - - - - Address - Adreso - - - - Blacklist - Blokataj kontaktoj - - - - Whitelist - Permesataj kontaktoj - - - - 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 - - - - 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: - - - - iconGlossaryDialog - - - Icon Glossary - Piktograma Glosaro - - - - You have no connections with other peers. - Vi havas neniujn konektojn al aliaj samtavolanoj. - - - - You have made at least one connection to a peer using an outgoing connection but you have not yet received any incoming connections. Your firewall or home router probably isn't configured to forward incoming TCP connections to your computer. Bitmessage will work just fine but it would help the Bitmessage network if you allowed for incoming connections and will help you be a better-connected node. - Vi konektis almenaŭ al unu samtavolano uzante elirantan konekton, sed vi ankoraŭ ne ricevis enirantajn konektojn. Via fajroŝirmilo (firewall) aŭ hejma enkursigilo (router) verŝajne estas agordita por ne plusendi enirantajn TCP-konektojn al via komputilo. Bitmesaĝo funkcios sufiĉe bone, sed helpus al la bitmesaĝa reto se vi permesus enirantajn konektojn kaj tiel estus pli bone konektita nodo. - - - - You are using TCP port ?. (This can be changed in the settings). - - - - - 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 - - - Total connections: - Ĉ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 - - - - - Since startup on %1 - Ekde lanĉo de la programo je %1 - - - - Down: %1/s Total: %2 - Elŝuto: %1/s Sume: %2 - - - - Up: %1/s Total: %2 - Alŝuto: %1/s Sume: %2 - - - - Total Connections: %1 - Ĉiuj konektoj: %1 - - - - Inventory lookups per second: %1 - Petoj pri inventaro en sekundo: %1 - - - - Up: 0 kB/s - Alŝuto: 0 kB/s - - - - Down: 0 kB/s - Elŝuto: 0 kB/s - - - - Network Status - Reto - - - - 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 - - - - - 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: - - - - - 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. - - - - 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: - - - - Address version number: - Numero de adresversio: - - - - Stream number: - Numero de fluo: - - - - 1 - 1 - - - - Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter - Pasigi kelkajn minutojn aldone kompute por krei adreso(j)n mallongaj je 1 aŭ 2 signoj - - - - You must check (or not check) this box just like you did (or didn't) when you made your addresses the first time. - Vi devas marki (aŭ ne marki) tiun ĉi mark-butonon samkiel vi faris kiam vi generis vian adresojn unuafoje. - - - - If you have previously made deterministic addresses but lost them due to an accident (like hard drive failure), you can regenerate them here. If you used the random number generator to make your addresses then this form will be of no use to you. - Se vi antaŭe kreis antaŭkalkuleblajn (determinismajn) adresojn sed perdis ilin akcidente (ekz. en diska paneo), vi povas regeneri ilin tie ĉi. Se vi uzis la hazardnombran generilon por krei viajn adresojn, tiu ĉi 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 - - - - 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. - - - - 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 - - - - 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 - - - - Type: - Speco: - - - - Server hostname: - Servil-nomo: - - - - Port: - Pordo: - - - - Authentication - Aŭtentigo - - - - Username: - Uzantnomo: - - - - Pass: - Pasvorto: - - - - Listen for incoming connections when using proxy - Aŭskulti pri alvenaj konektoj kiam dum uzado de retperanto - - - - none - neniu - - - - SOCKS4a - SOCKS4a - - - - SOCKS5 - SOCKS5 - - - - Network Settings - Agordoj de reto - - - - Total difficulty: - Tuta malfacilaĵo: - - - - The 'Total difficulty' affects the absolute amount of work the sender must complete. Doubling this value doubles the amount of work. - La 'Tuta malfacilaĵo' efikas sur la tuta kvalito da laboro, kiun la sendonto devos fari. Duobligo de tiu valoro, duobligas la kvanton de laboro. - - - - Small message difficulty: - Et-mesaĝa malfacilaĵo: - - - - When someone sends you a message, their computer must first complete some work. The difficulty of this work, by default, is 1. You may raise this default for new addresses you create by changing the values here. Any new addresses you create will require senders to meet the higher difficulty. There is one exception: if you add a friend or acquaintance to your address book, Bitmessage will automatically notify them when you next send a message that they need only complete the minimum amount of work: difficulty 1. - Kiam iu ajn sendas al vi mesaĝon, lia komputilo devas unue fari iom da laboro. La malfacilaĵo de tiu laboro implicite estas 1. Vi povas pligrandigi tiun valoron por novaj adresoj, kiujn vi generos per ŝanĝo de ĉi-tiaj valoroj. Ĉiuj novaj adresoj kreotaj de vi bezonos por ke sendontoj akceptu pli altan malfacilaĵon. Estas unu escepto: se vi aldonos amikon al via adresaro, Bitmesaĝo aŭtomate sciigos lin kiam vi sendos mesaĝon, ke li bezonos fari nur minimuman kvaliton da laboro: malfacilaĵo 1. - - - - 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) - - - - - <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> - - - - Host: - Gastiga servilo: - - - - Password: - Pasvorto: - - - - Test - Testi - - - - Connect to: - Konekti al: - - - - Namecoind - Namecoind - - - - NMControl - NMControl - - - - Namecoin integration - Integrigo kun Namecoin - - - - <html><head/><body><p>By default, if you send a message to someone and he is offline for more than two days, Bitmessage will send the message again after an additional two days. This will be continued with exponential backoff forever; messages will be resent after 5, 10, 20 days ect. until the receiver acknowledges them. Here you may change that behavior by having Bitmessage give up after a certain number of days or months.</p><p>Leave these input fields blank for the default behavior. </p></body></html> - <html><head/><body><p>Implicite se vi sendas mesaĝon al iu kaj li estos eksterrete por iomete da tempo, Bitmesaĝo provos resendi mesaĝon iam poste, kaj iam pli poste. La programo pluigos resendi mesaĝon ĝis sendonto konfirmos liveron. Tie ĉi vi povas ŝanĝi kiam Bitmesaĝo devos rezigni je sendado.</p><p>Lasu tiujn kampojn malplenaj por antaŭagordita sinteno.</p></body></html> - - - - Give up after - Rezigni post - - - - and - kaj - - - - days - tagoj - - - - months. - monatoj. - - - - Resends Expire - Resenda fortempiĝo - - - - Hide connection notifications - Ne montri sciigojn pri konekto - - - - Maximum outbound connections: [0: none] - Maksimume da eligaj konektoj: [0: senlima] - - - - 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 deleted file mode 100644 index 8cb08a3a..00000000 Binary files a/src/translations/bitmessage_fr.qm and /dev/null differ diff --git a/src/translations/bitmessage_fr.ts b/src/translations/bitmessage_fr.ts deleted file mode 100644 index 149fd1ef..00000000 --- a/src/translations/bitmessage_fr.ts +++ /dev/null @@ -1,2600 +0,0 @@ - - - AddAddressDialog - - - Add new entry - Ajouter une nouvelle entrée - - - - Label - Étiquette - - - - Address - Adresse - - - - 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: - - - - - 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: - - - - - 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, -# 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. - - - - - - # 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. - - - - - - MainWindow - - - Reply to sender - Répondre à l’expéditeur - - - - Reply to channel - Répondre au canal - - - - Add sender to your Address Book - Ajouter l’expéditeur au carnet d’adresses - - - - Add sender to your Blacklist - Ajouter l’expéditeur à votre liste noire - - - - Move to Trash - Envoyer à la Corbeille - - - - Undelete - Restaurer - - - - View HTML code as formatted text - Voir le code HTML comme du texte formaté - - - - Save message as... - Enregistrer le message sous… - - - - Mark Unread - Marquer comme non-lu - - - - New - Nouvelle - - - - Enable - Activer - - - - Disable - Désactiver - - - - Set avatar... - Configurer l’avatar - - - - Copy address to clipboard - Copier l’adresse dans le presse-papier - - - - Special address behavior... - Comportement spécial de l’adresse… - - - - Email gateway - Passerelle de courriel - - - - Delete - Effacer - - - - Send message to this address - Envoyer un message à cette adresse - - - - Subscribe to this address - S’abonner à cette adresse - - - - Add New Address - Ajouter une nouvelle adresse - - - - Copy destination address to clipboard - Copier l’adresse de destination dans le presse-papier - - - - Force send - Forcer l’envoi - - - - One of your addresses, %1, is an old version 1 address. Version 1 addresses are no longer supported. May we delete it now? - Une de vos adresses, %1, est une vieille adresse de la version 1. Les adresses de la version 1 ne sont plus supportées. Nous pourrions la supprimer maintenant? - - - - Waiting for their encryption key. Will request it again soon. - En attente de la clé de chiffrement. Une nouvelle requête sera bientôt lancée. - - - - Encryption key request queued. - - - - - 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. - - - - - 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. - 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. - - - - 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 - - - - - 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 - 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 - more work your computer must do to send the message. A Time-To-Live of four or five days is often appropriate. - Le TTL, ou Time-To-Live (temps à vivre) est la durée de temps durant laquelle le réseau va détenir le message. -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 - - - - - 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. - Erreur : Vous devez spécifier une adresse d’expéditeur. Si vous n’en avez pas, rendez-vous dans l’onglet "Vos identités". - - - - Address version number - Numéro de version de l’adresse - - - - Concerning the address %1, Bitmessage cannot understand address version numbers of %2. Perhaps upgrade Bitmessage to the latest version. - Concernant l’adresse %1, Bitmessage ne peut pas comprendre les numéros de version de %2. Essayez de mettre à jour Bitmessage vers la dernière version. - - - - Stream number - Numéro de flux - - - - Concerning the address %1, Bitmessage cannot handle stream numbers of %2. Perhaps upgrade Bitmessage to the latest version. - Concernant l’adresse %1, Bitmessage ne peut pas supporter les nombres de flux de %2. Essayez de mettre à jour Bitmessage vers la dernière version. - - - - Warning: You are currently not connected. Bitmessage will do the work necessary to send the message but it won't send until you connect. - Avertissement : Vous êtes actuellement déconnecté. Bitmessage fera le travail nécessaire pour envoyer le message mais il ne sera pas envoyé tant que vous ne vous connecterez pas. - - - - Message queued. - Message mis en file d’attente. - - - - Your 'To' field is empty. - Votre champ "Vers" est vide. - - - - Right click one or more entries in your address book and select 'Send message to this address'. - Cliquez droit sur une ou plusieurs entrées dans votre carnet d’adresses puis 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 - - - - - Sending email gateway registration request - - - - - Address is valid. - L’adresse est valide. - - - - The address you entered was invalid. Ignoring it. - L’adresse que vous avez entrée est invalide. Adresse ignorée. - - - - Error: You cannot add the same address to your address book twice. Try renaming the existing one if you want. - Erreur : Vous ne pouvez pas ajouter une adresse déjà présente dans votre carnet d’adresses. Essayez de renommer l’adresse existante. - - - - Error: You cannot add the same address to your subscriptions twice. Perhaps rename the existing one if you want. - Erreur : vous ne pouvez pas ajouter la même adresse deux fois à vos abonnements. Peut-être que vous pouvez renommer celle qui existe si vous le souhaitez. - - - - Restart - Redémarrer - - - - You must restart Bitmessage for the port number change to take effect. - Vous devez redémarrer Bitmessage pour que le changement de port prenne effet. - - - - Bitmessage will use your proxy from now on but you may want to manually restart Bitmessage now to close existing connections (if any). - Bitmessage utilisera votre proxy dorénavant, mais vous pouvez redémarrer manuellement Bitmessage maintenant afin de fermer des connexions existantes (si il y en existe). - - - - Number needed - Nombre requis - - - - Your maximum download and upload rate must be numbers. Ignoring what you typed. - Vos taux maximum de téléchargement et de téléversement doivent être des nombres. Ce que vous avez tapé est ignoré. - - - - Will not resend ever - Ne renverra jamais - - - - Note that the time limit you entered is less than the amount of time Bitmessage waits for the first resend attempt therefore your messages will never be resent. - Notez que la limite de temps que vous avez entrée est plus courte que le temps d’attente respecté par Bitmessage avant le premier essai de renvoi, par conséquent votre message ne sera jamais renvoyé. - - - - Sending email gateway unregistration request - - - - - Sending email gateway status request - - - - - Passphrase mismatch - Phrases secrètes différentes - - - - The passphrase you entered twice doesn't match. Try again. - Les phrases secrètes entrées sont différentes. Réessayez. - - - - Choose a passphrase - Choisissez une phrase secrète - - - - You really do need a passphrase. - Vous devez vraiment utiliser une phrase secrète. - - - - Address is gone - L’adresse a disparu - - - - Bitmessage cannot find your address %1. Perhaps you removed it? - Bitmessage ne peut pas trouver votre adresse %1. Peut-être l’avez-vous supprimée? - - - - Address disabled - Adresse désactivée - - - - Error: The address from which you are trying to send is disabled. You'll have to enable it on the 'Your Identities' tab before using it. - Erreur : L’adresse avec laquelle vous essayez de communiquer est désactivée. Vous devez d’abord l’activer dans l’onglet "Vos identités" avant de l’utiliser. - - - - Entry added to the Address Book. Edit the label to your liking. - - - - - 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? - Si vous supprimez cet abonnement, les messages que vous avez déjà reçus deviendront inaccessibles. Peut-être que vous devriez considérez d’abord de désactiver l’abonnement. Les abonnements désactivés ne reçoivent pas de nouveaux messages, mais vous pouvez encore voir ceux que vous avez déjà reçus. - -Ê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? - Si vous supprimez ce canal, les messages que vous avez déjà reçus deviendront inaccessibles. Peut-être que vous devriez considérez d’abord de désactiver le canal. Les canaux désactivés ne reçoivent pas de nouveaux messages, mais vous pouvez encore voir ceux que vous avez déjà reçus. - -Ê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. - - - - - 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. - - - - - 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). - - - - - Bitmessage - Bitmessage - - - - Identities - Identités - - - - New Identity - Nouvelle identité - - - - Search - Chercher - - - - All - Tous - - - - To - Vers - - - - From - De - - - - Subject - Sujet - - - - Message - Message - - - - Received - Reçu - - - - Messages - Messages - - - - Address book - Carnet d’adresses - - - - Address - Adresse - - - - Add Contact - Ajouter un contact - - - - Fetch Namecoin ID - Récupère l’ID Namecoin - - - - Subject: - Sujet : - - - - From: - De : - - - - To: - Vers : - - - - Send ordinary Message - Envoyer un message ordinaire - - - - Send Message to your Subscribers - Envoyer un message à vos abonnés - - - - TTL: - TTL : - - - - Subscriptions - Abonnements - - - - Add new Subscription - Ajouter un nouvel abonnement - - - - Chans - Canaux - - - - Add Chan - Ajouter un canal - - - - File - Fichier - - - - Settings - Paramètres - - - - Help - Aide - - - - Import keys - Importer les clés - - - - Manage keys - Gérer les clés - - - - Ctrl+Q - Ctrl+Q - - - - F1 - F1 - - - - Contact support - Contacter le support - - - - About - À propos - - - - Regenerate deterministic addresses - Regénérer les clés déterministes - - - - Delete all trashed messages - Supprimer tous les messages dans la corbeille - - - - Join / Create chan - Rejoindre / créer un canal - - - - All accounts - Tous les comptes - - - - Zoom level %1% - Niveau de zoom %1% - - - - Error: You cannot add the same address to your list twice. Perhaps rename the existing one if you want. - Erreur : vous ne pouvez pas ajouter la même adresse deux fois à votre liste. Vous pouvez peut-être, si vous le souhaitez, renommer celle qui existe déjà. - - - - Add new entry - Ajouter une nouvelle entrée - - - - Display the %1 recent broadcast(s) from this address. - - - - - 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 - - - - - 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 : - - - - <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 : - - - - 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) - - - - NewSubscriptionDialog - - - Add new entry - Ajouter une nouvelle entrée - - - - Label - Étiquette - - - - Address - Adresse - - - - Enter an address above. - Entrez ci-dessus une adresse. - - - - SpecialAddressBehaviorDialog - - - Special Address Behavior - Comportement spécial de l’adresse - - - - Behave as a normal address - Se comporter comme une adresse normale - - - - Behave as a pseudo-mailing-list address - Se comporter comme une adresse d’une pseudo liste de diffusion - - - - Mail received to a pseudo-mailing-list address will be automatically broadcast to subscribers (and thus will be public). - Un mail reçu sur une adresse d’une pseudo liste de diffusion sera automatiquement diffusé aux abonnés (et sera donc public). - - - - Name of the pseudo-mailing-list: - Nom de la pseudo liste de diffusion : - - - - This is a chan address. You cannot use it as a pseudo-mailing list. - Ceci est une adresse de canal. Vous ne pouvez pas l’utiliser en tant que pseudo liste de diffusion. - - - - aboutDialog - - - About - À propos - - - - PyBitmessage - - - - - version ? - - - - - <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> - - - - blacklist - - - Use a Blacklist (Allow all incoming messages except those on the Blacklist) - Utiliser une liste noire (Blacklist. Cela autorise tous les messages entrants sauf ceux présents sur la liste noire) - - - - Use a Whitelist (Block all incoming messages except those on the Whitelist) - Utiliser une liste blanche (Whitelist. Bloque tous les messages entrants sauf ceux présents sur cette liste blanche) - - - - Add new entry - Ajouter une nouvelle entrée - - - - Name or Label - Nom ou Étiquette - - - - Address - Adresse - - - - Blacklist - Liste noire - - - - Whitelist - Liste blanche - - - - 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: - - - - 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). - - - - - 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 - - - Total connections: - Total de connexions: - - - - Since startup: - Depuis le démarrage : - - - - Processed 0 person-to-person messages. - Traité 0 messages de personne à personne. - - - - Processed 0 public keys. - Traité 0 clés publiques. - - - - Processed 0 broadcasts. - Traité 0 message de diffusion. - - - - Inventory lookups per second: 0 - Consultations d’inventaire par seconde : 0 - - - - Objects to be synced: - Objets à synchroniser : - - - - Stream # - Flux N° - - - - Connections - - - - - Since startup on %1 - Démarré depuis le %1 - - - - Down: %1/s Total: %2 - Téléchargées : %1/s Total : %2 - - - - Up: %1/s Total: %2 - Téléversées : %1/s Total : %2 - - - - Total Connections: %1 - Total des connexions : %1 - - - - Inventory lookups per second: %1 - Consultations d’inventaire par seconde : %1 - - - - Up: 0 kB/s - Téléversement : 0 kO/s - - - - Down: 0 kB/s - Téléchargement : 0 kO/s - - - - Network Status - Statut du réseau - - - - byte(s) - octetoctets - - - - Object(s) to be synced: %n - Objet à synchroniser : %nObjets à synchroniser : %n - - - - Processed %n person-to-person message(s). - Traité %n message de personne à personne.Traité %n messages de personne à personne. - - - - Processed %n broadcast message(s). - Traité %n message de diffusion.Traité %n messages de diffusion. - - - - Processed %n public key(s). - Traité %n clé publique.Traité %n clés publiques. - - - - Peer - Pair - - - - IP address or hostname - Adresse IP ou nom d'hôte - - - - Rating - Évaluation - - - - PyBitmessage tracks the success rate of connection attempts to individual nodes. The rating ranges from -1 to 1 and affects the likelihood of selecting the node in the future - PyBitmessage suit à la trace le taux de réussites de connexions tentées vers les noeuds individuels. L'évaluation s'étend de -1 à 1 et affecte la probabilité de choisir ce noeud dans l'avenir - - - - User agent - Agent utilisateur - - - - Peer's self-reported software - Logiciel, auto-rapporté par le pair - - - - TLS - TLS - - - - Connection encryption - Chiffrement de la connexion - - - - List of streams negotiated between you and the peer - Liste de flux négociés entre vous et le pair - - - - 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> - - - - - 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: - - - - - 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. - - - - regenerateAddressesDialog - - - Regenerate Existing Addresses - Regénérer des adresses existantes - - - - Regenerate existing addresses - Adresses existantes régénérées - - - - 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é. - - - - 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. - - - - Small message difficulty: - Difficulté d’un message court : - - - - When someone sends you a message, their computer must first complete some work. The difficulty of this work, by default, is 1. You may raise this default for new addresses you create by changing the values here. Any new addresses you create will require senders to meet the higher difficulty. There is one exception: if you add a friend or acquaintance to your address book, Bitmessage will automatically notify them when you next send a message that they need only complete the minimum amount of work: difficulty 1. - Lorsque quelqu’un vous envoie un message, son ordinateur doit d’abord effectuer un travail. La difficulté de ce travail, par défaut, est de 1. Vous pouvez augmenter cette valeur pour les adresses que vous créez en changeant la valeur ici. Chaque nouvelle adresse que vous créez requerra à l’envoyeur de faire face à une difficulté supérieure. Il existe une exception : si vous ajoutez un ami ou une connaissance à votre carnet d’adresses, Bitmessage les notifiera automatiquement lors du prochain message que vous leur envoyez qu’ils ne doivent compléter que la charge de travail minimale : difficulté 1. - - - - The 'Small message difficulty' mostly only affects the difficulty of sending small messages. Doubling this value makes it almost twice as difficult to send a small message but doesn't really affect large messages. - La "difficulté d’un message court" affecte principalement la difficulté d’envoyer des messages courts. Doubler cette valeur rend la difficulté à envoyer un court message presque double, tandis qu’un message plus long ne sera pas réellement affecté. - - - - Demanded difficulty - Difficulté exigée - - - - Here you may set the maximum amount of work you are willing to do to send a message to another person. Setting these values to 0 means that any value is acceptable. - Vous pouvez préciser quelle charge de travail vous êtes prêt à effectuer afin d’envoyer un message à une personne. Placer cette valeur à 0 signifie que n’importe quelle valeur est acceptée. - - - - Maximum acceptable total difficulty: - Difficulté maximale acceptée : - - - - Maximum acceptable small message difficulty: - Difficulté maximale acceptée pour les messages courts : - - - - Max acceptable difficulty - Difficulté maximale acceptée - - - - 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> - <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 deleted file mode 100644 index d38e68bc..00000000 Binary files a/src/translations/bitmessage_it.qm and /dev/null differ diff --git a/src/translations/bitmessage_it.ts b/src/translations/bitmessage_it.ts deleted file mode 100644 index dbefce30..00000000 --- a/src/translations/bitmessage_it.ts +++ /dev/null @@ -1,2132 +0,0 @@ - - - - AddAddressDialog - - - Add new entry - Aggiungi una nuova voce - - - - Label - Etichetta - - - - Address - Indirizzo - - - - EmailGatewayDialog - - - Email gateway - Gateway di posta - - - - Register on email gateway - Registrati sul gateway di posta - - - - Account status at email gateway - Stato dell'account rispetto al gateway di posta - - - - Change account settings at email gateway - Modifica i settaggi dell'account rispetto al gateway di posta - - - - Unregister from email gateway - Rimuovi registrazione dal gateway di posta - - - - Email gateway allows you to communicate with email users. Currently, only the Mailchuck email gateway (@mailchuck.com) is available. - Il gateway di posta permette di comunicare con gli utenti email. Per ora, solo il gateway di posta Mailchuck (@mailchuck.com) è disponibile. - - - - Desired email address (including @mailchuck.com): - Indirizzo email desiderato (incluso @mailchuck.com): - - - - 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: - - - - Email gateway registration - Registrazione gateway 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: - - - - - 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, -# 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. - - # È possibile utilizzare questo per configurare il tuo gateway account di posta elettronica -# Decommentare l'impostazione che si desidera utilizzare -# Ecco le opzioni: -# -# PGP: server -# Il gateway e-mail creerà e manterrà le chiavi PGP per voi e firmerà, verificherà, -# cifrarà e decifrerà a vostro nome. Quando si desidera usare PGP, ma siete pigri, -# utilizzate questo. Richiede abbonamento. -# -# PGP: locale # -Il gateway email non condurrà operazioni PGP a vostro nome. È possibile -# o non usare PGP affatto, o utilizzarlo in locale. -# -# Allegati: sì -# gli allegati in entrata nella e-mail verranno caricati su MEGA.nz, e si può -# scaricarli da qui seguendo il link. Richiede un abbonamento. -# -# Allegati: no -# gli allegati saranno ignorati. -# -# Archivio: sì -# i vostri messaggi di posta elettronica in arrivo verranno archiviati sul server. Utilizzare questo se avete bisogno di -# aiuto per problemi di debug o avete necessità di un controllo di terze parti. -# Questo però significa che l'operatore del servizio sarà in grado di leggere i vostri messaggi di posta elettronica -# anche dopo che sono stati consegnati a voi. -# -# Archivio: no -# i messaggi di posta elettronica in arrivo verranno eliminati dal server non appena vengono inoltrati a te. -# -# masterpubkey_btc: BIP44 xpub key oppure seme electrum v1 pubblico -# offset_btc: intero (il default è 0) -# feeamount: numero con un massimo di 8 cifre decimali -# feecurrency: BTC, XBT, USD, EUR o GBP -# utilizzare questi se si vuole far pagare le persone che inviano messaggi di posta elettronica. -# Se questo è attivato una persona sconosciuta che ti invia una e-mail, sarà tenuta a pagare una commissione specificata. -# Dato che questo schema utilizza chiavi pubbliche deterministiche, si riceverà la commissione direttamente. -# Per disattivarla, impostare "feeamount" a 0. -# Richiede un abbonamento. - - - - - 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 - - - - Enable - Abilita - - - - Disable - Disabilita - - - - Set avatar... - Inserisci l'avatar - - - - Copy address to clipboard - Copia indirizzo negli appunti - - - - Special address behavior... - Comportamento indirizzo speciale - - - - Email gateway - Gateway di posta - - - - Delete - 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 - - - - - Not Connected - Non connesso - - - - Show Bitmessage - Mostra Bitmessage - - - - Send - Invia - - - - Subscribe - Sottoscrivi - - - - Channel - Canale - - - - Quit - Chiudi - - - - 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? - Aprire 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? - Svuotare il cestino? - - - - Are you sure you want to delete all trashed messages? - Sei sicuro di voler eliminare tutti i messaggi cestinati? - - - - 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. - - - - - Address invalid - Indirizzo non valido - - - - That Bitmessage address is not valid. - L'indirizzo Bitmessage non è valido. - - - - 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 - Connessione persa - - - - Connected - Connesso - - - - Message trashed - Messaggio cestinato - - - - 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. - - - - - Message too long - Messaggio troppo lungo - - - - 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 - - - - - Address is valid. - Indirizzo valido - - - - 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... - - - - - 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 - Bitmessage - - - - Identities - Identità - - - - New Identity - Nuova identità - - - - Search - Cerca - - - - All - - - - - To - A - - - - From - Da - - - - Subject - Soggetto - - - - Message - Messaggio - - - - Received - Ricevuto - - - - Messages - Messaggi - - - - Address book - - - - - Address - Indirizzo - - - - Add Contact - Aggiungere Contatto - - - - Fetch Namecoin ID - - - - - Subject: - Soggetto: - - - - From: - Da: - - - - To: - A: - - - - Send ordinary Message - - - - - Send Message to your Subscribers - - - - - TTL: - TTL: - - - - Subscriptions - - - - - Add new Subscription - - - - - Chans - - - - - Add Chan - - - - - File - File - - - - Settings - Impostazioni - - - - Help - Aiuto - - - - Import keys - Importa chiavi - - - - Manage keys - Gestisci chiavi - - - - Ctrl+Q - Ctrl+Q - - - - F1 - F1 - - - - Contact support - - - - - About - - - - - Regenerate deterministic addresses - - - - - Delete all trashed messages - - - - - Join / Create chan - - - - - All accounts - Tutti gli account - - - - 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 - 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? - - - - - NewAddressDialog - - - Create new Address - Crea nuovo Indirizzo - - - - 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) - - - - - NewSubscriptionDialog - - - Add new entry - Aggiungi una nuova voce - - - - Label - Etichetta - - - - Address - Indirizzo - - - - 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: - - - - - 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. - - - - - 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 - Aggiungi una nuova voce - - - - Name or Label - Nome o Etichetta - - - - Address - Indirizzo - - - - Blacklist - - - - - Whitelist - - - - - connectDialog - - - Bitmessage - Bitmessage - - - - Bitmessage won't connect to anyone until you let it. - - - - - Connect now - Connetti ora - - - - Let me configure special network settings first - - - - - helpDialog - - - 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: - - - - - iconGlossaryDialog - - - Icon Glossary - - - - - You have no connections with other peers. - Non sei connesso ad altri 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. - - - - - 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. - - - - - networkstatus - - - Total connections: - Connessioni totali: - - - - Since startup: - Dall'avvio: - - - - Processed 0 person-to-person messages. - Processati 0 messaggi da persona a persona - - - - Processed 0 public keys. - Processate 0 chiavi pubbliche - - - - Processed 0 broadcasts. - Processati 0 broadcast - - - - Inventory lookups per second: 0 - - - - - Objects to be synced: - Oggetti da sincronizzare: - - - - Stream # - - - - - Connections - Connessioni - - - - Since startup on %1 - - - - - Down: %1/s Total: %2 - - - - - Up: %1/s Total: %2 - - - - - Total Connections: %1 - Connessioni totali: %1 - - - - 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). - - - - - - - - newChanDialog - - - Dialog - Dialogo - - - - 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: - - - - - regenerateAddressesDialog - - - Regenerate Existing Addresses - Rigenera Indirizzi Esistenti - - - - Regenerate existing addresses - Rigenera indirizzi esistenti - - - - 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 - - - - - 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 - Impostazioni - - - - 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: - - - - - UPnP: - UPnP: - - - - Bandwidth limit - Limite banda - - - - Maximum download rate (kB/s): [0: unlimited] - - - - - Maximum upload rate (kB/s): [0: unlimited] - - - - - Proxy server / Tor - Server proxy / Tor - - - - Type: - - - - - Server hostname: - - - - - Port: - Porta: - - - - Authentication - Autenticazione - - - - Username: - Nome utente: - - - - Pass: - - - - - Listen for incoming connections when using proxy - - - - - none - - - - - SOCKS4a - SOCKS4a - - - - SOCKS5 - SOCKS5 - - - - Network Settings - Impostazioni di rete - - - - Total difficulty: - Difficoltà totale: - - - - 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 - Difficoltà richiesta - - - - 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: - Massima difficoltà totale accettabile: - - - - Maximum acceptable small message difficulty: - - - - - Max acceptable difficulty - Massima difficoltà accettabile - - - - 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> - - - - - Host: - Host: - - - - Password: - Password: - - - - Test - Test - - - - Connect to: - Connetti a: - - - - Namecoind - Namecoind - - - - NMControl - NMControl - - - - Namecoin integration - Integrazione 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> - - - - - Give up after - - - - - and - e - - - - days - giorni - - - - months. - mesi. - - - - Resends Expire - - - - diff --git a/src/translations/bitmessage_ja.qm b/src/translations/bitmessage_ja.qm deleted file mode 100644 index 77fa63d1..00000000 Binary files a/src/translations/bitmessage_ja.qm and /dev/null differ diff --git a/src/translations/bitmessage_ja.ts b/src/translations/bitmessage_ja.ts deleted file mode 100644 index f11289f5..00000000 --- a/src/translations/bitmessage_ja.ts +++ /dev/null @@ -1,2642 +0,0 @@ - - - AddAddressDialog - - - Add new entry - 新しい項目を追加 - - - - Label - ラベル - - - - Address - アドレス - - - - 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: - - - - - 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 - - - # 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. - - - - - - # 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 - 新規 - - - - Enable - 有効 - - - - Disable - 無効 - - - - Set avatar... - アバターを設定... - - - - Copy address to clipboard - アドレスをコピー - - - - Special address behavior... - アドレスの特別な動作 - - - - Email gateway - メールゲートウェイ - - - - Delete - 削除 - - - - Send message to this address - このアドレスへ送信 - - - - Subscribe to this address - このアドレスを購読 - - - - Add New Address - アドレスを追加 - - - - Copy destination address to clipboard - 宛先アドレスをコピー - - - - Force send - 強制的に送信 - - - - One of your addresses, %1, is an old version 1 address. Version 1 addresses are no longer supported. May we delete it now? - %1は古いバージョン1のアドレスです。バージョン1のアドレスはサポートが終了しています。すぐに削除しますか? - - - - Waiting for their encryption key. Will request it again soon. - 暗号鍵を待っています。 すぐにもう一度リクエストします。 - - - - 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 - 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ファイルを編集することで鍵を管理できます。このファイルをバックアップしておくことは重要です。すぐにファイルを開きますか?(必ず編集する前にBitmessageを終了してください) - - - - You may manage your keys by editing the keys.dat file stored in - %1 -It is important that you back up this file. Would you like to open the file now? (Be sure to close Bitmessage before making any changes.) - %1 -に保存されているkeys.datファイルを編集することで鍵を管理できます。 -ファイルをバックアップしておくことは重要です。すぐにファイルを開きますか?(必ず編集する前にBitmessageを終了してください) - - - - Delete trash? - ゴミ箱を空にしますか? - - - - Are you sure you want to delete all trashed messages? - ゴミ箱内のメッセージを全て削除してもよろしいですか? - - - - bad passphrase - 不正なパスフレーズ - - - - You must type your passphrase. If you don't have one then this is not the form for you. - パスフレーズを入力してください。パスフレーズがない場合は入力する必要はありません。 - - - - Bad address version number - 不正なアドレスのバージョン番号 - - - - Your address version number must be a number: either 3 or 4. - アドレスのバージョン番号は数字にする必要があります: 3 または 4。 - - - - Your address version number must be either 3 or 4. - アドレスのバージョン番号は、3 または 4 のどちらかにする必要があります。 - - - - 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 - メッセージが削除されました - - - - 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 - - - - - 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. - アドレス %1 に接続しています。%2 のバージョン番号は処理できません。Bitmessageを最新のバージョンへアップデートしてください。 - - - - Stream number - ストリーム番号 - - - - Concerning the address %1, Bitmessage cannot handle stream numbers of %2. Perhaps upgrade Bitmessage to the latest version. - アドレス %1 に接続しています。%2 のストリーム番号は処理できません。Bitmessageを最新のバージョンへアップデートしてください。 - - - - Warning: You are currently not connected. Bitmessage will do the work necessary to send the message but it won't send until you connect. - 警告: 接続されていません。Bitmessageはメッセージ送信の処理を行いますが、ネットワークに接続するまで送信はされません。 - - - - Message queued. - メッセージがキューに入りました。 - - - - Your 'To' field is empty. - 宛先が指定されていません。 - - - - Right click one or more entries in your address book and select 'Send message to this address'. - アドレス帳から一つ、または複数のアドレスを右クリックして「このアドレスへ送信」を選んでください。 - - - - Fetched address from namecoin identity. - namecoin IDからアドレスを取得。 - - - - New Message - 新規メッセージ - - - - From - - - - - Sending email gateway registration request - - - - - Address is valid. - アドレスが不正です。 - - - - The address you entered was invalid. Ignoring it. - 入力されたアドレスは不正です。無視されました。 - - - - Error: You cannot add the same address to your address book twice. Try renaming the existing one if you want. - エラー: 同じアドレスを複数アドレス帳に追加する事はできません。既存の項目をリネームしてください。 - - - - Error: You cannot add the same address to your subscriptions twice. Perhaps rename the existing one if you want. - エラー: 購読に、同じアドレスを2回追加することはできません。 必要に応じて、既存の名前を変更してください。 - - - - Restart - 再開 - - - - You must restart Bitmessage for the port number change to take effect. - ポート番号の変更を有効にするにはBitmessageを再起動してください。 - - - - Bitmessage will use your proxy from now on but you may want to manually restart Bitmessage now to close existing connections (if any). - プロキシの設定を有効にするには手動でBitmessageを再起動してください。既に接続がある場合は切断されます。 - - - - Number needed - 数字が必要です - - - - Your maximum download and upload rate must be numbers. Ignoring what you typed. - 最大ダウンロード数とアップロード数は数字にする必要があります。 入力されたものを無視します。 - - - - Will not resend ever - 今後再送信されません - - - - Note that the time limit you entered is less than the amount of time Bitmessage waits for the first resend attempt therefore your messages will never be resent. - 入力した時間制限は、Bitmessageが最初の再送試行を待つ時間よりも短いため、メッセージは再送信されないことにご注意ください。 - - - - Sending email gateway unregistration request - - - - - Sending email gateway status request - - - - - Passphrase mismatch - パスフレーズが一致しません - - - - The passphrase you entered twice doesn't match. Try again. - 再度入力されたパスフレーズが一致しません。再入力してください。 - - - - Choose a passphrase - パスフレーズを選択してください - - - - You really do need a passphrase. - パスフレーズが必要です。 - - - - Address is gone - アドレスが無効になりました - - - - Bitmessage cannot find your address %1. Perhaps you removed it? - アドレス %1 が見つかりません。既に削除していませんか? - - - - Address disabled - アドレスが無効になりました - - - - Error: The address from which you are trying to send is disabled. You'll have to enable it on the 'Your Identities' tab before using it. - エラー: 送信しようとしたアドレスは無効になっています。使用する前に「アドレス一覧」で有効にしてください。 - - - - Entry added to the Address Book. Edit the label to your liking. - - - - - Entry added to the blacklist. Edit the label to your liking. - ブラックリストに項目が追加されました。ラベルは自由に編集できます。 - - - - Error: You cannot add the same address to your blacklist twice. Try renaming the existing one if you want. - エラー: ブラックリストに同じアドレスを2回追加することはできません。 必要に応じて既存の名前を変更してみてください。 - - - - Moved items to trash. - アイテムをゴミ箱へ移動。 - - - - Undeleted item. - アイテムの削除を元に戻します。 - - - - Save As... - 形式を選択して保存 - - - - Write error. - 書き込みエラー。 - - - - No addresses selected. - アドレスが未選択です。 - - - - If you delete the subscription, messages that you already received will become inaccessible. Maybe you can consider disabling the subscription instead. Disabled subscriptions will not receive new messages, but you can still view messages you already received. - -Are you sure you want to delete the subscription? - 購読を削除すると、すでに受信したメッセージにアクセスできなくなります。 代わりに、購読を無効にすることもできます。 無効になった購読は新しいメッセージを受信しませんが、受信したメッセージは表示できます。 - -購読を削除してもよろしいですか? - - - - 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. - - - - - 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). - - - - - Bitmessage - Bitmessage - - - - Identities - アドレス一覧 - - - - New Identity - 新しいアドレス - - - - Search - 検索 - - - - All - 全て - - - - To - 宛先 - - - - From - 送信元 - - - - Subject - 題名 - - - - Message - メッセージ - - - - Received - 受信日時 - - - - Messages - メッセージ - - - - Address book - アドレス帳 - - - - Address - アドレス - - - - Add Contact - 連絡先を追加 - - - - Fetch Namecoin ID - namecoin IDを取得 - - - - Subject: - 題名: - - - - From: - 送信元: - - - - To: - 宛先: - - - - Send ordinary Message - 通常のメッセージを送信 - - - - Send Message to your Subscribers - 購読者にメッセージを送信 - - - - TTL: - TTL: - - - - Subscriptions - 購読リスト - - - - Add new Subscription - 購読先を追加 - - - - Chans - チャンネル - - - - Add Chan - チャンネルを追加 - - - - File - ファイル - - - - Settings - 設定 - - - - Help - ヘルプ - - - - Import keys - 鍵をインポート - - - - Manage keys - 鍵を管理 - - - - Ctrl+Q - Ctrrl+Q - - - - F1 - F1 - - - - Contact support - お問い合わせサポート - - - - About - 概要 - - - - Regenerate deterministic addresses - deterministicアドレスを再生成 - - - - Delete all trashed messages - ゴミ箱のメッセージを全て削除する - - - - Join / Create chan - チャンネルに参加 / 作成 - - - - All accounts - すべてのアカウント - - - - Zoom level %1% - ズーム レベル %1% - - - - Error: You cannot add the same address to your list twice. Perhaps rename the existing one if you want. - エラー: 同じアドレスを複数リストに追加する事はできません。既存の項目をリネームしてください。 - - - - Add new entry - 新しい項目を追加 - - - - Display the %1 recent broadcast(s) from this address. - - - - - 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 - 問題: メッセージに含まれた宛先のリクエストはモバイルデバイスですが、設定では許可されていません。 %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) - (帯域と処理能力を節約する) - - - - 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 ? - - - - - <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を参照してください: - - - - 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. - 0 通の1対1のメッセージを処理しました。 - - - - Processed 0 public keys. - 0 件の公開鍵を処理しました。 - - - - Processed 0 broadcasts. - 0 件の配信を処理しました。 - - - - Inventory lookups per second: 0 - 毎秒のインベントリ検索: 0 - - - - Objects to be synced: - 同期する必要のあるオブジェクト: - - - - Stream # - ストリーム # - - - - Connections - - - - - Since startup on %1 - 起動日時 %1 - - - - Down: %1/s Total: %2 - ダウン: %1/秒 合計: %2 - - - - Up: %1/s Total: %2 - アップ: %1/秒 合計: %2 - - - - Total Connections: %1 - 接続数: %1 - - - - Inventory lookups per second: %1 - 毎秒のインベントリ検索: %1 - - - - Up: 0 kB/s - アップ: 0 kB/秒 - - - - Down: 0 kB/s - ダウン: 0 kB/秒 - - - - 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 - あなたとピアの間でネゴシエーションしたストリームのリスト - - - - 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> - - - - - 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: - - - - - 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. - もし以前にdeterministicアドレスを作ったことがあり、何かしらのトラブル(ハードディスクの故障のような)でそれを紛失していた場合、ここで再生成することができます。もし乱数でアドレスを作っていたのであればこのフォームは再生成には使えません。 - - - - 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 - 許可する最大の難易度 - - - - 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> - <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_nb.ts b/src/translations/bitmessage_nb.ts deleted file mode 100644 index 21f641c0..00000000 --- a/src/translations/bitmessage_nb.ts +++ /dev/null @@ -1,1898 +0,0 @@ - - - AddAddressDialog - - - Add new entry - - - - - Label - - - - - Address - - - - - 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. - - - - - 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: - - - - - 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, -# 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. - - - - - - 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 - - - - - Save message as... - - - - - Mark Unread - - - - - New - - - - - Enable - - - - - Disable - - - - - Set avatar... - - - - - Copy address to clipboard - - - - - Special address behavior... - - - - - Email gateway - - - - - Delete - - - - - Send message to this address - - - - - Subscribe to this address - - - - - Add New Address - - - - - Copy destination address to clipboard - - - - - Force send - - - - - One of your addresses, %1, is an old version 1 address. Version 1 addresses are no longer supported. May we delete it now? - - - - - 1 hour - - - - - %1 hours - - - - - %1 days - - - - - 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 - - - - - 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. - - - - - 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. - - - - - All done. Closing user interface... - - - - - 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... - - - - - 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 - - - - - Add Contact - - - - - Fetch Namecoin ID - - - - - Subject: - - - - - From: - - - - - To: - - - - - Send ordinary Message - - - - - Send Message to your Subscribers - - - - - TTL: - - - - - X days - - - - - Subscriptions - - - - - Add new Subscription - - - - - Chans - - - - - Add Chan - - - - - Network Status - - - - - File - - - - - Settings - - - - - Help - - - - - 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 - - - - - Display the %1 recent broadcast(s) from this address. - - - - - 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> - - - - - 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) - - - - - 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: - - - - - aboutDialog - - - About - - - - - PyBitmessage - - - - - version ? - - - - - <html><head/><body><p>Copyright © 2012-2014 Jonathan Warren<br/>Copyright © 2013-2014 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> - - - - - This is Beta software. - - - - - 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 won't connect to anyone until you let it. - - - - - Connect now - - - - - Let me configure special network settings first - - - - - helpDialog - - - 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: - - - - - 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. - - - - - 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. - - - - - networkstatus - - - Total connections: - - - - - Since startup: - - - - - Processed 0 person-to-person messages. - - - - - Processed 0 public keys. - - - - - Processed 0 broadcasts. - - - - - Inventory lookups per second: 0 - - - - - Down: 0 KB/s - - - - - Up: 0 KB/s - - - - - Objects to be synced: - - - - - Stream # - - - - - Connections - - - - - Since startup on %1 - - - - - Objects to be synced: %1 - - - - - Processed %1 person-to-person messages. - - - - - Processed %1 broadcast messages. - - - - - Processed %1 public keys. - - - - - Down: %1/s Total: %2 - - - - - Up: %1/s Total: %2 - - - - - Total Connections: %1 - - - - - Inventory lookups per second: %1 - - - - - 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> - - - - - 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: - - - - - regenerateAddressesDialog - - - Regenerate Existing Addresses - - - - - Regenerate existing addresses - - - - - Passphrase - - - - - Number of addresses to make based on your passphrase: - - - - - Address version number: - - - - - Stream number: - - - - - 1 - - - - - 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. - - - - - 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. - - - - - Willingly include unencrypted destination address when sending to a mobile device - - - - - Use Identicons - - - - - Reply below Quote - - - - - Interface Language - - - - - System Settings - system - - - - - Pirate English - en_pirate - - - - - Other (set in keys.dat) - other - - - - - 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: - - - - - Port: - - - - - Authentication - - - - - Username: - - - - - Pass: - - - - - Listen for incoming connections when using proxy - - - - - none - - - - - SOCKS4a - - - - - 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. - - - - - 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: - - - - - 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_nl.qm b/src/translations/bitmessage_nl.qm deleted file mode 100644 index 72a7ade2..00000000 Binary files a/src/translations/bitmessage_nl.qm and /dev/null differ diff --git a/src/translations/bitmessage_nl.ts b/src/translations/bitmessage_nl.ts deleted file mode 100644 index 3e7c1640..00000000 --- a/src/translations/bitmessage_nl.ts +++ /dev/null @@ -1,2095 +0,0 @@ - - - - AddAddressDialog - - - Add new entry - Nieuw adres toevoegen - - - - Label - Label - - - - Address - Adres - - - - EmailGatewayDialog - - - Email gateway - E-mail 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. - Een e-mail gateway maakt het mogelijk te communiceren met e-mail gebruikers. Momenteel is enkel de Mailchuck e-mail gateway (@mailchuck.com) beschikbaar. - - - - 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 - - - - - 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: - Een e-mail gateway maakt het mogelijk te communiceren met e-mail gebruikers. Momenteel is enkel de Mailchuck e-mail gateway (@mailchuck.com) beschikbaar. -Voer het gewenste e-mail adres (inclusief @mailchuck.com) hieronder in: - - - - 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, -# 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. - - - - - - 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 - - - - Enable - Inschakelen - - - - Disable - Uitschakelen - - - - Set avatar... - Avatar instellen - - - - Copy address to clipboard - Kopieer adres naar klembord - - - - Special address behavior... - - - - - Email gateway - E-mail gateway - - - - Delete - 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? - - - - - Waiting for their encryption key. Will request it again soon. - Aan het wachten up hun encryptie sleutel. Zal binnenkort opnieuw proberen. - - - - Encryption key request queued. - Encryptie sleutel aanvraag toegevoegd aan de wachtrij - - - - Queued. - In wachtrij. - - - - Message sent. Waiting for acknowledgement. Sent at %1 - Bericht verzonden. Wachten op bevestiging. Verzonden op %1 - - - - Message sent. Sent at %1 - Bericht verzonden. Verzonden op %1 - - - - Need to do work to send message. Work is queued. - - - - - Acknowledgement of the message received %1 - Bevestiging van het bericht ontvangen op %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 - Status onbekend: %1 %2 - - - - Not Connected - Niet Verbonden - - - - Show Bitmessage - Toon Bitmessage - - - - Send - Verzend - - - - Subscribe - Abonneer - - - - Channel - - - - - Quit - Sluiten - - - - 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? - keys.dat openen? - - - - 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? - Prullenbak legen? - - - - Are you sure you want to delete all trashed messages? - - - - - bad passphrase - verkeerd wachtwoord - - - - You must type your passphrase. If you don't have one then this is not the form for you. - - - - - Bad address version number - Slechte adres versienummer - - - - 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 - Gelukt - - - - 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 - Adres te nieuw - - - - 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 - Adres ongeldig - - - - That Bitmessage address is not valid. - Dat Bitmessage adres is niet geldig. - - - - 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 - Verbinding verloren - - - - Connected - Verbonden - - - - Message trashed - Bericht weggegooit - - - - 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. - - - - - Message too long - Bericht te 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. - - - - - 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 - Adres versienummer - - - - 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 - - - - - Address is valid. - Adres is geldig. - - - - The address you entered was invalid. Ignoring it. - 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). - - - - - Bitmessage - Bitmessage - - - - Identities - - - - - New Identity - - - - - Search - Zoeken - - - - All - Alle - - - - To - Naar - - - - From - Van - - - - Subject - Onderwerp - - - - Message - Bericht - - - - Received - Ontvangen - - - - Messages - - - - - Address book - - - - - Address - Adres - - - - Add Contact - - - - - Fetch Namecoin ID - Ophalen Namecoin ID - - - - Subject: - Onderwerp: - - - - From: - Van: - - - - To: - Naar: - - - - Send ordinary Message - - - - - Send Message to your Subscribers - - - - - TTL: - - - - - Subscriptions - - - - - Add new Subscription - - - - - Chans - - - - - Add Chan - - - - - File - Bestand - - - - Settings - Instellingen - - - - Help - Help - - - - Import keys - Importeer sleutels - - - - Manage keys - - - - - Ctrl+Q - Ctrl+Q - - - - F1 - F1 - - - - Contact support - - - - - About - Over - - - - 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? - - - - - NewAddressDialog - - - Create new Address - Nieuw adres aanmaken - - - - 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 - Gebruik een willekeurige nummer generator om een adres te maken - - - - 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 - Adres versienummer: 4 - - - - In addition to your passphrase, you must remember these numbers: - - - - - Passphrase - Wachtwoord - - - - Number of addresses to make based on your passphrase: - - - - - Stream number: 1 - - - - - Retype passphrase - - - - - Randomly generate address - Genereer willekeurig adres - - - - 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 - Nieuw adres toevoegen - - - - Label - Label - - - - Address - Adres - - - - 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: - - - - - 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. - - - - - 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 - Nieuw adres toevoegen - - - - Name or Label - - - - - Address - Adres - - - - Blacklist - - - - - Whitelist - - - - - connectDialog - - - Bitmessage - Bitmessage - - - - Bitmessage won't connect to anyone until you let it. - - - - - Connect now - - - - - Let me configure special network settings first - - - - - helpDialog - - - Help - 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: - - - - - 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. - - - - - 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. - - - - - networkstatus - - - 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). - - - - - - - - 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> - - - - - 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: - - - - - regenerateAddressesDialog - - - Regenerate Existing Addresses - - - - - Regenerate existing addresses - - - - - Passphrase - Wachtwoord - - - - 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 - - - - - 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 - Instellingen - - - - Start Bitmessage on user login - Start Bitmessage bij 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 - Interface Taal - - - - System Settings - system - - - - - User Interface - Gebruikersinterface - - - - Listening port - Luisterpoort - - - - Listen for connections on port: - Luister voor connecties op poort: - - - - UPnP: - - - - - Bandwidth limit - - - - - Maximum download rate (kB/s): [0: unlimited] - - - - - Maximum upload rate (kB/s): [0: unlimited] - - - - - Proxy server / Tor - Proxy server / Tor - - - - Type: - Type: - - - - Server hostname: - Server hostnaam: - - - - Port: - Poort: - - - - Authentication - Authenticatie - - - - Username: - Gebruikersnaam: - - - - Pass: - Wachtwoord: - - - - Listen for incoming connections when using proxy - - - - - none - geen - - - - SOCKS4a - SOCKS4a - - - - SOCKS5 - SOCKS5 - - - - Network Settings - Netwerkinstellingen - - - - 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: - - - - - Password: - Wachtwoord - - - - 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 - Opgeven na - - - - and - en - - - - days - dagen - - - - months. - maanden. - - - - Resends Expire - - - - diff --git a/src/translations/bitmessage_no.qm b/src/translations/bitmessage_no.qm deleted file mode 100644 index 493d09ef..00000000 Binary files a/src/translations/bitmessage_no.qm and /dev/null differ diff --git a/src/translations/bitmessage_no.ts b/src/translations/bitmessage_no.ts deleted file mode 100644 index bb1d5278..00000000 --- a/src/translations/bitmessage_no.ts +++ /dev/null @@ -1,2099 +0,0 @@ - - - - AddAddressDialog - - - Add new entry - Legg til ny oppføring - - - - Label - Etikett - - - - Address - Adresse - - - - EmailGatewayDialog - - - Email gateway - Epost portal - - - - Register on email gateway - Registrer på epost portal - - - - Account status at email gateway - Kontoinnstillinger på epost portal - - - - Change account settings at email gateway - Endre kontoinnstillinger på epost portal - - - - Unregister from email gateway - Avregistrer fra epost portal - - - - 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): - Ønsket epost adresse (inkludert @mailchuck.com): - - - - 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: - - - - - Email gateway registration - Epost portal registrering - - - - 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 - - - # 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. - - - - - - 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 - - - - Enable - Aktiver - - - - Disable - Deaktiver - - - - Set avatar... - Sett ett avatar... - - - - Copy address to clipboard - Kopier adressen til utklippstavlen - - - - Special address behavior... - Spesieladressebehandling ... - - - - Email gateway - Epost portal - - - - Delete - 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? - - - - Waiting for their encryption key. Will request it again soon. - Venter på krypteringsnøkkel. Sender straks en ny forespørsel. - - - - Encryption key request queued. - Forespørsel for å finne krypteringsnøkkel er satt i kø. - - - - Queued. - Satt i kø. - - - - Message sent. Waiting for acknowledgement. Sent at %1 - Beskjed sendt. Venter på bekreftelse. Sendt %1 - - - - Message sent. Sent at %1 - Beskjed sendt. Sendt %1 - - - - Need to do work to send message. Work is queued. - Trenger å utføre arbeidsoppgave for sende beskjed. Denne er satt i kø. - - - - Acknowledgement of the message received %1 - Bekreftelse på beskjeden mottatt %1 - - - - Broadcast queued. - Kringkasting satt i kø. - - - - Broadcast on %1 - Kringkasting på %1 - - - - Problem: The work demanded by the recipient is more difficult than you are willing to do. %1 - Problem: Det nødvendige arbeidet som kreves utført av mottaker er mer krevende enn det som er satt som akseptabelt. %1 - - - - Problem: The recipient's encryption key is no good. Could not encrypt message. %1 - Problem: Mottakerens nøkkel kunne ikke brukes til å kryptere beskjeden. %1 - - - - Forced difficulty override. Send should start soon. - Tvunget vanskelighet overstyrt. Sender snart. - - - - Unknown status: %1 %2 - Ukjent status: %1 %2 - - - - Not Connected - Ikke tilkoblet - - - - Show Bitmessage - Vis Bitmessage - - - - Send - Send - - - - Subscribe - Abonner - - - - Channel - Kanal - - - - Quit - Avslutt - - - - 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. - Du kan administrere nøklene dine ved å endre filen keys.dat i samme katalog som dette programmet. Det er viktig at du tar en sikkerhetskopi av denne filen. - - - - You may manage your keys by editing the keys.dat file stored in - %1 -It is important that you back up this file. - Du kan administrere nøklene dine ved å endre filen keys.dat lagret i - %1 -Det er viktig at du tar en sikkerhetskopi av denne filen. - - - - Open keys.dat? - Åpne 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.) - Du kan administrere nøklene dine ved å endre filen keys.dat i samme katalog som dette programmet. Det er viktig at du tar en sikkerhetskopi av denne filen. Vil du åpne denne filen nå? (Pass på å lukke Bitmessage før du gjør endringer.) - - - - 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.) - Du kan administrere dine nøkler ved å endre på filen keys.dat lagret i - %1 -Det er viktig at du tar sikkerhetskopi av denne filen. Vil du åpne denne filen nå? (Vær sikker på å få avsluttet Bitmessage før du gjør endringer.) - - - - Delete trash? - Vil du slette kastet innhold? - - - - Are you sure you want to delete all trashed messages? - Er du sikker på at du vil slette alle kastede beskjeder? - - - - bad passphrase - Dårlig passordfrase - - - - You must type your passphrase. If you don't have one then this is not the form for you. - Du må skrive inn passordfrasen din. Hvis du ikke har en kan du ikke bruke dette skjemaet. - - - - Bad address version number - Feil adresseversjonsnummer - - - - Your address version number must be a number: either 3 or 4. - Ditt adressetypenummer må være et nummer: Enten 3 eller 4. - - - - Your address version number must be either 3 or 4. - Ditt adressetypenummer må enten være 3 eller 4. - - - - Chan name needed - Kanalnavn nødvendig - - - - You didn't enter a chan name. - Du oppga ikke noe kanalnavn. - - - - Address already present - Adressen eksisterer allerede - - - - Could not add chan because it appears to already be one of your identities. - Kunne ikke legge til kanal siden den ser ut til å allerede være lagret som en av dine identiteter. - - - - Success - Suksess - - - - 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'. - Opprettet ny kanal. For å la andre delta i din nye kanal gir du dem dem kanalnavnet og denne Bitmessage-adressen: %1. Denne adressen vises også i 'Dine identiteter'. - - - - Address too new - Adressen er for ny - - - - Although that Bitmessage address might be valid, its version number is too new for us to handle. Perhaps you need to upgrade Bitmessage. - Selv om Bitmessage-adressen kanskje er gyldig så er tilhørende typenummer for nytt til å håndteres. Kanskje du trenger å oppgradere Bitmessage. - - - - Address invalid - Ugyldig adresse - - - - That Bitmessage address is not valid. - Bitmessage-adressen er ikke gyldig. - - - - Address does not match chan name - Adresse stemmer ikke med kanalnavnet - - - - Although the Bitmessage address you entered was valid, it doesn't match the chan name. - Selv om Bitmessage-adressen du oppga var gyldig stemmer den ikke med kanalnavnet. - - - - Successfully joined chan. - Deltar nå i kanal. - - - - Connection lost - Mistet tilkobling - - - - Connected - Tilkoblet - - - - Message trashed - Beskjed kastet - - - - 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. - - - - - Message too long - Meldingen er for 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. - - - - - 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 - Feil: Bitmessage-adresser begynner med BM-. Vennligst sjekk %1 - - - - Error: The address %1 is not typed or copied correctly. Please check it. - Feil: Adressen %1 er skrevet eller kopiert inn feil. Vennligst sjekk den. - - - - Error: The address %1 contains invalid characters. Please check it. - Feil: Adressen %1 innerholder ugyldige tegn. Vennligst sjekk den. - - - - Error: The address version in %1 is too high. Either you need to upgrade your Bitmessage software or your acquaintance is being clever. - Feil: Typenummeret for adressen %1 er for høy. Enten trenger du å oppgradere Bitmessaage-programvaren eller så er det fordi kontakten din har funnet på noe smart. - - - - Error: Some data encoded in the address %1 is too short. There might be something wrong with the software of your acquaintance. - Feil: Noen av de kodede dataene i adressen %1 er for korte. Det kan hende det er noe galt med programvaren til kontakten din. - - - - Error: Some data encoded in the address %1 is too long. There might be something wrong with the software of your acquaintance. - Feil: Noen av de kodede dataene i adressen %1 er for lange. Det kan hende det er noe galt med programvaren til kontakten din. - - - - 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. - Feil: Noe er galt med adressen %1. - - - - Error: You must specify a From address. If you don't have one, go to the 'Your Identities' tab. - Feil: Du må oppgi en avsenderadresse. Hvis du ikke har en gå til 'Dine identiteter'-fanen. - - - - Address version number - Adressetypenummer - - - - Concerning the address %1, Bitmessage cannot understand address version numbers of %2. Perhaps upgrade Bitmessage to the latest version. - Angående adressen %1, Bitmessage forstår ikke adressetypenumre for %2. Oppdater Bitmessage til siste versjon. - - - - Stream number - Strømnummer - - - - Concerning the address %1, Bitmessage cannot handle stream numbers of %2. Perhaps upgrade Bitmessage to the latest version. - Angående adressen %1, Bitmessage kan ikke håndtere strømnumre for %2. Oppdater Bitmessage til siste utgivelse. - - - - Warning: You are currently not connected. Bitmessage will do the work necessary to send the message but it won't send until you connect. - Advarsel: Du er ikke tilkoblet. Bitmessage vil utføre nødvendige arbeidsoppgaver for å sende beskjeder, men ingen vil bli sendt før du kobler til igjen. - - - - Message queued. - Meldingen er lagt i kø. - - - - Your 'To' field is empty. - Ditt 'Til'-felt er tomt. - - - - Right click one or more entries in your address book and select 'Send message to this address'. - 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 - - - - - Address is valid. - Adressen er gyldig. - - - - The address you entered was invalid. Ignoring it. - 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. - - - - Tray notifications not yet supported on your OS. - Varslinger via systemkurv 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). - - - - Bitmessage - Bitmessage - - - - Identities - Identiteter - - - - New Identity - Ny identitet - - - - Search - Søk - - - - All - Alle - - - - To - Til - - - - From - Fra - - - - Subject - Emne - - - - Message - Beskjed - - - - Received - Mottatt - - - - Messages - Meldinger - - - - Address book - Adressebok - - - - Address - Adresse - - - - Add Contact - Legg til kontakt - - - - Fetch Namecoin ID - Hent Namecoin-id - - - - Subject: - Emne: - - - - From: - Fra: - - - - To: - Til: - - - - Send ordinary Message - Send vanlig melding - - - - Send Message to your Subscribers - Send melding til dine abonnementer - - - - TTL: - TTL: - - - - Subscriptions - Abonnement - - - - Add new Subscription - Legg til nytt abonnement - - - - Chans - Kanaler - - - - Add Chan - Legg til kanal - - - - File - Fil - - - - Settings - Innstillinger - - - - Help - Hjelp - - - - Import keys - Importer inn nøkler - - - - Manage keys - Administrer nøkler - - - - Ctrl+Q - Ctrl+Q - - - - F1 - F1 - - - - Contact support - Kontakt support - - - - About - Om - - - - Regenerate deterministic addresses - Regenerer deterministiske adresser - - - - Delete all trashed messages - Slett alle kastede meldinger - - - - Join / Create chan - Delta i / opprett kanal - - - - All accounts - Alle kontoer - - - - Zoom level %1% - Zoom nivå %1% - - - - 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? - - - - - NewAddressDialog - - - Create new Address - Opprett ny 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: - Her kan du generere så mange adresser du vil. Du oppfordres til å ta i bruk nye adresser med jevne mellomrom. Du kan generere nye adresser enten ved å bruke tilfeldige numre eller en passordfrase. Hvis du bruker passordfrase får du en såkalt 'deterministisk' adresse. -'Tilfeldig nummer'-valget er valgt som standard. En deterministisk adresse har både fordeler og ulemper: - - - - <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;">Fordeler:<br/></span>Du kan gjenskape adressene dine på hvilken som helst datamaskin ved hjelp av hukommelsen. <br/>Du trenger ikke ta noen sikkerhetskopi av keys.dat-filen så lenge du husker passordfrasen din. <br/><span style=" font-weight:600;">Ulemper:<br/></span>Du må huske (eller skrive ned) din passordfrase hvis du forventer å måtte gjenopprette nøklene dine fordi de går tapt. <br/>Du må huske adresseversjonsnummeret og strømnummeret i tillegg til passordfrasen. <br/>Hvis du velger en svak passordfrase og noen andre på Internett klarer å knekke den kan de lese beskjedene dine og sende nye beskjeder på vegne av deg.</p></body></html> - - - - Use a random number generator to make an address - Opprett en adresse ved å bruke generatoren som lager tilfeldige tall - - - - Use a passphrase to make addresses - Bruk en passordfrase for å opprette adresser - - - - Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter - Bruk ekstra tid på å få adressen(e) en eller to tegn kortere - - - - Make deterministic addresses - Opprett deterministisk adresse - - - - Address version number: 4 - Adressetypenummer: 4 - - - - In addition to your passphrase, you must remember these numbers: - I tillegg til passordfrasen må du huske disse numrene: - - - - Passphrase - Passordfrase - - - - Number of addresses to make based on your passphrase: - Antall adresser som skal opprettes basert på din passordfrase: - - - - Stream number: 1 - Strømnummer: 1 - - - - Retype passphrase - Gjenta passordfrase - - - - Randomly generate address - Generer tilfeldig adresse - - - - Label (not shown to anyone except you) - Etikett (ikke vist til noen andre enn deg) - - - - Use the most available stream - Bruk den mest tilgjengelige strømmen - - - - (best if this is the first of many addresses you will create) - (best hvis dette er de første av mange adresser du kommer til å opprette) - - - - Use the same stream as an existing address - Bruk samme strøm som en eksisterende adresse - - - - (saves you some bandwidth and processing power) - (sparer deg for litt båndbredde og prosesseringskraft) - - - - NewSubscriptionDialog - - - Add new entry - Legg til ny oppføring - - - - Label - Etikett - - - - Address - Adresse - - - - Enter an address above. - Oppgi inn en adresse ovenfor. - - - - SpecialAddressBehaviorDialog - - - Special Address Behavior - Spesiell adresseoppførsel - - - - Behave as a normal address - Oppførsel som vanlig adresse - - - - Behave as a pseudo-mailing-list address - Oppførsel som adresse på pseudo-epostliste - - - - Mail received to a pseudo-mailing-list address will be automatically broadcast to subscribers (and thus will be public). - E-post mottatt med en adresse oppført på en pseudo-epostliste vil automatisk bli kringkastet til abonnenter (og vil derfor bli offentlig tilgjengelig). - - - - Name of the pseudo-mailing-list: - 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. - - - - 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 - - - - Name or Label - Navn eller Etikett - - - - Address - Adresse - - - - Blacklist - Svarteliste - - - - Whitelist - Hviteliste - - - - connectDialog - - - Bitmessage - Bitmessage - - - - Bitmessage won't connect to anyone until you let it. - Bitmessage kobler ikke til noen før du lar den. - - - - Connect now - Koble til nå - - - - Let me configure special network settings first - La meg konfigurere spesielle nettverksinnstillinger først - - - - helpDialog - - - Help - Hjelp - - - - <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 er et samarbeidsprosjekt, hjelp kan bli funnet på nettet i Bitmessage-wikien: - - - - iconGlossaryDialog - - - Icon Glossary - Statusoversikt - - - - You have no connections with other peers. - Du har ingen tilkoblinger til andre. - - - - 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. - Du har opprettet minst en utgående tilkobling til andre, men ikke mottatt noen innkommende tilkoblinger enda. Din brannmur eller ruter er antagelig ikke konfigurert til å videreformidle innkommende TCP-tilkoblinger frem til datamaskinen din. Bitmessage vil fungere helt fint, men det ville hjelpe Bitmessage-nettverket hvis du tillot innkommende tilkoblinger. Det vil også hjelpe deg å bli en bedre tilkoblet node. - - - - You are using TCP port ?. (This can be changed in the settings). - Du bruker TCP port ?. (Dette kan endres på fra innstillingene). - - - - You do have connections with other peers and your firewall is correctly configured. - Du har aktive tilkoblinger til andre og riktig konfigurert brannmur. - - - - networkstatus - - - Total connections: - Antall tilkoblinger: - - - - Since startup: - Siden oppstart: - - - - Processed 0 person-to-person messages. - Behandlet 0 person-til-person meldinger. - - - - Processed 0 public keys. - Behandlet 0 offentlige nøkler. - - - - Processed 0 broadcasts. - Behandlet 0 meldinger. - - - - Inventory lookups per second: 0 - - - - - Objects to be synced: - Objekter som skal synkroniseres: - - - - Stream # - - - - - Connections - Tilkoblinger - - - - Since startup on %1 - Siden oppstart på %1 - - - - Down: %1/s Total: %2 - Ned: %1/s Totalt: %2 - - - - Up: %1/s Total: %2 - Opp: %1/s Totalt: %2 - - - - Total Connections: %1 - Antall tilkoblinger: %1 - - - - 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). - - - - - - - - newChanDialog - - - Dialog - Dialog - - - - Create a new chan - Opprett en ny kanal - - - - Join a chan - Delta i kanal - - - - Create a chan - Opprett en kanal - - - - <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>Skriv inn et navn for kanalen din. Hvis du velger et komplisert nok kanalnavn (et som er langt nok og unikt som passfrase) og ingen av dine kontakter deler det offentlig vil kanalen være sikker og privat. Hvis du og noen andre begge oppretter en kanal med samme kanalnavnet vil dette bli samme kanalen</p></body></html> - - - - Chan name: - Kanalnavn: - - - - <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>En kanal eksisterer når en gruppe personer deler de samme dekrypteringsnøklene. Nøklene og Bitmessage-adressen brukt av kanalen er generert fra et menneskevennlig ord eller en frase (kanalnavnet). For å sende en beskjed til alle som er i kanalen sender man en vanlig beskjed av typen person-til-person til kanaladressen.</p><p>Kanaler er fullstendig umodererbare og eksperimentelle.</p></body></html> - - - - Chan bitmessage address: - Bitmessage-kanaladresse: - - - - regenerateAddressesDialog - - - Regenerate Existing Addresses - Regenerer eksisterende adresser - - - - Regenerate existing addresses - Regenerer eksisterende adresser - - - - Passphrase - Passordfrase - - - - Number of addresses to make based on your passphrase: - Antall adresser som skal opprettes basert på din passordfrase: - - - - Address version number: - Adressetypenummer: - - - - Stream number: - Strømnummer: - - - - 1 - 1 - - - - Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter - Bruk ekstra tid på å få adressen(e) en eller to tegn kortere - - - - You must check (or not check) this box just like you did (or didn't) when you made your addresses the first time. - Du må krysse av for (eller ikke krysse av for) i denne boksen slik du gjorde når du opprettet adressene dine første gangen. - - - - 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. - Hvis du tidligere har opprettet deterministiske adresser, men mistet dem p.g.a. et uhell (f.eks. harddiskkræsj) så kan de regenereres her. Hvis du derimot brukte generatoren for generering av tilfeldige tall vil ikke dette skjemaet være til hjelp for deg. - - - - settingsDialog - - - Settings - Innstillinger - - - - Start Bitmessage on user login - Start Bitmessage ved brukerpålogging - - - - Tray - Systemkurv - - - - Start Bitmessage in the tray (don't show main window) - Start Bitmessage i systemkurv (ikke vis hovedvinduet) - - - - Minimize to tray - Minimiser til systemkurv - - - - Close to tray - Lukk til systemkurv - - - - Show notification when message received - Vis varsel når beskjed mottas - - - - Run in Portable Mode - Kjør i flyttbar modus - - - - 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. - I flyttbar modus blir beskjeder og konfigurasjonsfiler oppbevart i samme katalog som programmet istedet for den vanlige applikasjonsdatamappen. Dette gjør Bitmessage enkel å kjøre fra f.eks. minnepinne. - - - - Willingly include unencrypted destination address when sending to a mobile device - Inkluder med vilje ukrypterte destinasjonadresser når mobil enhet er mottaker - - - - Use Identicons - Bruk identikoner - - - - Reply below Quote - Svar under sitat - - - - Interface Language - Grensesnittspråk - - - - System Settings - system - Systeminnstillinger - - - - User Interface - Brukergrensesnitt - - - - Listening port - Lyttende port - - - - Listen for connections on port: - Lytt etter tilkoblinger på port: - - - - UPnP: - UPnP: - - - - Bandwidth limit - Båndbredde grense - - - - Maximum download rate (kB/s): [0: unlimited] - - - - - Maximum upload rate (kB/s): [0: unlimited] - - - - - Proxy server / Tor - Proxytjener / Tor - - - - Type: - Type: - - - - Server hostname: - Tjenernavn: - - - - Port: - Port: - - - - Authentication - Autentisering - - - - Username: - Brukernavn: - - - - Pass: - Passord: - - - - Listen for incoming connections when using proxy - Lytt etter innkommende tilkoblinger når proxy benyttes - - - - none - ingen - - - - SOCKS4a - SOCKS4a - - - - SOCKS5 - SOCKS5 - - - - Network Settings - Nettverksinnstillinger - - - - Total difficulty: - Total vanskelighet: - - - - The 'Total difficulty' affects the absolute amount of work the sender must complete. Doubling this value doubles the amount of work. - 'Total vanskelighet' påvirker den absolutte mengden av arbeid som avsender må fullføre. Dobling av denne verdien dobler også arbeidsmengden. - - - - Small message difficulty: - Vanskelighet for kort beskjed: - - - - 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. - Når noen sender deg en beskjed må først datamaskin deres fullføre en arbeidsoppgave. Vanskelighetsgraden for denne oppgaven er satt til 1 som standard. Du kan heve denne grensen for nye adresser du oppretter ved å endre på verdiene her. Alle nye adresser du oppretter vil kreve av avsender å løse enda vanskeligere oppgaver. Det finnes èt unntak: Hvis du legger til en kontakt i din adressebok vil de bli varslet neste gang du sender en beskjed om at de kun trenger å utføre enkleste form for arbeidsoppgave, denne har vanskelighetsgrad 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. - 'Vanskelighet for kort beskjed' vil kun påvirke sending av korte beskjeder. Dobling av denne verdien vil også doble vanskeligheten for å sende en kort beskjed. - - - - Demanded difficulty - Krevd vanskelighet - - - - 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. - Her kan du sette den maksimale mengden med arbeid som du er villig til å gjennomføre for å sende en beskjed til en annen person. Om disse verdiene settes til null vil alle verdier bli akseptert. - - - - Maximum acceptable total difficulty: - Maks akseptabel total vanskelighet: - - - - Maximum acceptable small message difficulty: - Maks akseptabel vanskelighet for korte beskjeder: - - - - Max acceptable difficulty - Maks akseptabel vanskelighet - - - - 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> - <html><head/><body><p>Bitmessage kan benytte et annet Bitcoin-basert program ved navn Namecoin for å lage menneskevennlige adresser. For eksempel, istedet for å fortelle din kontakt din lange Bitmessage-adresse så kan du enkelt fortelle vedkommende at beskjeden skal sendes til <span style=" font-style:italic;">test. </span></p><p>(å få din egen adresse inn Namecoin er fortsatt ganske vanskelig).</p><p>Bitmessage kan bruke enten namecoind direkte eller en kjørende instans av nmcontrol.</p></body></html> - - - - Host: - Vert: - - - - Password: - Passord: - - - - Test - Test - - - - Connect to: - Koble til: - - - - Namecoind - Namecoind - - - - NMControl - NMControl - - - - Namecoin integration - Namecoin-integrasjon - - - - <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>Som standard er det slik at hvis du sender en beskjed til noen og de er frakoblet i mer enn to dager så vil Bitmessage sende beskjeden på nytt igjen etter at det er gått to ekstra dager. Sånn vil det holde på fremover med eksponentiell vekst; beskjeder sendes på nytt etter 8, 16, 32 dager o.s.v., helt til mottakeren erkjenner dem. Her kan du endre på denne oppførselen ved å få Bitmessage til å gi opp etter et bestemt antall dager eller måneder.</p><p>La disse feltene stå tomme for å få standard oppsettet. </p></body></html> - - - - Give up after - Gi opp etter - - - - and - og - - - - days - dager - - - - months. - måneder. - - - - Resends Expire - Igjensending - - - 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 deleted file mode 100644 index 89c6162e..00000000 --- a/src/translations/bitmessage_pl.ts +++ /dev/null @@ -1,2644 +0,0 @@ - - - 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: - - - - - 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 - - - # 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. - - - - - - # 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 - - - - 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. - - - - Encryption key request queued. - - - - - 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 - - - - Need to do work to send message. Work is queued. - - - - - 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 - Tożsamości - - - - New Identity - Nowa tożsamość - - - - Search - Szukaj - - - - All - Wszystkie - - - - To - Do - - - - From - Od - - - - Subject - Temat - - - - Message - Wiadomość - - - - Received - Odebrana - - - - Messages - Wiadomości - - - - Address book - Książka adresowa - - - - Address - Adres - - - - Add Contact - Dodaj kontakt - - - - Fetch Namecoin ID - Pobierz Namecoin ID - - - - Subject: - Temat: - - - - From: - Od: - - - - To: - Do: - - - - Send ordinary Message - Wyślij zwykłą wiadomość - - - - Send Message to your Subscribers - Wyślij wiadomość broadcast - - - - TTL: - Czas życia: - - - - Subscriptions - Subskrypcje - - - - Add new Subscription - Dodaj subskrypcję - - - - Chans - Kanały - - - - Add Chan - Dodaj kanał - - - - File - Plik - - - - Settings - Ustawienia - - - - Help - Pomoc - - - - Import keys - Importuj klucze - - - - Manage keys - Zarządzaj kluczami - - - - Ctrl+Q - Ctrl+Q - - - - F1 - F1 - - - - Contact support - Kontakt z twórcami - - - - About - O programie - - - - Regenerate deterministic addresses - Odtwórz adres deterministyczny - - - - Delete all trashed messages - Usuń wiadomości z kosza - - - - Join / Create chan - Dołącz / Utwórz kanał - - - - All accounts - Wszystkie konta - - - - Zoom level %1% - Poziom powiększenia %1% - - - - Error: You cannot add the same address to your list twice. Perhaps rename the existing one if you want. - Błąd: Adres znajduje sie już na liście. - - - - Add new entry - Dodaj nowy wpis - - - - Display the %1 recent broadcast(s) from this address. - - - - - 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 - 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>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> - - - - blacklist - - - 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. - - - - 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. - Masz połączenia z innymi użytkownikami i twoja zapora sieciowa jest skonfigurowana poprawnie. - - - - You are using TCP port %1. (This can be changed in the settings). - Btimessage używa portu TCP %1. (Można go zmienić w ustawieniach). - - - - networkstatus - - - Total connections: - Wszystkich połączeń: - - - - Since startup: - Od startu: - - - - Processed 0 person-to-person messages. - Przetworzono 0 wiadomości zwykłych. - - - - Processed 0 public keys. - Przetworzono 0 kluczy publicznych. - - - - Processed 0 broadcasts. - Przetworzono 0 wiadomości subskrypcji. - - - - Inventory lookups per second: 0 - Zapytań o elementy na sekundę: 0 - - - - Objects to be synced: - Obiektów do zsynchronizowania: - - - - Stream # - Strumień # - - - - Connections - - - - - 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 - - - - 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> - - - - - 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: - - - - - 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ść - - - - 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> - <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 deleted file mode 100644 index 9c6b3402..00000000 Binary files a/src/translations/bitmessage_pt.qm and /dev/null differ diff --git a/src/translations/bitmessage_pt.ts b/src/translations/bitmessage_pt.ts deleted file mode 100644 index 8c43b926..00000000 --- a/src/translations/bitmessage_pt.ts +++ /dev/null @@ -1,2094 +0,0 @@ - - - - AddAddressDialog - - - Add new entry - Adiciona nova entrada - - - - Label - Rótulo - - - - Address - Morada - - - - EmailGatewayDialog - - - Email gateway - Email gateway - - - - Register on email gateway - Registar na 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: - - - - - 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, -# 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. - - - - - - 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 - - - - - Save message as... - - - - - Mark Unread - - - - - New - - - - - Enable - - - - - Disable - - - - - Set avatar... - - - - - Copy address to clipboard - - - - - Special address behavior... - - - - - Email gateway - Email gateway - - - - Delete - 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? - - - - - Waiting for their encryption key. Will request it again soon. - Aguardando chave de encriptação. Novo pedido em breve. - - - - Encryption key request queued. - - - - - Queued. - Em fila - - - - Message sent. Waiting for acknowledgement. Sent at %1 - Mensagem enviada. Aguardando confirmação. Enviada a %1 - - - - Message sent. Sent at %1 - Mensagem enviada. Enviada 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 - - - - - Not Connected - Não está ligado - - - - Show Bitmessage - Mostra Bitmessage - - - - Send - Enviar - - - - Subscribe - Subscrever - - - - Channel - Canal - - - - Quit - Desligar - - - - 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? - Esvaziar lixo? - - - - 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 - - - - - 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. - - - - - 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... - - - - - 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 - - - - - Import keys - Importar chaves - - - - Manage keys - Gerir chaves - - - - Ctrl+Q - Ctrl+Q - - - - F1 - - - - - Contact support - - - - - About - Acerca - - - - 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? - - - - - 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> - - - - - 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) - - - - - NewSubscriptionDialog - - - Add new entry - Adiciona nova entrada - - - - Label - Rótulo - - - - Address - Morada - - - - 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: - - - - - 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. - - - - 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 - Adiciona nova entrada - - - - Name or Label - - - - - Address - Morada - - - - Blacklist - - - - - Whitelist - - - - - connectDialog - - - Bitmessage - - - - - Bitmessage won't connect to anyone until you let it. - - - - - Connect now - Ligar agora - - - - Let me configure special network settings first - - - - - helpDialog - - - 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: - - - - - 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. - - - - - 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. - - - - - networkstatus - - - 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). - - - - - - - - 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> - - - - - 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: - - - - - 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 - - - - - 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 - Configurações - - - - 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: - - - - - Port: - Porta: - - - - Authentication - Autenticação - - - - Username: - Nome de utilizador: - - - - Pass: - - - - - Listen for incoming connections when using proxy - - - - - none - nenhum - - - - SOCKS4a - SOCKS4a - - - - SOCKS5 - SOCKS5 - - - - Network Settings - Configurações de Rede - - - - Total difficulty: - Dificuldade Máxima: - - - - 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: - - - - - Password: - Palavra-passe: - - - - Test - Teste - - - - Connect to: - Ligar a: - - - - Namecoind - Namecoind - - - - NMControl - NMControl - - - - Namecoin integration - Integração 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> - - - - - Give up after - - - - - and - e - - - - days - dias - - - - months. - meses - - - - Resends Expire - - - - diff --git a/src/translations/bitmessage_ru.qm b/src/translations/bitmessage_ru.qm deleted file mode 100644 index 8c0269b9..00000000 Binary files a/src/translations/bitmessage_ru.qm and /dev/null differ diff --git a/src/translations/bitmessage_ru.ts b/src/translations/bitmessage_ru.ts deleted file mode 100644 index 4a80f62e..00000000 --- a/src/translations/bitmessage_ru.ts +++ /dev/null @@ -1,2584 +0,0 @@ - - - AddAddressDialog - - - Add new entry - Добавить новую запись - - - - Label - Имя - - - - Address - Адрес - - - - 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). - - - - 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-шлюзе - - - - 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: - - - - - 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, -# 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. - - - - - - # 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. - - - - - - MainWindow - - - Reply to sender - Ответить отправителю - - - - Reply to channel - Ответить в канал - - - - Add sender to your Address Book - Добавить отправителя в адресную книгу - - - - Add sender to your Blacklist - Добавить отправителя в чёрный список - - - - Move to Trash - Поместить в корзину - - - - Undelete - Отменить удаление - - - - View HTML code as formatted text - Просмотреть HTML код как отформатированный текст - - - - Save message as... - Сохранить сообщение как ... - - - - Mark Unread - Отметить как непрочитанное - - - - New - Новый адрес - - - - Enable - Включить - - - - Disable - Выключить - - - - Set avatar... - Установить аватар... - - - - Copy address to clipboard - Скопировать адрес в буфер обмена - - - - Special address behavior... - Особое поведение адресов... - - - - Email gateway - Email-шлюз - - - - Delete - Удалить - - - - Send message to this address - Отправить сообщение на этот адрес - - - - Subscribe to this address - Подписаться на рассылку с этого адреса - - - - Add New Address - Добавить новый адрес - - - - Copy destination address to clipboard - Скопировать адрес отправки в буфер обмена - - - - Force send - Форсировать отправку - - - - One of your addresses, %1, is an old version 1 address. Version 1 addresses are no longer supported. May we delete it now? - Один из Ваших адресов, %1, является устаревшим адресом версии 1. Адреса версии 1 больше не поддерживаются. Хотите ли Вы удалить его сейчас? - - - - Waiting for their encryption key. Will request it again soon. - Ожидаем ключ шифрования от Вашего собеседника. Запрос будет повторен через некоторое время. - - - - 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 - Показать 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. - Вы можете управлять Вашими ключами, редактируя файл 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 перед тем, как вносить в него какие-либо изменения.) - - - - 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. - Адрес номера версии должен быть числом: либо 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. - - - - - 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, тем больше расчётов ваш компьютер должен сделать, чтобы отправить -сообщение. Часто разумным вариантом будет установка TTL на 4 или 5 дней. - - - - Message too long - Сообщение слишком длинное - - - - The message that you are trying to send is too long by %1 bytes. (The maximum is 261644 bytes). Please cut it down before sending. - Сообщение, которое вы пытаетесь отправить, длиннее максимально допустимого на %1 байт. (Максимально допустимое значение 261644 байта). Пожалуйста, сократите сообщение перед отправкой. - - - - Error: Your account wasn't registered at an email gateway. Sending registration now as %1, please wait for the registration to be processed before retrying sending. - Ошибка: ваш аккаунт не зарегистрирован на Email-шлюзе. Отправка регистрации %1, пожалуйста, подождите пока процесс регистрации не завершится, прежде чем попытаться отправить сообщение заново. - - - - 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. - По поводу адреса %1: Bitmessage не поддерживает адреса версии %2. Возможно вам нужно обновить клиент Bitmessage. - - - - Stream number - Номер потока - - - - Concerning the address %1, Bitmessage cannot handle stream numbers of %2. Perhaps upgrade Bitmessage to the latest version. - По поводу адреса %1: Bitmessage не поддерживает поток номер %2. Возможно вам нужно обновить клиент Bitmessage. - - - - Warning: You are currently not connected. Bitmessage will do the work necessary to send the message but it won't send until you connect. - Внимание: вы не подключены к сети. Bitmessage выполнит работу, требуемую для отправки сообщения, но не отправит его до тех пор, пока вы не подключитесь. - - - - Message queued. - Сообщение в очереди. - - - - Your 'To' field is empty. - Вы не заполнили поле 'Кому'. - - - - Right click one or more entries in your address book and select 'Send message to this address'. - Нажмите правую кнопку мыши на каком-либо адресе и выберите "Отправить сообщение на этот адрес". - - - - Fetched address from namecoin identity. - Получить адрес через Namecoin. - - - - New Message - Новое сообщение - - - - From - - - - - Sending email gateway registration request - - - - - Address is valid. - Адрес введен правильно. - - - - The address you entered was invalid. Ignoring it. - Вы ввели неправильный адрес. Это адрес проигнорирован. - - - - Error: You cannot add the same address to your address book twice. Try renaming the existing one if you want. - Ошибка: Вы не можете добавлять один и тот же адрес в Адресную Книгу несколько раз. Попробуйте переименовать существующий адрес. - - - - Error: You cannot add the same address to your subscriptions twice. Perhaps rename the existing one if you want. - Ошибка: вы не можете добавить один и тот же адрес в ваши подписки дважды. Пожалуйста, переименуйте имеющийся адрес, если хотите. - - - - Restart - Перезапустить - - - - You must restart Bitmessage for the port number change to take effect. - Вы должны перезапустить Bitmessage, чтобы смена номера порта имела эффект. - - - - Bitmessage will use your proxy from now on but you may want to manually restart Bitmessage now to close existing connections (if any). - Bitmessage будет использовать ваш прокси, начиная с этого момента. Тем не менее, имеет смысл перезапустить Bitmessage, чтобы закрыть уже существующие соединения. - - - - 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? - 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). - Адрес введен или скопирован неверно (контрольная сумма не сходится). - - - - 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). - - - - - Bitmessage - Bitmessage - - - - Identities - Адреса - - - - New Identity - Создать новый адрес - - - - Search - Поиск - - - - All - По всем полям - - - - To - Кому - - - - From - От кого - - - - Subject - Тема - - - - Message - Текст сообщения - - - - Received - Получено - - - - Messages - Сообщения - - - - Address book - Адресная книга - - - - Address - Адрес - - - - Add Contact - Добавить контакт - - - - Fetch Namecoin ID - Получить Namecoin ID - - - - Subject: - Тема: - - - - From: - От: - - - - To: - Кому: - - - - Send ordinary Message - Отправить обычное сообщение - - - - Send Message to your Subscribers - Отправить сообщение для ваших подписчиков - - - - TTL: - TTL: - - - - Subscriptions - Подписки - - - - Add new Subscription - Добавить новую подписку - - - - Chans - Чаны - - - - Add Chan - Добавить чан - - - - File - Файл - - - - Settings - Настройки - - - - Help - Помощь - - - - Import keys - Импортировать ключи - - - - Manage keys - Управлять ключами - - - - Ctrl+Q - Ctrl+Q - - - - F1 - F1 - - - - Contact support - Связаться с поддержкой - - - - About - О программе - - - - Regenerate deterministic addresses - Сгенерировать заново все адреса - - - - Delete all trashed messages - Стереть все сообщения из корзины - - - - Join / Create chan - Подключить или создать чан - - - - All accounts - Все аккаунты - - - - 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 часа%n часов%n час(а/ов) - - - - %n day(s) - %n день%n дня%n дней%n дней - - - - Shutting down PyBitmessage... %1% - Завершение PyBitmessage... %1% - - - - Sent - Отправлено - - - - Generating one new address - Создание одного нового адреса - - - - Done generating address. Doing work necessary to broadcast it... - Создание адреса завершено. Выполнение работы, требуемой для его рассылки... - - - - Generating %1 new addresses. - Создание %1 новых адресов. - - - - %1 is already in 'Your Identities'. Not adding it again. - %1 уже имеется в ваших адресах. Не добавляю его снова. - - - - Done generating address - Создание адресов завершено. - - - - 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 - Проблема: адресат является мобильным устройством, которое требует, чтобы адрес назначения был включен в сообщение, однако, это запрещено в ваших настройках. %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 объекта в ожидании доказательства работы%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) - (немного сэкономит Вам пропускную способность сети и вычислительную мощь) - - - - NewSubscriptionDialog - - - Add new entry - Добавить новую запись - - - - Label - Имя - - - - Address - Адрес - - - - Enter an address above. - Введите адрес выше. - - - - SpecialAddressBehaviorDialog - - - Special Address Behavior - Особое поведение адреса - - - - Behave as a normal address - Вести себя как обычный адрес - - - - Behave as a pseudo-mailing-list address - Вести себя как адрес псевдо-рассылки - - - - Mail received to a pseudo-mailing-list address will be automatically broadcast to subscribers (and thus will be public). - Почта, полученная на адрес псевдо-рассылки, будет автоматически разослана всем подписчикам (и поэтому будет публичной). - - - - Name of the pseudo-mailing-list: - Имя псевдо-рассылки: - - - - This is a chan address. You cannot use it as a pseudo-mailing list. - Это адрес чана. Вы не можете его использовать как адрес рассылки. - - - - aboutDialog - - - About - О программе - - - - PyBitmessage - - - - - version ? - - - - - <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> - - - - 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 - общественный проект. Вы можете найти подсказки и советы на Wiki-страничке Bitmessage: - - - - 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 будет прекрасно работать и без этого, но Вы могли бы помочь сети если бы разрешили и входящие соединения тоже. Это помогло бы Вам стать более важным узлом сети. - - - - 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. - Вы установили соединение с другими участниками сети и ваш файрвол настроен правильно. - - - - You are using TCP port %1. (This can be changed in the settings). - Вы используете TCP порт %1. (Его можно поменять в настройках). - - - - networkstatus - - - Total connections: - Всего соединений: - - - - 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/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 сообщений. - - - - Processed %n broadcast message(s). - Обработана %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 - Перечень потоков, согласованных с конкретным узлом - - - - 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> - - - - - 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: - - - - - 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. - Если Вы ранее создали детерминистические адреса, но случайно потеряли их, Вы можете их восстановить здесь. Если же Вы использовали генератор случайных чисел чтобы создать Ваши адреса, то Вы не сможете их здесь восстановить. - - - - 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) - - - - - <html><head/><body><p>Bitmessage can utilize a different Bitcoin-based program called Namecoin to make addresses human-friendly. For example, instead of having to tell your friend your long Bitmessage address, you can simply tell him to send a message to <span style=" font-style:italic;">test. </span></p><p>(Getting your own Bitmessage address into Namecoin is still rather difficult).</p><p>Bitmessage can use either namecoind directly or a running nmcontrol instance.</p></body></html> - <html><head/><body><p>Bitmessage умеет пользоваться программой Namecoin для того, чтобы сделать адреса более дружественными для пользователей. Например, вместо того, чтобы диктовать Вашему другу длинный и нудный адрес Bitmessage, Вы можете попросить его отправить сообщение на адрес вида <span style=" font-style:italic;">test. </span></p><p>(Перенести Ваш Bitmessage адрес в Namecoin по-прежнему пока довольно сложно).</p><p>Bitmessage может использовать либо прямо namecoind, либо уже запущенную программу nmcontrol.</p></body></html> - - - - Host: - Адрес: - - - - Password: - Пароль: - - - - Test - Проверить - - - - Connect to: - Соединиться с: - - - - Namecoind - Namecoind - - - - NMControl - NMControl - - - - Namecoin integration - Интеграция с Namecoin - - - - <html><head/><body><p>By default, if you send a message to someone and he is offline for more than two days, Bitmessage will send the message again after an additional two days. This will be continued with exponential backoff forever; messages will be resent after 5, 10, 20 days ect. until the receiver acknowledges them. Here you may change that behavior by having Bitmessage give up after a certain number of days or months.</p><p>Leave these input fields blank for the default behavior. </p></body></html> - <html><head/><body><p>По умолчанию, когда вы отправляете сообщение кому-либо, и адресат находится оффлайн несколько дней, ваш Bitmessage перепосылает сообщение. Это будет продолжаться с увеличивающимся по экспоненте интервалом; сообщение будет переотправляться, например, через 5, 10, 20 дней, пока адресат их запрашивает. Здесь вы можете изменить это поведение, заставив Bitmessage прекращать переотправку по прошествии указанного количества дней или месяцев.</p><p>Оставьте поля пустыми, чтобы вернуться к поведению по умолчанию.</p></body></html> - - - - Give up after - Прекратить через - - - - and - и - - - - days - дней - - - - months. - месяцев. - - - - Resends Expire - Окончание попыток отправки - - - - Hide connection notifications - Спрятать уведомления о подключениях - - - - Maximum outbound connections: [0: none] - Максимальное число исходящих подключений: [0: неограничено] - - - - Hardware GPU acceleration (OpenCL): - Аппаратное ускорение GPU - - - \ No newline at end of file diff --git a/src/translations/bitmessage_sk.qm b/src/translations/bitmessage_sk.qm deleted file mode 100644 index 26c2a24d..00000000 Binary files a/src/translations/bitmessage_sk.qm and /dev/null differ diff --git a/src/translations/bitmessage_sk.ts b/src/translations/bitmessage_sk.ts deleted file mode 100644 index 8c3b0209..00000000 --- a/src/translations/bitmessage_sk.ts +++ /dev/null @@ -1,2597 +0,0 @@ - - - AddAddressDialog - - - Add new entry - Pridať nový záznam - - - - Label - Označenie - - - - Address - Adresa - - - - 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: - - - - - 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: - - - - - 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, -# 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. - - # Tento text môžete použiť na konfiguráciu vášho účtu na e-mailovej bráne -# odkomentujte nastavenia, ktoré chcete použiť -# Tu sú možnosti: -# -# pgp: server -# E-mailová brána bude za vás vytvárať a udržiavať PGP kľúče a podpisovať, overovať, -# šifrovať a dešifrovať vaše e-maily. Ak chcete používať PGP, ale ste leniví, -# toto je voľba pre vás. Vyžaduje predplatné. -# -# pgp: local -# E-mailová brána nebude za vás vykonávať operácie PGP. Môžete buď -# nepoužívať PGP vôbec, alebo ho použiť lokálne. -# -# attachments: yes -# Prichádzajúce prílohy v e-maile budú nahrané na MEGA.nz, a môžete si ich odtiaľ stiahnuť -# pomocou odkazu v správe. Vyžaduje predplatné. -# -# attachments: no -# Prílohy budú ignorované. -# -# archive: yes -# Prichádzajúce e-maily budú archivované na serveri. Použite, ak potrebujete -# pomoc s problémami, alebo potrebujete doklad pre tretie strán o obsahu e-mailov. Táto voľba však -# znamená, že prevádzkovateľ služby budú môcť čítať vaše e-maily -# aj potom, ako vám budú doručené -# -# archive: no -# Prichádzajúce e-maily budú odstránené zo servera, akonáhle vám budú doručené -# -# masterpubkey_btc: BIP44 xpub kľúč alebo electrum v1 základ (seed) -# offset_btc: celé číslo (predvolená 0) -# feeamount: číslo s max. 8 desatinnými miest -# feecurrency: BTC, XBT, USD, EUR alebo GBP -# Ak chcete účtovať ľuďom, ktorí vám posielať e-maily, použite tieto parametre. Ak vám potom -# neznáma osoba pošle e-mail, bude požiadaná o zaplatenie poplatku -# určeného týmito premennými. -# feeamount je výška platby -# feecurrency je mena, v ktorej sa bude počítať -# Keďže systém používa deterministické verejné kľúče, platby obdržíte priamo vy -# Ak ju chcete túto funkciu opäť vypnúť, nastavte "feeamount" na 0. Vyžaduje -# predplatné. - - - - - 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. - - - - - 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. - - - - - 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. - Kľúče môžete spravovať úpravou súboru keys.dat, ktorý je uložený v adresári -%1 -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.) - Kľúče môžete spravovať úpravou súboru keys.dat, ktorý je uložený v adresári -%1 -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 - - - - - 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 - 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 - more work your computer must do to send the message. A Time-To-Live of four or five days is often appropriate. - 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 - - - - - 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. - 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 - - - - - Sending email gateway registration request - - - - - 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 - - - - - Sending email gateway status request - - - - - 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. - - - - - 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? - Ak odstránite odber, správy, ktoré ste už prijali, sa stanú nedostupné. Možno by ste mali zvážit namiesto toho odber deaktivovať. Deaktivované odbery nebudú prijímať nové správy, ale stále si môžete pozrieť správy, ktoré ste už prijali. - -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? - Ak odstránite kanál, správy, ktoré ste už prijali, sa stanú nedostupné. Možno by ste mali zvážit namiesto toho kanál deaktivovať. Deaktivované kanály nebudú prijímať nové správy, ale stále si môžete pozrieť správy, ktoré ste už prijali. - -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. - - - - - 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. - - - - - 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). - - - - - 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. - - - - - 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 - - - - - 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) - - - - NewSubscriptionDialog - - - Add new entry - Pridať nový záznam - - - - Label - Označenie - - - - Address - Adresa - - - - Enter an address above. - Zadajte adresu vyššie. - - - - 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 - - - - - 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>Ší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> - - - - blacklist - - - Use a Blacklist (Allow all incoming messages except those on the Blacklist) - Použiť ako zoznam zakázaných (prijať všetky prichádzajúce správy s výnimkou odosielateľov na zozname) - - - - Use a Whitelist (Block all incoming messages except those on the Whitelist) - Použiť ako zoznam povolených (blokovať všetky prichádzajúce správy s výnimkou odosielateľov na zozname) - - - - Add new entry - Pridať nový záznam - - - - Name or Label - Meno alebo popis - - - - Address - Adresa - - - - Blacklist - Zoznam zakázaných - - - - Whitelist - Zoznam povolených - - - - 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: - - - - 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). - - - - - 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 - - - Total connections: - 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 - - - - - 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 - - - - - 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: - - - - - 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 - - - - 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ý. - - - - 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) - - - - - <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_sv.ts b/src/translations/bitmessage_sv.ts deleted file mode 100644 index 015546b3..00000000 --- a/src/translations/bitmessage_sv.ts +++ /dev/null @@ -1,2058 +0,0 @@ - - - AddAddressDialog - - - Add new entry - - - - - Label - - - - - Address - - - - - 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. - - - - - 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: - - - - - 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, -# 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. - - - - - - 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 - - - - - Save message as... - - - - - Mark Unread - - - - - New - - - - - Enable - - - - - Disable - - - - - Set avatar... - - - - - Copy address to clipboard - - - - - Special address behavior... - - - - - Email gateway - - - - - Delete - - - - - Send message to this address - - - - - Subscribe to this address - - - - - Add New Address - - - - - Copy destination address to clipboard - - - - - Force send - - - - - One of your addresses, %1, is an old version 1 address. Version 1 addresses are no longer supported. May we delete it now? - - - - - 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 - - - - - 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. - - - - - 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... - - - - - 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 - - - - - 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 - - - - - Help - - - - - 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 - - - - - 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 - - - - - 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> - - - - - 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) - - - - - 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: - - - - - aboutDialog - - - About - - - - - 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 - - - - - connectDialog - - - Bitmessage - - - - - Bitmessage won't connect to anyone until you let it. - - - - - Connect now - - - - - Let me configure special network settings first - - - - - helpDialog - - - 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: - - - - - 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. - - - - - 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. - - - - - networkstatus - - - 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). - - - - - 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> - - - - - 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: - - - - - regenerateAddressesDialog - - - Regenerate Existing Addresses - - - - - Regenerate existing addresses - - - - - Passphrase - - - - - Number of addresses to make based on your passphrase: - - - - - Address version number: - - - - - Stream number: - - - - - 1 - - - - - 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. - - - - - 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. - - - - - 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: - - - - - Port: - - - - - Authentication - - - - - Username: - - - - - Pass: - - - - - Listen for incoming connections when using proxy - - - - - none - - - - - SOCKS4a - - - - - 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. - - - - - 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: - - - - - 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_zh_cn.qm b/src/translations/bitmessage_zh_cn.qm deleted file mode 100644 index 7cb18983..00000000 Binary files a/src/translations/bitmessage_zh_cn.qm and /dev/null differ diff --git a/src/translations/bitmessage_zh_cn.ts b/src/translations/bitmessage_zh_cn.ts deleted file mode 100644 index 474f8c6c..00000000 --- a/src/translations/bitmessage_zh_cn.ts +++ /dev/null @@ -1,2635 +0,0 @@ - - - AddAddressDialog - - - Add new entry - 添加新条目 - - - - Label - 标签 - - - - Address - 地址 - - - - 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: - - - - - 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 - - - # 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. - - - - - - # 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, 或在本地使用它。 -# -# attachement: yes -#传入附件的电子邮件将会被上传到MEGA.nz,您可以从 -# 按照那里链接下载。需要订阅。 -# -# attachement: no -#附件将被忽略。 -# -# archive: yes -#您收到的邮件将在服务器上存档。如果您有需要请使用 -#帮助调试问题,或者您需要第三方电子邮件的证明。这 -#然而,意味着服务的操作运将能够读取您的 -#电子邮件即使电子邮件已经传送给您。 -# -# archive: no -# 已传入的电子邮件将从服务器被删除只要他们已中继。 -# -# masterpubkey_btc:BIP44 XPUB键或琥珀金V1公共种子 -#offset_btc:整数(默认为0) -#feeamount:多达8位小数 -#feecurrency号:BTC,XBT,美元,欧元或英镑 -#用这些,如果您想主管谁送您的电子邮件的人。如果这是在和 -#一个不明身份的人向您发送一封电子邮件,他们将被要求支付规定的费用 -#。由于这个方案使用确定性的公共密钥,您会直接接收 -#钱。要再次将其关闭,设置“feeamount”0 -#需要订阅。 - - - - - MainWindow - - - Reply to sender - 回复发件人 - - - - Reply to channel - 回复通道 - - - - Add sender to your Address Book - 将发送者添加到您的通讯簿 - - - - Add sender to your Blacklist - 将发件人添加到您的黑名单 - - - - Move to Trash - 移入回收站 - - - - Undelete - 取消删除 - - - - View HTML code as formatted text - 作为HTML查看 - - - - Save message as... - 将消息保存为... - - - - Mark Unread - 标记为未读 - - - - New - 新建 - - - - Enable - 启用 - - - - Disable - 禁用 - - - - Set avatar... - 设置头像... - - - - Copy address to clipboard - 将地址复制到剪贴板 - - - - Special address behavior... - 特别的地址行为... - - - - Email gateway - 电子邮件网关 - - - - Delete - 删除 - - - - Send message to this address - 发送消息到这个地址 - - - - Subscribe to this address - 订阅到这个地址 - - - - Add New Address - 创建新地址 - - - - Copy destination address to clipboard - 复制目标地址到剪贴板 - - - - Force send - 强制发送 - - - - One of your addresses, %1, is an old version 1 address. Version 1 addresses are no longer supported. May we delete it now? - 您的地址中的一个, %1,是一个过时的版本1地址. 版本1地址已经不再受到支持了. 我们可以将它删除掉么? - - - - Waiting for their encryption key. Will request it again soon. - 正在等待他们的加密密钥,我们会在稍后再次请求。 - - - - 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. - - - - - 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, 通常是合适的. - - - - 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 - - - - - 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. - 地址 %1 的地址版本号 %2 无法被比特信理解。也许您应该升级您的比特信到最新版本。 - - - - Stream number - 节点流序号 - - - - Concerning the address %1, Bitmessage cannot handle stream numbers of %2. Perhaps upgrade Bitmessage to the latest version. - 地址 %1 的节点流序号 %2 无法被比特信所理解。也许您应该升级您的比特信到最新版本。 - - - - Warning: You are currently not connected. Bitmessage will do the work necessary to send the message but it won't send until you connect. - 警告: 您尚未连接。 比特信将做足够的功来发送消息,但是消息不会被发出直到您连接。 - - - - Message queued. - 信息排队。 - - - - Your 'To' field is empty. - “收件人"是空的。 - - - - Right click one or more entries in your address book and select 'Send message to this address'. - 在您的地址本的一个条目上右击,之后选择”发送消息到这个地址“。 - - - - Fetched address from namecoin identity. - 已经自namecoin接收了地址。 - - - - New Message - 新消息 - - - - From - - - - - Sending email gateway registration request - - - - - Address is valid. - 地址有效。 - - - - The address you entered was invalid. Ignoring it. - 您输入的地址是无效的,将被忽略。 - - - - Error: You cannot add the same address to your address book twice. Try renaming the existing one if you want. - 错误:您无法将一个地址添加到您的地址本两次,请尝试重命名已经存在的那个。 - - - - Error: You cannot add the same address to your subscriptions twice. Perhaps rename the existing one if you want. - 错误: 您不能在同一地址添加到您的订阅两次. 也许您可重命名现有之一. - - - - Restart - 重启 - - - - You must restart Bitmessage for the port number change to take effect. - 您必须重启以便使比特信对于使用的端口的改变生效。 - - - - Bitmessage will use your proxy from now on but you may want to manually restart Bitmessage now to close existing connections (if any). - 比特信将会从现在开始使用代理,但是您可能想手动重启比特信以便使之前的连接关闭(如果有的话)。 - - - - Number needed - 需求数字 - - - - Your maximum download and upload rate must be numbers. Ignoring what you typed. - 您最大的下载和上传速率必须是数字. 忽略您键入的内容. - - - - Will not resend ever - 不尝试再次发送 - - - - Note that the time limit you entered is less than the amount of time Bitmessage waits for the first resend attempt therefore your messages will never be resent. - 请注意,您所输入的时间限制小于比特信的最小重试时间,因此您将永远不会重发消息。 - - - - Sending email gateway unregistration request - - - - - Sending email gateway status request - - - - - Passphrase mismatch - 密钥不匹配 - - - - The passphrase you entered twice doesn't match. Try again. - 您两次输入的密码并不匹配,请再试一次。 - - - - Choose a passphrase - 选择一个密钥 - - - - You really do need a passphrase. - 您真的需要一个密码。 - - - - Address is gone - 已经失去了地址 - - - - Bitmessage cannot find your address %1. Perhaps you removed it? - 比特信无法找到您的地址 %1 ,也许您已经把它删掉了? - - - - Address disabled - 地址已经禁用 - - - - Error: The address from which you are trying to send is disabled. You'll have to enable it on the 'Your Identities' tab before using it. - 错误: 您想以一个您已经禁用的地址发出消息。在使用之前您需要在“您的身份”处再次启用。 - - - - Entry added to the Address Book. Edit the label to your liking. - - - - - Entry added to the blacklist. Edit the label to your liking. - 条目添加到黑名单. 根据自己的喜好编辑标签. - - - - Error: You cannot add the same address to your blacklist twice. Try renaming the existing one if you want. - 错误: 您不能在同一地址添加到您的黑名单两次. 也许您可重命名现有之一. - - - - Moved items to trash. - 已经移动项目到回收站。 - - - - Undeleted item. - 未删除的项目。 - - - - Save As... - 另存为... - - - - Write error. - 写入失败。 - - - - No addresses selected. - 没有选择地址。 - - - - If you delete the subscription, messages that you already received will become inaccessible. Maybe you can consider disabling the subscription instead. Disabled subscriptions will not receive new messages, but you can still view messages you already received. - -Are you sure you want to delete the subscription? - 如果删除订阅, 您已经收到的信息将无法访问. 也许您可以考虑禁用订阅.禁用订阅将不会收到新信息, 但您仍然可以看到您已经收到的信息. - -您确定要删除订阅? - - - - 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). - - - - - 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 - 添加 Chans - - - - File - 文件 - - - - Settings - 设置 - - - - Help - 帮助 - - - - Import keys - 导入密钥 - - - - Manage keys - 管理密钥 - - - - Ctrl+Q - Ctrl+Q - - - - F1 - F1 - - - - Contact support - 联系支持 - - - - About - 关于 - - - - Regenerate deterministic addresses - 重新生成静态地址 - - - - Delete all trashed messages - 彻底删除全部回收站中的消息 - - - - Join / Create chan - 加入或创建一个频道 - - - - All accounts - 所有帐户 - - - - Zoom level %1% - 缩放级别%1% - - - - Error: You cannot add the same address to your list twice. Perhaps rename the existing one if you want. - 错误: 您不能在同一地址添加到列表中两次. 也许您可重命名现有之一. - - - - Add new entry - 添加新条目 - - - - Display the %1 recent broadcast(s) from this address. - - - - - 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. - 警告: 您的磁盘或数据存储量已满. 比特信将立即退出. - - - - Error! Could not find sender address (your address) in the keys.dat file. - 错误! 找不到在keys.dat 件发件人的地址 ( 您的地址). - - - - Doing work necessary to send broadcast... - 做必要的工作, 以发送广播... - - - - Broadcast sent on %1 - 广播发送%1 - - - - Encryption key was requested earlier. - 加密密钥已请求. - - - - Sending a request for the recipient's encryption key. - 发送收件人的加密密钥的请求. - - - - Looking up the receiver's public key - 展望接收方的公钥 - - - - Problem: Destination is a mobile device who requests that the destination be included in the message but this is disallowed in your settings. %1 - 问题:对方是移动设备,并且对方的地址包含在此消息中,但是您的设置禁止了。 %1 - - - - Doing work necessary to send message. -There is no required difficulty for version 2 addresses like this. - 做必要的工作, 以发送信息. -这样第2版的地址没有难度. - - - - Doing work necessary to send message. -Receiver's required difficulty: %1 and %2 - 做必要的工作, 以发送短信. -接收者的要求难度: %1与%2 - - - - Problem: The work demanded by the recipient (%1 and %2) is more difficult than you are willing to do. %3 - 问题: 由接收者(%1%2)要求的工作量比您愿意做的工作量來得更困难. %3 - - - - Problem: You are trying to send a message to yourself or a chan but your encryption key could not be found in the keys.dat file. Could not encrypt message. %1 - 问题: 您正在尝试将信息发送给自己或频道, 但您的加密密钥无法在keys.dat文件中找到. 无法加密信息. %1 - - - - Doing work necessary to send message. - 做必要的工作, 以发送信息. - - - - Message sent. Waiting for acknowledgement. Sent on %1 - 信息发送. 等待确认. 已发送%1 - - - - Doing work necessary to request encryption key. - 做必要的工作以要求加密密钥. - - - - Broadcasting the public key request. This program will auto-retry if they are offline. - 广播公钥请求. 这个程序将自动重试, 如果他们处于离线状态. - - - - Sending public key request. Waiting for reply. Requested at %1 - 发送公钥的请求. 等待回复. 请求在%1 - - - - UPnP port mapping established on port %1 - UPnP端口映射建立在端口%1 - - - - UPnP port mapping removed - UPnP端口映射被删除 - - - - Mark all messages as read - 标记全部信息为已读 - - - - Are you sure you would like to mark all messages read? - 确定将所有信息标记为已读吗? - - - - Doing work necessary to send broadcast. - 持续进行必要的工作,以发送广播。 - - - - Proof of work pending - 待传输内容的校验 - - - - %n object(s) pending proof of work - %n 待传输内容校验任务 - - - - %n object(s) waiting to be distributed - %n 任务等待分配 - - - - Wait until these tasks finish? - 等待所有任务执行完? - - - - 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 - - - - - 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> - - - - 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 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上了解如何帮助比特信: - - - - 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连接到您的电脑。比特信将正常运行,不过如果您允许入站连接的话将帮助比特信网络并成为一个通信状态更好的节点。 - - - - 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. - 您有和其他节点的连接且您的防火墙已经正确配置。 - - - - You are using TCP port %1. (This can be changed in the settings). - 您正在使用TCP端口 %1 。(可以在设置中修改)。 - - - - networkstatus - - - Total connections: - 总连接: - - - - 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> - - - - - 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: - - - - - 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. - 如果您之前创建了静态地址,但是因为一些意外失去了它们(比如硬盘坏了),您可以在这里将他们再次生成。如果您使用随机数来生成的地址的话,那么这个表格对您没有帮助。 - - - - settingsDialog - - - Settings - 设置 - - - - Start Bitmessage on user login - 在用户登录时启动比特信 - - - - Tray - 任务栏 - - - - Start Bitmessage in the tray (don't show main window) - 启动比特信到托盘 (不要显示主窗口) - - - - Minimize to tray - 最小化到托盘 - - - - Close to tray - 关闭任务栏 - - - - Show notification when message received - 在收到消息时提示 - - - - Run in Portable Mode - 以便携方式运行 - - - - In Portable Mode, messages and config files are stored in the same directory as the program rather than the normal application-data folder. This makes it convenient to run Bitmessage from a USB thumb drive. - 在便携模式下, 消息和配置文件和程序保存在同一个目录而不是通常的程序数据文件夹。 这使在U盘中允许比特信很方便。 - - - - Willingly include unencrypted destination address when sending to a mobile device - 愿意在发送到手机时使用不加密的目标地址 - - - - Use Identicons - 用户身份 - - - - Reply below Quote - 回复 引述如下 - - - - Interface Language - 界面语言 - - - - System Settings - system - 系统设置 - - - - User Interface - 用户界面 - - - - Listening port - 监听端口 - - - - Listen for connections on port: - 监听连接于端口: - - - - UPnP: - UPnP: - - - - Bandwidth limit - 带宽限制 - - - - Maximum download rate (kB/s): [0: unlimited] - 最大下载速率(kB/秒): [0: 无限制] - - - - Maximum upload rate (kB/s): [0: unlimited] - 最大上传速度 (kB/秒): [0: 无限制] - - - - Proxy server / Tor - 代理服务器 / Tor - - - - Type: - 类型: - - - - Server hostname: - 服务器主机名: - - - - Port: - 端口: - - - - Authentication - 认证 - - - - Username: - 用户名: - - - - Pass: - 密码: - - - - Listen for incoming connections when using proxy - 在使用代理时仍然监听入站连接 - - - - none - - - - - SOCKS4a - SOCKS4a - - - - SOCKS5 - SOCKS5 - - - - Network Settings - 网络设置 - - - - Total difficulty: - 总难度: - - - - The 'Total difficulty' affects the absolute amount of work the sender must complete. Doubling this value doubles the amount of work. - “总难度”影响发送者所需要的做工总数。当这个值翻倍时,做工的总数也翻倍。 - - - - Small message difficulty: - 小消息难度: - - - - When someone sends you a message, their computer must first complete some work. The difficulty of this work, by default, is 1. You may raise this default for new addresses you create by changing the values here. Any new addresses you create will require senders to meet the higher difficulty. There is one exception: if you add a friend or acquaintance to your address book, Bitmessage will automatically notify them when you next send a message that they need only complete the minimum amount of work: difficulty 1. - 当一个人向您发送消息的时候, 他们的电脑必须先做工。这个难度的默认值是1,您可以在创建新的地址前提高这个值。任何新创建的地址都会要求更高的做工量。这里有一个例外,当您将您的朋友添加到地址本的时候,比特信将自动提示他们,当他们下一次向您发送的时候,他们需要的做功量将总是1. - - - - The 'Small message difficulty' mostly only affects the difficulty of sending small messages. Doubling this value makes it almost twice as difficult to send a small message but doesn't really affect large messages. - “小消息困难度”几乎仅影响发送消息。当这个值翻倍时,发小消息时做工的总数也翻倍,但是并不影响大的消息。 - - - - Demanded difficulty - 要求的难度 - - - - Here you may set the maximum amount of work you are willing to do to send a message to another person. Setting these values to 0 means that any value is acceptable. - 您可以在这里设置您所愿意接受的发送消息的最大难度。0代表接受任何难度。 - - - - Maximum acceptable total difficulty: - 最大接受难度: - - - - Maximum acceptable small message difficulty: - 最大接受的小消息难度: - - - - Max acceptable difficulty - 最大可接受难度 - - - - 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> - <html><head/><body><p>比特信可以利用基于比特币的Namecoin让地址更加友好。比如除了告诉您的朋友您的长长的比特信地址,您还可以告诉他们发消息给 <span style=" font-style:italic;">test. </span></p><p>把您的地址放入Namecoin还是相当的难的.</p><p>比特信可以不但直接连接到namecoin守护程序或者连接到运行中的nmcontrol实例.</p></body></html> - - - - Host: - 主机名: - - - - Password: - 密码: - - - - Test - 测试 - - - - Connect to: - 连接到: - - - - Namecoind - Namecoind - - - - NMControl - NMControl - - - - Namecoin integration - Namecoin整合 - - - - <html><head/><body><p>By default, if you send a message to someone and he is offline for more than two days, Bitmessage will send the message again after an additional two days. This will be continued with exponential backoff forever; messages will be resent after 5, 10, 20 days ect. until the receiver acknowledges them. Here you may change that behavior by having Bitmessage give up after a certain number of days or months.</p><p>Leave these input fields blank for the default behavior. </p></body></html> - <html><head/><body><p>您发给他们的消息默认会在网络上保存两天,之后比特信会再重发一次. 重发时间会随指数上升; 消息会在5, 10, 20... 天后重发并以此类推. 直到收到收件人的回执. 您可以在这里改变这一行为,让比特信在尝试一段时间后放弃.</p><p>留空意味着默认行为. </p></body></html> - - - - Give up after - - - - - and - - - - - days - - - - - months. - 月后放弃。 - - - - Resends Expire - 重发超时 - - - - Hide connection notifications - 隐藏连接通知 - - - - Maximum outbound connections: [0: none] - 最大外部连接:[0: 无] - - - - Hardware GPU acceleration (OpenCL): - 硬件GPU加速(OpenCL): - - - \ No newline at end of file diff --git a/src/upnp.py b/src/upnp.py deleted file mode 100644 index aa04a556..00000000 --- a/src/upnp.py +++ /dev/null @@ -1,350 +0,0 @@ -# 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 -""" - -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 tr -from bmconfigparser import config -from debug import logger -from network import BMConnectionPool, knownnodes, StoppableThread -from network.node import Peer - - -def createRequestXML(service, action, arguments=None): - """Router UPnP requests are XML formatted""" - - doc = Document() - - # create the envelope element and set its attributes - envelope = doc.createElementNS('', 's:Envelope') - envelope.setAttribute('xmlns:s', 'http://schemas.xmlsoap.org/soap/envelope/') - envelope.setAttribute('s:encodingStyle', 'http://schemas.xmlsoap.org/soap/encoding/') - - # create the body element - body = doc.createElementNS('', 's:Body') - - # create the function element and set its attribute - fn = doc.createElementNS('', 'u:%s' % action) - fn.setAttribute('xmlns:u', 'urn:schemas-upnp-org:service:%s' % service) - - # setup the argument element names and values - # using a list of tuples to preserve order - - # container for created nodes - argument_list = [] - - # 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) - - # append the prepared argument nodes to the function element - for arg in argument_list: - fn.appendChild(arg) - - # append function element to the body element - body.appendChild(fn) - - # append body element to envelope element - envelope.appendChild(body) - - # append envelope element to document, making it the root element - doc.appendChild(envelope) - - # 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) - - -class Router: # pylint: disable=old-style-class - """Encapulate routing""" - name = "" - path = "" - address = None - routerPath = None - extPort = None - - def __init__(self, ssdpResponse, address): - - self.address = address - - row = ssdpResponse.split('\r\n') - header = {} - for i in range(1, len(row)): - part = row[i].split(': ') - if len(part) == 2: - header[part[0].lower()] = part[1] - - try: - self.routerPath = urlparse(header['location']) - if not self.routerPath or not hasattr(self.routerPath, "hostname"): - logger.error("UPnP: no hostname: %s", header['location']) - except KeyError: - logger.error("UPnP: missing location header") - - # get the profile xml file and read it into a variable - directory = urllib2.urlopen(header['location']).read() - - # create a DOM object that represents the `directory` document - dom = parseString(directory) - - self.name = dom.getElementsByTagName('friendlyName')[0].childNodes[0].data - # find all 'serviceType' elements - service_types = dom.getElementsByTagName('serviceType') - - for service in service_types: - if service.childNodes[0].data.find('WANIPConnection') > 0 or \ - 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""" - - 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)) - ]) - self.extPort = 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""" - - resp = self.soapRequest(self.upnp_schema + ':1', 'DeletePortMapping', [ - ('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""" - - conn = httplib.HTTPConnection(self.routerPath.hostname, self.routerPath.port) - conn.request( - 'POST', - self.path, - createRequestXML(service, action, arguments), - { - 'SOAPAction': '"urn:schemas-upnp-org:service:%s#%s"' % (service, action), - 'Content-Type': 'text/xml' - } - ) - resp = conn.getresponse() - conn.close() - if resp.status == 500: - respData = resp.read() - try: - dom = parseString(respData) - errinfo = dom.getElementsByTagName('errorDescription') - if errinfo: - 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)) - return resp - - -class uPnPThread(StoppableThread): - """Start a thread to handle UPnP activity""" - - 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) - self.localIP = self.getLocalIP() - self.routers = [] - self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - self.sock.bind((self.localIP, 0)) - self.sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2) - self.sock.settimeout(5) - self.sendSleep = 60 - - def run(self): - """Start the thread to manage UPnP activity""" - - logger.debug("Starting UPnP thread") - logger.debug("Local IP: %s", self.localIP) - lastSent = 0 - - # wait until asyncore binds so that we know the listening port - bound = False - while state.shutdown == 0 and not self._stopped and not bound: - for s in BMConnectionPool().listeningSockets.values(): - if s.is_bound(): - bound = True - if not bound: - time.sleep(1) - - # 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: - try: - self.sendSearchRouter() - except: # nosec B110 # noqa:E722 # pylint:disable=bare-except - pass - lastSent = time.time() - try: - while state.shutdown == 0 and config.safeGetBoolean('bitmessagesettings', 'upnp'): - resp, (ip, _) = self.sock.recvfrom(1000) - if resp is None: - continue - newRouter = Router(resp, ip) - for router in self.routers: - if router.routerPath == newRouter.routerPath: - 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)))) - break - except socket.timeout: - pass - except: # noqa:E722 - 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 - pass - try: - self.sock.close() - except (IOError, OSError): # noqa:E722 - pass - deleted = False - for router in self.routers: - if router.extPort is not None: - deleted = True - self.deletePortMapping(router) - if deleted: - queues.UISignalQueue.put(('updateStatusBar', tr._translate("MainWindow", 'UPnP port mapping removed'))) - logger.debug("UPnP thread done") - - def getLocalIP(self): - """Get the local IP of the node""" - - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) - s.connect((uPnPThread.GOOGLE_DNS, 1)) - return s.getsockname()[0] - - def sendSearchRouter(self): - """Querying for UPnP services""" - - 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" - - try: - logger.debug("Sending UPnP query") - self.sock.sendto(ssdpRequest, (uPnPThread.SSDP_ADDR, uPnPThread.SSDP_PORT)) - except: # noqa:E722 - logger.exception("UPnP send query failed") - - def createPortMapping(self, router): - """Add a port mapping""" - - for i in range(50): - try: - localIP = self.localIP - if i == 0: - extPort = self.localPort # try same port first - elif i == 1 and self.extPort: - 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) - router.AddPortMapping(extPort, self.localPort, localIP, 'TCP', 'BitMessage') - self.extPort = extPort - config.set('bitmessagesettings', 'extport', str(extPort)) - config.save() - break - except UPnPError: - logger.debug("UPnP error: ", exc_info=True) - - def deletePortMapping(self, router): - """Delete a port mapping""" - router.DeletePortMapping(router.extPort, 'TCP') diff --git a/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 fec3a54c..00000000 --- a/tox.ini +++ /dev/null @@ -1,67 +0,0 @@ -[tox] -requires = virtualenv<20.22.0 -envlist = reset,py{27,27-portable,35,36,38,39,310},stats -skip_missing_interpreters = true - -[testenv] -setenv = - BITMESSAGE_HOME = {envtmpdir} - HOME = {envtmpdir} - PYTHONWARNINGS = default -deps = -rrequirements.txt -commands = - python checkdeps.py - python src/bitmessagemain.py -t - coverage run -a -m tests - -[testenv:lint-basic] -skip_install = true -basepython = python3 -deps = - bandit - flake8 -commands = - bandit -r -s B101,B411,B413,B608 \ - -x checkdeps.*,bitmessagecurses,bitmessageqt,tests pybitmessage - flake8 pybitmessage --count --select=E9,F63,F7,F82 \ - --show-source --statistics - -[testenv:py27] -sitepackages = true - -[testenv:py27-doc] -deps = - .[docs] - -r docs/requirements.txt -commands = python setup.py build_sphinx - -[testenv:py27-portable] -skip_install = true -commands = python pybitmessage/bitmessagemain.py -t - -[testenv:py35] -skip_install = true - -[testenv:reset] -skip_install = true -deps = coverage -commands = coverage erase - -[testenv:stats] -skip_install = true -deps = coverage -commands = - coverage report - coverage xml - -[coverage:run] -source = src -omit = - tests.py - */tests/* - src/bitmessagekivy/* - src/version.py - src/fallback/umsgpack/* - -[coverage:report] -ignore_errors = true