diff --git a/.buildbot/android/build.sh b/.buildbot/android/build.sh index f2c08ac5f1..9b47c30bed 100755 --- a/.buildbot/android/build.sh +++ b/.buildbot/android/build.sh @@ -1,4 +1,5 @@ #!/bin/bash +exit 0 # migrated to .gitea/workflows/android.yml export LC_ALL=en_US.UTF-8 export LANG=en_US.UTF-8 diff --git a/.buildbot/android/test.sh b/.buildbot/android/test.sh index 65a0fe7dfa..a00ad87838 100755 --- a/.buildbot/android/test.sh +++ b/.buildbot/android/test.sh @@ -1,4 +1,5 @@ #!/bin/bash +exit 0 # migrated to .gitea/workflows/android.yml RELEASE_ARTIFACT=$(grep release_artifact packages/android/buildozer.spec |cut -d= -f2|tr -Cd 'a-z') diff --git a/.buildbot/tox-bionic/Dockerfile b/.buildbot/tox-bionic/Dockerfile deleted file mode 100644 index 1acf58dcd7..0000000000 --- a/.buildbot/tox-bionic/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -FROM ubuntu:bionic - -ENV DEBIAN_FRONTEND=noninteractive - -RUN apt-get update - -RUN apt-get install -yq --no-install-suggests --no-install-recommends \ - software-properties-common build-essential libcap-dev libffi-dev \ - libssl-dev python-all-dev python-setuptools \ - python3-dev python3-pip python3.8 python3.8-dev python3.8-venv \ - python-msgpack python-qt4 language-pack-en qt5dxcb-plugin tor xvfb - -RUN apt-get install -yq sudo - -RUN echo 'builder ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers - -RUN python3.8 -m pip install setuptools wheel -RUN python3.8 -m pip install --upgrade pip tox virtualenv - -ENV LANG en_US.UTF-8 -ENV LANGUAGE en_US:en -ENV LC_ALL en_US.UTF-8 - -ADD . . - -CMD .buildbot/tox-bionic/test.sh diff --git a/.buildbot/tox-bionic/build.sh b/.buildbot/tox-bionic/build.sh deleted file mode 100755 index 87f670ce54..0000000000 --- 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 b280953a42..0000000000 --- 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 fecc081985..0000000000 --- a/.buildbot/tox-focal/Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -FROM ubuntu:focal - -ENV DEBIAN_FRONTEND=noninteractive - -RUN apt-get update - -RUN apt-get install -yq --no-install-suggests --no-install-recommends \ - software-properties-common build-essential libcap-dev libffi-dev \ - libssl-dev python-all-dev python-setuptools \ - python3-dev python3-pip python3.9 python3.9-dev python3.9-venv \ - language-pack-en qt5dxcb-plugin tor xvfb - -RUN python3.9 -m pip install --upgrade pip tox virtualenv - -ADD . . - -CMD .buildbot/tox-focal/test.sh diff --git a/.buildbot/tox-focal/test.sh b/.buildbot/tox-focal/test.sh deleted file mode 120000 index a9f8525c17..0000000000 --- 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 b15c3b8f27..0000000000 --- a/.buildbot/tox-jammy/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -FROM ubuntu:jammy - -ENV DEBIAN_FRONTEND=noninteractive - -RUN apt-get update - -RUN apt-get install -yq --no-install-suggests --no-install-recommends \ - software-properties-common build-essential libcap-dev libffi-dev \ - libssl-dev python-all-dev python-is-python3 python-setuptools \ - python3-dev python3-pip language-pack-en qt5dxcb-plugin tor xvfb - -RUN pip install tox - -ADD . . - -CMD .buildbot/tox-jammy/test.sh diff --git a/.buildbot/tox-jammy/test.sh b/.buildbot/tox-jammy/test.sh deleted file mode 100755 index ab6134c458..0000000000 --- a/.buildbot/tox-jammy/test.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -tox -e lint || exit 1 -tox -e py310 diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index d00fdd111d..d6e7102101 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -17,7 +17,11 @@ "visualstudioexptteam.vscodeintellicode" ], "settings": { + "python.defaultInterpreterPath": "/usr/bin/python3", + "flake8.interpreter": ["/usr/bin/python3"], + "flake8.importStrategy": "fromEnvironment", "flake8.args": ["--config=setup.cfg"], + "pylint.interpreter": ["/usr/bin/python3"], "pylint.args": ["--rcfile=setup.cfg", "--init-hook", "import sys;sys.path.append('src')"], "terminal.integrated.shell.linux": "/usr/bin/zsh", "terminal.integrated.defaultProfile.linux": "zsh", diff --git a/.devcontainer/postCreateCommand.sh b/.devcontainer/postCreateCommand.sh index 2350f686fe..f04a3b7d3d 100755 --- a/.devcontainer/postCreateCommand.sh +++ b/.devcontainer/postCreateCommand.sh @@ -1,4 +1,9 @@ #!/bin/sh pip3 install -r requirements.txt -pip3 install -r kivy-requirements.txt \ No newline at end of file +pip3 install -r kivy-requirements.txt + +# Linter tools needed by the VS Code extensions (ms-python.flake8, +# ms-python.pylint, nwgh.bandit). The apt-installed system packages +# are not visible to the VS Code Python interpreter. +pip3 install flake8 pylint bandit \ No newline at end of file diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml new file mode 100644 index 0000000000..9fa8454c3c --- /dev/null +++ b/.github/workflows/android.yml @@ -0,0 +1,92 @@ +name: Android Build + +on: + push: + branches: [master, main, v0.6] + pull_request: + branches: [master, main, v0.6] + +jobs: + build: + # Only run on Gitea; this build needs a self-hosted runner with + # the tool cache and is not meant for GitHub's hosted runners. + if: >- + !contains(github.server_url, 'github.com') + name: buildozer android debug + runs-on: ubuntu-latest + container: ubuntu:focal + + steps: + - name: Install system dependencies + env: + DEBIAN_FRONTEND: noninteractive + run: | + apt-get update -qq + apt-get -y install -qq --no-install-recommends \ + locales git curl autoconf automake build-essential cmake \ + libtool libltdl-dev libffi-dev libssl-dev \ + patch pkg-config python-is-python3 python3-dev python3-pip \ + unzip zip openjdk-17-jdk libzbar0 gettext + locale-gen en_US.UTF-8 + + - name: Checkout repository + env: + TOKEN: ${{ github.token }} + run: | + git config --global --add safe.directory "$PWD" + git init + SERVER="${GITHUB_SERVER_URL#https://}" + git remote add origin "https://x-access-token:${TOKEN}@${SERVER}/${GITHUB_REPOSITORY}.git" + git fetch --depth 1 origin "${GITHUB_SHA}" + git checkout FETCH_HEAD + + - name: Install buildozer and cython + run: pip install buildozer cython==3.0.10 virtualenv + + - name: Create build user + run: | + useradd -m builder + chown -R builder:builder . + chown -R builder:builder "${RUNNER_TOOL_CACHE}" + + - name: Prepare source tree + run: | + # buildozer symlink workaround (from build.sh) + rm -rf src/pybitmessage + mkdir -p src/pybitmessage + cp src/*.py src/pybitmessage + cp -r src/bitmessagekivy src/backend src/mockbm src/images src/pybitmessage + + # Symlink both buildozer directories to persistent tool cache: + # ~/.buildozer holds SDK/NDK downloads + # packages/android/.buildozer holds p4a toolchain and build artifacts + mkdir -p "${RUNNER_TOOL_CACHE}/buildozer-home" "${RUNNER_TOOL_CACHE}/buildozer-builddir" + ln -sfn "${RUNNER_TOOL_CACHE}/buildozer-home" /home/builder/.buildozer + ln -sfn "${RUNNER_TOOL_CACHE}/buildozer-builddir" packages/android/.buildozer + chown -R builder:builder "${RUNNER_TOOL_CACHE}/buildozer-home" "${RUNNER_TOOL_CACHE}/buildozer-builddir" + + - name: Build APK + env: + LC_ALL: en_US.UTF-8 + LANG: en_US.UTF-8 + run: | + runuser -u builder -- bash -c ' + # gradle OOM workaround + mkdir -p ~/.gradle + echo "org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8" \ + > ~/.gradle/gradle.properties + cd packages/android + buildozer android debug + ' + + - name: Verify APK contents + run: | + RELEASE_ARTIFACT=$(grep release_artifact packages/android/buildozer.spec | cut -d= -f2 | tr -Cd 'a-z') + if [ "$RELEASE_ARTIFACT" = "aab" ]; then + echo "Release artifact is AAB, skipping APK verification" + exit 0 + fi + unzip -p packages/android/bin/*.apk assets/private.tar \ + | tar --list -z > package.list + cat package.list + grep '\.sql$' package.list || exit 1 diff --git a/.github/workflows/tox-lint.yml b/.github/workflows/tox-lint.yml new file mode 100644 index 0000000000..2987315fbb --- /dev/null +++ b/.github/workflows/tox-lint.yml @@ -0,0 +1,76 @@ +name: Tox Linter Tests + +on: + push: + branches: [master, main, v0.6] + pull_request: + branches: [master, main, v0.6] + +jobs: + lint: + name: "${{ matrix.tox-env }} / python ${{ matrix.python-version }}" + runs-on: ubuntu-latest + container: ${{ matrix.container }} + strategy: + fail-fast: false + matrix: + tox-env: [bandit, flake8, pycodestyle, pylint] + python-version: ["2.7", "3.10"] + include: + - python-version: "2.7" + container: "python:2.7-buster" + tox-suffix: "-py27" + - python-version: "3.10" + tox-suffix: "" + + steps: + # ── System packages ──────────────────────────────────────── + # Container (Python 2): runs as root, needs git for checkout + - name: Install system dependencies (container) + if: matrix.python-version == '2.7' + run: | + sed -i 's|deb.debian.org|archive.debian.org|g' /etc/apt/sources.list + sed -i 's|security.debian.org|archive.debian.org|g' /etc/apt/sources.list + sed -i '/buster-updates/d' /etc/apt/sources.list + apt-get update -q + apt-get install -qy git libcap-dev + + # Host VM (Python 3): git is pre-installed, just need libcap-dev + - name: Install system dependencies + if: matrix.python-version != '2.7' + run: | + sudo apt-get update -q + sudo apt-get install -qy libcap-dev + + # GitHub mounts Node into containers, so actions/checkout works everywhere. + # Gitea runners don't, so the buster container needs a manual git clone. + - name: Checkout repository + if: "!(matrix.python-version == '2.7' && env.GITEA_ACTIONS == 'true')" + uses: actions/checkout@v4 + + - name: Checkout repository (Gitea container) + if: matrix.python-version == '2.7' && env.GITEA_ACTIONS == 'true' + env: + TOKEN: ${{ github.token }} + run: | + git config --global --add safe.directory "$PWD" + git init + SERVER="${GITHUB_SERVER_URL#https://}" + git remote add origin "https://x-access-token:${TOKEN}@${SERVER}/${GITHUB_REPOSITORY}.git" + git fetch --depth 1 origin "${GITHUB_SHA}" + git checkout FETCH_HEAD + + # ── Python 3 ─────────────────────────────────────────────── + - name: Set up Python ${{ matrix.python-version }} + if: matrix.python-version != '2.7' + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + # ── Common ───────────────────────────────────────────────── + - name: Install tox + run: | + pip install --upgrade tox virtualenv + + - name: Run tox -e ${{ matrix.tox-env }}${{ matrix.tox-suffix }} + run: tox -e ${{ matrix.tox-env }}${{ matrix.tox-suffix }} diff --git a/.github/workflows/tox-tests.yml b/.github/workflows/tox-tests.yml new file mode 100644 index 0000000000..b580fee9ae --- /dev/null +++ b/.github/workflows/tox-tests.yml @@ -0,0 +1,154 @@ +name: Tox Tests + +on: + push: + branches: [master, main, v0.6] + pull_request: + branches: [master, main, v0.6] + +jobs: + tests: + name: "Ubuntu ${{ matrix.ubuntu }} / ${{ matrix.tox-env }}" + runs-on: ubuntu-latest + container: + image: ubuntu:${{ matrix.ubuntu }} + options: --init + + strategy: + fail-fast: false + matrix: + include: + # ── Python 2.7 (bionic & focal only) ───────────────── + - ubuntu: bionic + tox-env: py27 + stats-env: ",stats" + python-pkg: "python3.8" + python-dev-pkg: "python-all-dev python3.8-dev" + python-venv-pkg: "python3.8-venv" + python-bin: python3.8 + extra-pkgs: "python-setuptools python-msgpack python-prctl + python-qt4" + start-tor: false + deadsnakes-ppa: false + - ubuntu: bionic + tox-env: py27-portable + python-pkg: "python3.8" + python-dev-pkg: "python-all-dev python3.8-dev" + python-venv-pkg: "python3.8-venv" + python-bin: python3.8 + extra-pkgs: "python-setuptools python-msgpack python-prctl + python-qt4" + start-tor: false + deadsnakes-ppa: false + - ubuntu: focal + tox-env: py27 + stats-env: ",stats" + python-pkg: "python3.9 python3-pip" + python-dev-pkg: "python-all-dev python3.9-dev" + python-venv-pkg: "python3.9-venv" + python-bin: python3.9 + extra-pkgs: "python-setuptools" + start-tor: false + deadsnakes-ppa: false + - ubuntu: focal + tox-env: py27-portable + python-pkg: "python3.9 python3-pip" + python-dev-pkg: "python-all-dev python3.9-dev" + python-venv-pkg: "python3.9-venv" + python-bin: python3.9 + extra-pkgs: "python-setuptools" + start-tor: false + deadsnakes-ppa: false + # ── Python 3 ───────────────────────────────────────── + - ubuntu: bionic + tox-env: py38 + stats-env: ",stats" + python-pkg: python3.8 + python-dev-pkg: python3.8-dev + python-venv-pkg: python3.8-venv + python-bin: python3.8 + extra-pkgs: "" + start-tor: false + deadsnakes-ppa: true + - ubuntu: focal + tox-env: py39 + stats-env: ",stats" + python-pkg: python3.9 + python-dev-pkg: python3.9-dev + python-venv-pkg: python3.9-venv + python-bin: python3.9 + extra-pkgs: "" + start-tor: false + deadsnakes-ppa: true + - ubuntu: jammy + tox-env: py310 + stats-env: ",stats" + python-pkg: python3 + python-dev-pkg: python3-dev + python-venv-pkg: "" + python-bin: python3 + extra-pkgs: "" + start-tor: false + deadsnakes-ppa: false + + env: + DEBIAN_FRONTEND: noninteractive + LANG: en_US.UTF-8 + LANGUAGE: "en_US:en" + LC_ALL: en_US.UTF-8 + + steps: + - name: Install base packages + run: | + apt-get update -q + apt-get install -yq --no-install-suggests --no-install-recommends \ + software-properties-common + + - name: Add deadsnakes PPA + if: matrix.deadsnakes-ppa + run: add-apt-repository -y ppa:deadsnakes/ppa + + - name: Install system dependencies + run: | + apt-get install -yq --no-install-suggests --no-install-recommends \ + build-essential libcap-dev libffi-dev \ + libssl-dev python3-dev python3-pip \ + ${{ matrix.python-pkg }} ${{ matrix.python-dev-pkg }} \ + ${{ matrix.python-venv-pkg }} ${{ matrix.extra-pkgs }} \ + language-pack-en qt5dxcb-plugin tor xvfb git sudo dumb-init + + # actions/checkout@v4 needs Node 20 → glibc 2.28+. + # Bionic (glibc 2.27) and Gitea runners use a manual git clone. + - name: Checkout repository + if: env.GITEA_ACTIONS != 'true' && matrix.ubuntu != 'bionic' + uses: actions/checkout@v4 + + - name: Checkout repository (manual) + if: env.GITEA_ACTIONS == 'true' || matrix.ubuntu == 'bionic' + env: + TOKEN: ${{ github.token }} + run: | + git config --global --add safe.directory "$PWD" + git init + SERVER="${GITHUB_SERVER_URL#https://}" + git remote add origin "https://x-access-token:${TOKEN}@${SERVER}/${GITHUB_REPOSITORY}.git" + git fetch --depth 1 origin "${GITHUB_SHA}" + git checkout FETCH_HEAD + + - name: Install tox + run: | + ${{ matrix.python-bin }} -m pip install --upgrade pip setuptools wheel + ${{ matrix.python-bin }} -m pip install --upgrade tox virtualenv + + - name: Create unprivileged user + run: | + useradd -m builder + chown -R builder:builder . + + # TODO: fix tor tests + # - name: Start tor + # if: matrix.start-tor + # run: service tor start + + - name: Run tox ${{ matrix.tox-env }} + run: runuser -u builder -- tox -e "reset,${{ matrix.tox-env }}${{ matrix.stats-env }}" diff --git a/checkdeps.py b/checkdeps.py index 0a28a6d256..f0dde14b1e 100755 --- a/checkdeps.py +++ b/checkdeps.py @@ -57,6 +57,7 @@ def detectPrereqs(missing=True): + """Detect which required modules are present or absent""" available = [] for module in PACKAGES: try: @@ -70,6 +71,7 @@ def detectPrereqs(missing=True): def prereqToPackages(): + """Map python modules to package names""" if not detectPrereqs(): return print("%s %s" % ( @@ -78,6 +80,7 @@ def prereqToPackages(): def compilerToPackages(): + """Map compiler to package name""" if not detectOS() in COMPILING: return print("%s %s" % ( @@ -85,6 +88,7 @@ def compilerToPackages(): def testCompiler(): + """Check if compiler can build C PoW library""" if not HAVE_SETUPTOOLS: # silent, we can't test without setuptools return True @@ -141,9 +145,9 @@ def testCompiler(): if OPSYS is None: break if rhs and any([ - EXTRAS_REQUIRE_DEPS[x][OPSYS] - for x in rhs - if x in EXTRAS_REQUIRE_DEPS + EXTRAS_REQUIRE_DEPS[x][OPSYS] + for x in rhs + if x in EXTRAS_REQUIRE_DEPS ]): try: import_module(lhs) diff --git a/dev/powinterrupttest.py b/dev/powinterrupttest.py index bfb55d7867..fb7b1c05ff 100644 --- a/dev/powinterrupttest.py +++ b/dev/powinterrupttest.py @@ -1,3 +1,6 @@ +""" +Code for discovering how C PoW can be interrupted +""" import ctypes import hashlib from multiprocessing import current_process @@ -9,9 +12,12 @@ shutdown = 0 -def signal_handler(signal, frame): - global shutdown - print("Got signal %i in %s/%s" % (signal, current_process().name, current_thread().name)) +# pylint: disable=unused-argument +def signal_handler(signum, frame): + """Signal handler""" + global shutdown # pylint: disable=global-statement + print("Got signal %i in %s/%s" % (signum, current_process().name, + current_thread().name)) if current_process().name != "MainProcess": raise StopIteration("Interrupted") if current_thread().name != "PyBitmessage": @@ -31,7 +37,12 @@ def _doCPoW(target, initialHash): nonce = bmpow(out_h, out_m) if shutdown: break - trialValue, = unpack('>Q', hashlib.sha512(hashlib.sha512(pack('>Q', nonce) + initialHash).digest()).digest()[0:8]) + trialValue, = unpack('>Q', + hashlib.sha512( + hashlib.sha512( + pack('>Q', nonce) + initialHash). + digest()). + digest()[0:8]) if shutdown != 0: raise StopIteration("Interrupted") print("C PoW done") diff --git a/dev/ssltest.py b/dev/ssltest.py index 7268b65fc2..b485aa0799 100644 --- a/dev/ssltest.py +++ b/dev/ssltest.py @@ -1,3 +1,6 @@ +""" +Development code for SSL compatibility investigations +""" import os import select import socket @@ -9,8 +12,12 @@ PORT = 8912 +# pylint: disable=no-member def sslProtocolVersion(): - # sslProtocolVersion + """ + Find a protocol version value with compatibility across + different python versions + """ if sys.version_info >= (2, 7, 13): # this means TLSv1 or higher # in the future change to @@ -26,18 +33,30 @@ def sslProtocolVersion(): def sslProtocolCiphers(): + """ + Find protocol cipher that is compatible for PyBitmessage across + different python and OpenSSL versions + """ if ssl.OPENSSL_VERSION_NUMBER >= 0x10100000: return "AECDH-AES256-SHA@SECLEVEL=0" else: return "AECDH-AES256-SHA" +# pylint: disable=redefined-outer-name def connect(): + """ + Connect a socket + """ sock = socket.create_connection((HOST, PORT)) return sock +# pylint: disable=redefined-outer-name def listen(): + """ + Listen on a socket + """ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind((HOST, PORT)) @@ -45,7 +64,11 @@ def listen(): return sock +# pylint: disable=redefined-outer-name def sslHandshake(sock, server=False): + """ + Perform SSL handshake + """ if sys.version_info >= (2, 7, 9): context = ssl.SSLContext(sslProtocolVersion()) context.set_ciphers(sslProtocolCiphers()) @@ -54,12 +77,19 @@ def sslHandshake(sock, server=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) + sslSock = context.wrap_socket(sock, server_side=server, + do_handshake_on_connect=False) else: - sslSock = ssl.wrap_socket(sock, keyfile=os.path.join('src', 'sslkeys', 'key.pem'), - certfile=os.path.join('src', 'sslkeys', 'cert.pem'), - server_side=server, ssl_version=sslProtocolVersion(), - do_handshake_on_connect=False, ciphers='AECDH-AES256-SHA') + sslSock = ssl.wrap_socket(sock, keyfile=os.path.join('src', + 'sslkeys', + 'key.pem'), + certfile=os.path.join('src', + 'sslkeys', + 'cert.pem'), + server_side=server, + ssl_version=sslProtocolVersion(), + do_handshake_on_connect=False, + ciphers='AECDH-AES256-SHA') while True: try: diff --git a/docs/conf.py b/docs/conf.py index b0cfef7b9d..eeff353b98 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -6,14 +6,16 @@ http://www.sphinx-doc.org/en/master/config """ +# flake8: noqa:E402 + import os import sys sys.path.insert(0, os.path.abspath('../src')) -from importlib import import_module +from importlib import import_module # pylint: disable=wrong-import-position -import version # noqa:E402 +import version # noqa:E402 pylint: disable=wrong-import-position # -- Project information ----------------------------------------------------- diff --git a/packages/android/buildozer.spec b/packages/android/buildozer.spec index edad9241e7..9649459f3d 100644 --- a/packages/android/buildozer.spec +++ b/packages/android/buildozer.spec @@ -107,13 +107,13 @@ android.ndk_api = 21 android.private_storage = True # (str) Android NDK directory (if empty, it will be automatically downloaded.) -android.ndk_path = /opt/android/android-ndk +#android.ndk_path = # (str) Android SDK directory (if empty, it will be automatically downloaded.) -android.sdk_path = /opt/android/android-sdk +#android.sdk_path = # (str) ANT directory (if empty, it will be automatically downloaded.) -android.ant_path = /opt/android/apache-ant +#android.ant_path = # (bool) If True, then skip trying to update the Android sdk # This can be useful to avoid excess Internet downloads or save time @@ -124,7 +124,7 @@ android.ant_path = /opt/android/apache-ant # 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 +android.accept_sdk_license = True # (str) Android entry point, default is ok for Kivy-based app #android.entrypoint = org.renpy.android.PythonActivity diff --git a/packages/collectd/pybitmessagestatus.py b/packages/collectd/pybitmessagestatus.py index f82656f7c8..0edb9d093e 100644 --- a/packages/collectd/pybitmessagestatus.py +++ b/packages/collectd/pybitmessagestatus.py @@ -1,22 +1,33 @@ #!/usr/bin/env python2.7 +""" +PyBitmessage status module for collectd +Provides values for active connections and processed objects +""" import json -import collectd +import collectd # pylint: disable=import-error from six.moves import xmlrpc_client as xmlrpclib pybmurl = "" -api = "" +api = None def init_callback(): - global api + """ + Initialise callback + Creates an API object + """ + global api # pylint: disable=global-statement api = xmlrpclib.ServerProxy(pybmurl) collectd.info('pybitmessagestatus.py init done') def config_callback(ObjConfiguration): - global pybmurl + """ + Load module config + """ + global pybmurl # pylint: disable=global-statement apiUsername = "" apiPassword = "" apiInterface = "127.0.0.1" @@ -31,11 +42,17 @@ def config_callback(ObjConfiguration): apiInterface = node.values[0] elif key.lower() == "apiport" and node.values: apiPort = node.values[0] - pybmurl = "http://{}:{}@{}:{}/".format(apiUsername, apiPassword, apiInterface, str(int(apiPort))) + pybmurl = "http://{}:{}@{}:{}/".format(apiUsername, + apiPassword, + apiInterface, + str(int(apiPort))) collectd.info('pybitmessagestatus.py config done') def read_callback(): + """ + Read data from API + """ try: clientStatus = json.loads(api.clientStatus()) except (ValueError, TypeError): @@ -61,6 +78,11 @@ def read_callback(): metric.dispatch() +def main(): + """Dummy function""" + pass + + if __name__ == "__main__": main() else: diff --git a/packages/pyinstaller/hooks/pyinstaller_rthook_plugins.py b/packages/pyinstaller/hooks/pyinstaller_rthook_plugins.py index e796c1f54f..470998b4c8 100644 --- a/packages/pyinstaller/hooks/pyinstaller_rthook_plugins.py +++ b/packages/pyinstaller/hooks/pyinstaller_rthook_plugins.py @@ -1,5 +1,6 @@ """Runtime PyInstaller hook to load plugins""" +# pylint: disable=unused-import import os import sys diff --git a/setup.cfg b/setup.cfg index c735e0a876..64a5909dce 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,6 +5,7 @@ [pycodestyle] max-line-length = 119 +ignore = E402,E722,W503 [flake8] max-line-length = 119 @@ -14,14 +15,35 @@ ignore = E722,F841,W503 # 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 +# -- pylint (modern, >= 2.14) -- pylint.*-prefixed sections for setup.cfg + +[pylint.main] +init-hook = import sys;sys.path.append('src') +ignore = bitmessagekivy + +[pylint.messages_control] +disable = + invalid-name,bare-except,broad-except,relative-import, + superfluous-parens,bad-option-value # invalid-name: needs fixing during a large, project-wide refactor # bare-except,broad-except: Need fixing once thorough testing is easier +# bad-option-value is for backward compatibility between python 2 and 3 + +[pylint.design] max-args = 8 max-attributes = 8 +# -- pylint (legacy, < 2.0 / python 2.7) -- old .pylintrc-style section names + [MASTER] init-hook = import sys;sys.path.append('src') +ignore = bitmessagekivy + +[MESSAGES CONTROL] +disable = + invalid-name,bare-except,broad-except,relative-import, + superfluous-parens,bad-option-value + +[DESIGN] +max-args = 8 +max-attributes = 8 diff --git a/setup.py b/setup.py index e1dc1ba656..6e36492c9a 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,5 @@ #!/usr/bin/env python2.7 +"""Setuptools script for building and installing PyBitmessage""" import os import platform @@ -111,18 +112,18 @@ def run(self): data_files = [ ('share/applications/', - ['desktop/pybitmessage.desktop']), + ['desktop/pybitmessage.desktop']), ('share/icons/hicolor/scalable/apps/', - ['desktop/icons/scalable/pybitmessage.svg']), + ['desktop/icons/scalable/pybitmessage.svg']), ('share/icons/hicolor/24x24/apps/', - ['desktop/icons/24x24/pybitmessage.png']) + ['desktop/icons/24x24/pybitmessage.png']) ] try: if platform.dist()[0] in ('Debian', 'Ubuntu'): data_files += [ ("etc/apparmor.d/", - ['packages/apparmor/pybitmessage']) + ['packages/apparmor/pybitmessage']) ] except AttributeError: pass # FIXME: use distro for more recent python diff --git a/src/api.py b/src/api.py index 7826e62954..95debcab3d 100644 --- a/src/api.py +++ b/src/api.py @@ -57,6 +57,8 @@ For further examples please reference `.tests.test_api`. """ +# pylint: disable=too-many-lines + import base64 import errno import hashlib @@ -70,7 +72,7 @@ import six from six.moves import configparser, http_client, xmlrpc_server -from six.moves.reprlib import repr +from six.moves.reprlib import repr # pylint: disable=redefined-builtin,import-error import helper_inbox import helper_sent @@ -112,25 +114,25 @@ class ErrorCodes(type): 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).', + ' (which means auto-select).', 3: 'The stream number must be 1 (or 0 which means' - ' auto-select). Others aren\'t supported.', + ' 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.', + ' 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.', + ' Others aren\'t supported. Check the address.', 12: 'The stream number must be 1. Others aren\'t supported.' - ' Check the address.', + ' 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.', @@ -138,14 +140,14 @@ class ErrorCodes(type): 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).', + ' 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.', + ' Use deleteAddress API call instead.', 26: 'Malformed varint in address: ', 27: 'Message is too long.', 28: 'Invalid parameter' @@ -153,7 +155,7 @@ class ErrorCodes(type): def __new__(mcs, name, bases, namespace): result = super(ErrorCodes, mcs).__new__(mcs, name, bases, namespace) - for code in six.iteritems(mcs._CODES): + for code in six.iteritems(mcs._CODES): # pylint: disable=no-member # beware: the formatting is adjusted for list-table result.__doc__ += """ * - %04i - %s @@ -466,6 +468,7 @@ def APIAuthenticateClient(self): class BMRPCDispatcher(object): """This class is used to dispatch API commands""" + # pylint: disable=inconsistent-return-statements @staticmethod def _decode(text, decode_type): try: @@ -722,8 +725,8 @@ def HandleDeleteWhitelistEntry(self, address): @command('createRandomAddress') def HandleCreateRandomAddress( - self, label, eighteenByteRipe=False, totalDifficulty=0, - smallMessageDifficulty=0 + self, label, eighteenByteRipe=False, totalDifficulty=0, + smallMessageDifficulty=0 ): """ Create one address using the random number generator. @@ -763,9 +766,9 @@ def HandleCreateRandomAddress( @command('createDeterministicAddresses') def HandleCreateDeterministicAddresses( - self, passphrase, numberOfAddresses=1, addressVersionNumber=0, - streamNumber=0, eighteenByteRipe=False, totalDifficulty=0, - smallMessageDifficulty=0 + self, passphrase, numberOfAddresses=1, addressVersionNumber=0, + streamNumber=0, eighteenByteRipe=False, totalDifficulty=0, + smallMessageDifficulty=0 ): """ Create many addresses deterministically using the passphrase. @@ -1190,8 +1193,8 @@ def HandleTrashSentMessage(self, msgid): @command('sendMessage') def HandleSendMessage( - self, toAddress, fromAddress, subject, message, - encodingType=2, TTL=4 * 24 * 60 * 60 + self, toAddress, fromAddress, subject, message, + encodingType=2, TTL=4 * 24 * 60 * 60 ): """ Send the message and return ackdata (hex encoded string). @@ -1242,7 +1245,7 @@ def HandleSendMessage( @command('sendBroadcast') def HandleSendBroadcast( - self, fromAddress, subject, message, encodingType=2, + self, fromAddress, subject, message, encodingType=2, TTL=4 * 24 * 60 * 60): """Send the broadcast message. Similiar to *sendMessage*.""" @@ -1361,9 +1364,9 @@ def ListSubscriptions(self): @command('disseminatePreEncryptedMsg', 'disseminatePreparedObject') def HandleDisseminatePreparedObject( - self, encryptedPayload, - nonceTrialsPerByte=networkDefaultProofOfWorkNonceTrialsPerByte, - payloadLengthExtraBytes=networkDefaultPayloadLengthExtraBytes + self, encryptedPayload, + nonceTrialsPerByte=networkDefaultProofOfWorkNonceTrialsPerByte, + payloadLengthExtraBytes=networkDefaultPayloadLengthExtraBytes ): """ Handle a request to disseminate an encrypted message. diff --git a/src/bitmessagecli.py b/src/bitmessagecli.py index d46c7debfd..536c5821b6 100644 --- a/src/bitmessagecli.py +++ b/src/bitmessagecli.py @@ -2,7 +2,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 +# pylint: disable=no-member,superfluous-parens """ 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. @@ -16,15 +16,14 @@ import datetime import imghdr import json -import ntpath import os import socket import sys import time - from six.moves import input as raw_input from six.moves import xmlrpc_client as xmlrpclib +import ntpath from bmconfigparser import config diff --git a/src/bitmessagecurses/__init__.py b/src/bitmessagecurses/__init__.py index 64fd735b45..edaf6b905d 100644 --- a/src/bitmessagecurses/__init__.py +++ b/src/bitmessagecurses/__init__.py @@ -10,6 +10,7 @@ # * python2-pythondialog # * dialog +# pylint: disable=global-statement,too-many-lines,no-member import ConfigParser import curses import os @@ -18,7 +19,7 @@ from textwrap import fill from threading import Timer -from dialog import Dialog +from dialog import Dialog # pylint: disable=import-error import helper_sent import l10n import network.stats @@ -31,8 +32,6 @@ from bmconfigparser import config from helper_sql import sqlExecute, sqlQuery -# pylint: disable=global-statement - quit_ = False menutab = 1 @@ -252,14 +251,14 @@ def drawtab(stdscr): # Connection data connected_hosts = network.stats.connectedHostsList() stdscr.addstr( - 4, 5, "Total Connections: " + - str(len(connected_hosts)).ljust(2) + 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: + for _, stream in connected_hosts: if stream >= len(streamcount): streamcount.append(1) else: @@ -350,13 +349,13 @@ def handlech(c, stdscr): if t == "1": # View set_background_title( d, - "\"" + - inbox[inboxcur][5] + - "\" from \"" + - inbox[inboxcur][3] + - "\" to \"" + - inbox[inboxcur][1] + - "\"") + "\"" + + 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 != []: @@ -453,13 +452,13 @@ def handlech(c, stdscr): if t == "1": # View set_background_title( d, - "\"" + - sentbox[sentcur][4] + - "\" from \"" + - sentbox[sentcur][3] + - "\" to \"" + - sentbox[sentcur][1] + - "\"") + "\"" + + sentbox[sentcur][4] + + "\" from \"" + + sentbox[sentcur][3] + + "\" to \"" + + sentbox[sentcur][1] + + "\"") data = "" ret = sqlQuery( "SELECT message FROM sent WHERE subject=? AND ackdata=?", @@ -949,11 +948,11 @@ def sendMessage(sender="", recv="", broadcast=None, subject="", body="", reply=F 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 + - ".")) + "Could not understand version number " + + version + + " of address " + + addr + + ".")) continue if stream > 1 or stream == 0: set_background_title(d, "Recipient address error") @@ -984,7 +983,7 @@ def loadInbox(): """Load the list of messages""" sys.stdout = sys.__stdout__ print("Loading inbox messages...") - sys.stdout = printlog + sys.stdout = printlog # pylint: disable=redefined-variable-type where = "toaddress || fromaddress || subject || message" what = "%%" @@ -1037,7 +1036,7 @@ def loadSent(): """Load the messages that sent""" sys.stdout = sys.__stdout__ print("Loading sent messages...") - sys.stdout = printlog + sys.stdout = printlog # pylint: disable=redefined-variable-type where = "toaddress || fromaddress || subject || message" what = "%%" @@ -1123,7 +1122,7 @@ def loadAddrBook(): """Load address book""" sys.stdout = sys.__stdout__ print("Loading address book...") - sys.stdout = printlog + sys.stdout = printlog # pylint: disable=redefined-variable-type ret = sqlQuery("SELECT label, address FROM addressbook") for row in ret: @@ -1230,7 +1229,7 @@ def doShutdown(): """Shutting the app down""" sys.stdout = sys.__stdout__ print("Shutting down...") - sys.stdout = printlog + sys.stdout = printlog # pylint: disable=redefined-variable-type shutdown.doCleanShutdown() sys.stdout = sys.__stdout__ sys.stderr = sys.__stderr__ diff --git a/src/bitmessagekivy/base_navigation.py b/src/bitmessagekivy/base_navigation.py index 5f6b1aa527..25ec3d13dc 100644 --- a/src/bitmessagekivy/base_navigation.py +++ b/src/bitmessagekivy/base_navigation.py @@ -1,4 +1,5 @@ -# pylint: disable=unused-argument, no-name-in-module, too-few-public-methods +# pylint: disable=unused-argument,no-name-in-module,too-few-public-methods +# pylint: disable=import-error """ Base class for Navigation Drawer """ diff --git a/src/bitmessagekivy/baseclass/addressbook.py b/src/bitmessagekivy/baseclass/addressbook.py index b25f061369..d4267e3b33 100644 --- a/src/bitmessagekivy/baseclass/addressbook.py +++ b/src/bitmessagekivy/baseclass/addressbook.py @@ -83,6 +83,7 @@ def set_mdList(self, start_index, end_index): listItem.secondary_text = item[1] listItem.theme_text_color = "Custom" listItem.text_color = ThemeClsColor + # pylint: disable=syntax-error image = os.path.join( self.kivy_state.image_dir, "text_images", f"{avatar_image_first_letter(item[0].strip())}.png" # noqa: E999 diff --git a/src/bitmessagekivy/baseclass/addressbook_widgets.py b/src/bitmessagekivy/baseclass/addressbook_widgets.py index 7c2872ffa6..0a6b10de63 100644 --- a/src/bitmessagekivy/baseclass/addressbook_widgets.py +++ b/src/bitmessagekivy/baseclass/addressbook_widgets.py @@ -1,4 +1,5 @@ -# pylint: disable=no-member, too-many-arguments, too-few-public-methods, no-init +# pylint: disable=no-member,too-many-arguments,too-few-public-methods +# pylint: disable=no-init,import-error """Addressbook widgets are here.""" diff --git a/src/bitmessagekivy/baseclass/common.py b/src/bitmessagekivy/baseclass/common.py index cd0d2d7a9f..8dd722ff17 100644 --- a/src/bitmessagekivy/baseclass/common.py +++ b/src/bitmessagekivy/baseclass/common.py @@ -1,5 +1,6 @@ -# pylint: disable=no-name-in-module, attribute-defined-outside-init, import-error, unused-argument -# pylint: disable=no-init, too-few-public-methods, useless-object-inheritance +# pylint: disable=no-name-in-module,attribute-defined-outside-init +# pylint: disable=import-error,unused-argument +# pylint: disable=no-init,too-few-public-methods,useless-object-inheritance """ All Common widgets of kivy are managed here. diff --git a/src/bitmessagemain.py b/src/bitmessagemain.py index 0402e841fa..7b74bca26b 100755 --- a/src/bitmessagemain.py +++ b/src/bitmessagemain.py @@ -11,6 +11,7 @@ # yet contain logic to expand into further streams. # flake8: noqa:402 +# pylint: disable=superfluous-parens import os import sys @@ -211,7 +212,7 @@ def start(self): # API is also objproc dependent if config.safeGetBoolean('bitmessagesettings', 'apienabled'): - import api # pylint: disable=relative-import + import api singleAPIThread = api.singleAPI() # close the main program even if there are threads left singleAPIThread.daemon = True @@ -255,14 +256,13 @@ def start(self): while state.shutdown == 0: time.sleep(1) if ( - state.testmode - and time.time() - state.last_api_response >= 30 + 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: diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 2b0ff89b41..b9eb7e0ee7 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -1,7 +1,7 @@ """ PyQt based UI for bitmessage, the main module """ - +# pylint: disable=import-error,too-many-lines,no-member import hashlib import locale import os @@ -56,8 +56,7 @@ import bitmessage_icons_rc # noqa:F401 pylint: disable=unused-import import helper_sent -from six.moves import iteritems, itervalues, range as xrange -from six import text_type +from six import iteritems, itervalues, text_type try: from plugins.plugin import get_plugin, get_plugins @@ -239,20 +238,20 @@ def init_inbox_popup_menu(self, connectSignal=True): QtCore.Qt.CustomContextMenu) if connectSignal: self.connect(self.ui.tableWidgetInbox, QtCore.SIGNAL( - 'customContextMenuRequested(const QPoint&)'), - self.on_context_menuInbox) + '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) + '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) + 'customContextMenuRequested(const QPoint&)'), + self.on_context_menuInbox) def init_identities_popup_menu(self, connectSignal=True): # Popup menu for the Your Identities tab @@ -291,8 +290,8 @@ def init_identities_popup_menu(self, connectSignal=True): QtCore.Qt.CustomContextMenu) if connectSignal: self.connect(self.ui.treeWidgetYourIdentities, QtCore.SIGNAL( - 'customContextMenuRequested(const QPoint&)'), - self.on_context_menuYourIdentities) + 'customContextMenuRequested(const QPoint&)'), + self.on_context_menuYourIdentities) # load all gui.menu plugins with prefix 'address' self.menu_plugins = {'address': []} @@ -410,6 +409,7 @@ def init_subscriptions_popup_menu(self, connectSignal=True): 'customContextMenuRequested(const QPoint&)'), self.on_context_menuSubscriptions) + # pylint: disable=unused-argument def init_sent_popup_menu(self, connectSignal=True): # Actions self.actionTrashSentMessage = self.ui.sentContextMenuToolbar.addAction( @@ -449,7 +449,6 @@ def rerenderTabTreeSubscriptions(self): if treeWidget.isSortingEnabled(): treeWidget.setSortingEnabled(False) - widgets = {} i = 0 while i < treeWidget.topLevelItemCount(): widget = treeWidget.topLevelItem(i) @@ -538,8 +537,6 @@ def rerenderTabTree(self, tab): toAddress, 'enabled') isChan = config.safeGetBoolean( toAddress, 'chan') - isMaillinglist = config.safeGetBoolean( - toAddress, 'mailinglist') if treeWidget == self.ui.treeWidgetYourIdentities: if isChan: @@ -577,7 +574,6 @@ def rerenderTabTree(self, tab): if treeWidget.isSortingEnabled(): treeWidget.setSortingEnabled(False) - widgets = {} i = 0 while i < treeWidget.topLevelItemCount(): widget = treeWidget.topLevelItem(i) @@ -652,8 +648,7 @@ def __init__(self, parent=None): # 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) + addressVersionNumber = decodeAddress(addressInKeysFile)[1] if addressVersionNumber == 1: displayMsg = _translate( "MainWindow", @@ -969,18 +964,6 @@ def appIndicatorSwitchQuietMode(self): str(not self.actionQuiet.isChecked()) ) - # application indicator show or hide - """# application indicator show or hide - def appIndicatorShowBitmessage(self): - #if self.actionShow == None: - # return - print self.actionShow.isChecked() - if not self.actionShow.isChecked(): - self.hide() - #self.setWindowState(self.windowState() & QtCore.Qt.WindowMinimized) - else: - self.appIndicatorShowOrHideWindow()""" - # Show the program window and select inbox tab def appIndicatorInbox(self, item=None): self.appIndicatorShow() @@ -2399,7 +2382,7 @@ def rerenderComboBoxSendFrom(self): i, AccountColor(address).accountColor(), QtCore.Qt.ForegroundRole) self.ui.comboBoxSendFrom.insertItem(0, '', '') - if(self.ui.comboBoxSendFrom.count() == 2): + if self.ui.comboBoxSendFrom.count() == 2: self.ui.comboBoxSendFrom.setCurrentIndex(1) else: self.ui.comboBoxSendFrom.setCurrentIndex(0) @@ -2422,7 +2405,7 @@ def rerenderComboBoxSendFromBroadcast(self): i, AccountColor(address).accountColor(), QtCore.Qt.ForegroundRole) self.ui.comboBoxSendFromBroadcast.insertItem(0, '', '') - if(self.ui.comboBoxSendFromBroadcast.count() == 2): + if self.ui.comboBoxSendFromBroadcast.count() == 2: self.ui.comboBoxSendFromBroadcast.setCurrentIndex(1) else: self.ui.comboBoxSendFromBroadcast.setCurrentIndex(0) @@ -3787,7 +3770,7 @@ def on_action_SetAvatar(self, thisTableWidget): def setAvatar(self, addressAtCurrentRow): if not os.path.exists(state.appdata + 'avatars/'): os.makedirs(state.appdata + 'avatars/') - hash = hashlib.md5(addBMIfNotPresent(addressAtCurrentRow)).hexdigest() + hash_ = hashlib.md5(addBMIfNotPresent(addressAtCurrentRow)).hexdigest() extensions = [ 'PNG', 'GIF', 'JPG', 'JPEG', 'SVG', 'BMP', 'MNG', 'PBM', 'PGM', 'PPM', 'TIFF', 'XBM', 'XPM', 'TGA'] @@ -3813,8 +3796,8 @@ def setAvatar(self, addressAtCurrentRow): 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() + 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): @@ -3826,7 +3809,8 @@ def setAvatar(self, addressAtCurrentRow): filter=';;'.join(filters) ) # determine the correct filename (note that avatars don't use the suffix) - destination = state.appdata + 'avatars/' + hash + '.' + sourcefile.split('.')[-1] + destination = state.appdata + 'avatars/' + hash_ \ + + '.' + sourcefile.split('.')[-1] exists = QtCore.QFile.exists(destination) if sourcefile == '': # ask for removal of avatar diff --git a/src/bitmessageqt/account.py b/src/bitmessageqt/account.py index 8c82c6f64e..dcfb89236b 100644 --- a/src/bitmessageqt/account.py +++ b/src/bitmessageqt/account.py @@ -6,7 +6,7 @@ Account related functions. """ - +# pylint: disable=import-error from __future__ import absolute_import import inspect diff --git a/src/bitmessageqt/address_dialogs.py b/src/bitmessageqt/address_dialogs.py index bf571041a6..9181acd14a 100644 --- a/src/bitmessageqt/address_dialogs.py +++ b/src/bitmessageqt/address_dialogs.py @@ -1,7 +1,8 @@ """ Dialogs that work with BM address. """ -# pylint: disable=attribute-defined-outside-init,too-few-public-methods,relative-import +# pylint: disable=attribute-defined-outside-init,too-few-public-methods +# pylint: disable=import-error import hashlib diff --git a/src/bitmessageqt/addressvalidator.py b/src/bitmessageqt/addressvalidator.py index dc61b41cde..21bfff4f97 100644 --- a/src/bitmessageqt/addressvalidator.py +++ b/src/bitmessageqt/addressvalidator.py @@ -2,6 +2,7 @@ Address validator module. """ # pylint: disable=too-many-branches,too-many-arguments +# pylint: disable=import-error from Queue import Empty @@ -103,7 +104,7 @@ def returnValid(self): return QtGui.QValidator.Acceptable return QtGui.QValidator.Intermediate - def validate(self, s, pos): + def validate(self, s, pos): # pylint: disable=unused-argument """Top level validator method""" if self.addressObject is None: address = None diff --git a/src/bitmessageqt/bitmessage_icons_rc.py b/src/bitmessageqt/bitmessage_icons_rc.py index bb0a02c02c..e8d04eb240 100644 --- a/src/bitmessageqt/bitmessage_icons_rc.py +++ b/src/bitmessageqt/bitmessage_icons_rc.py @@ -7,7 +7,7 @@ # # WARNING! All changes made in this file will be lost! -from PyQt4 import QtCore +from PyQt4 import QtCore # pylint: disable=import-error qt_resource_data = "\ \x00\x00\x03\x66\ @@ -1666,10 +1666,15 @@ \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) + 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) + 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 index bee8fd571a..e16c2642ad 100644 --- a/src/bitmessageqt/bitmessageui.py +++ b/src/bitmessageqt/bitmessageui.py @@ -7,7 +7,8 @@ # # WARNING! All changes made in this file will be lost! -from PyQt4 import QtCore, QtGui +# pylint: disable=attribute-defined-outside-init +from PyQt4 import QtCore, QtGui # pylint: disable=import-error from bmconfigparser import config from foldertree import AddressBookCompleter from messageview import MessageView @@ -25,17 +26,34 @@ def _fromUtf8(s): try: _encoding = QtGui.QApplication.UnicodeUTF8 - def _translate(context, text, disambig, encoding=QtCore.QCoreApplication.CodecForTr, n=None): + # pylint: disable=unused-argument + def _translate(context, text, disambig, + encoding=QtCore.QCoreApplication.CodecForTr, n=None): if n is None: - return QtGui.QApplication.translate(context, text, disambig, _encoding) + return QtGui.QApplication.translate(context, + text, + disambig, + _encoding) else: - return QtGui.QApplication.translate(context, text, disambig, _encoding, n) + return QtGui.QApplication.translate(context, + text, + disambig, + _encoding, + n) except AttributeError: - def _translate(context, text, disambig, encoding=QtCore.QCoreApplication.CodecForTr, n=None): + # pylint: disable=unused-argument + def _translate(context, text, disambig, + encoding=QtCore.QCoreApplication.CodecForTr, n=None): if n is None: - return QtGui.QApplication.translate(context, text, disambig) + return QtGui.QApplication.translate(context, + text, + disambig) else: - return QtGui.QApplication.translate(context, text, disambig, QtCore.QCoreApplication.CodecForTr, n) + return QtGui.QApplication.translate(context, + text, + disambig, + QtCore.QCoreApplication.CodecForTr, + n) class Ui_MainWindow(object): @@ -44,7 +62,8 @@ def setupUi(self, 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 + QtGui.QPixmap(_fromUtf8(":/newPrefix/images/can-icon-24px.png")), + QtGui.QIcon.Normal, QtGui.QIcon.Off ) MainWindow.setWindowIcon(icon) MainWindow.setTabShape(QtGui.QTabWidget.Rounded) @@ -53,7 +72,8 @@ def setupUi(self, MainWindow): 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 = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, + QtGui.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.tabWidget.sizePolicy().hasHeightForWidth()) diff --git a/src/bitmessageqt/blacklist.py b/src/bitmessageqt/blacklist.py index 093f23d866..d1f339a88e 100644 --- a/src/bitmessageqt/blacklist.py +++ b/src/bitmessageqt/blacklist.py @@ -1,4 +1,7 @@ -from PyQt4 import QtCore, QtGui +""" +Blacklist / whitelist administration UI +""" +from PyQt4 import QtCore, QtGui # pylint: disable=import-error import widgets from addresses import addBMIfNotPresent @@ -22,7 +25,7 @@ def __init__(self, parent=None): QtCore.QObject.connect(self.radioButtonWhitelist, QtCore.SIGNAL( "clicked()"), self.click_radioButtonWhitelist) QtCore.QObject.connect(self.pushButtonAddBlacklist, QtCore.SIGNAL( - "clicked()"), self.click_pushButtonAddBlacklist) + "clicked()"), self.click_pushButtonAddBlacklist) self.init_blacklist_popup_menu() @@ -31,7 +34,7 @@ def __init__(self, parent=None): "itemChanged(QTableWidgetItem *)"), self.tableWidgetBlacklistItemChanged) # Set the icon sizes for the identicons - identicon_size = 3*7 + identicon_size = 3 * 7 self.tableWidgetBlacklist.setIconSize(QtCore.QSize(identicon_size, identicon_size)) self.UISignalThread = UISignaler.get() @@ -55,12 +58,12 @@ def click_radioButtonWhitelist(self): self.rerenderBlackWhiteList() def click_pushButtonAddBlacklist(self): - self.NewBlacklistDialogInstance = AddAddressDialog(self) - if self.NewBlacklistDialogInstance.exec_(): - if self.NewBlacklistDialogInstance.labelAddressCheck.text() == \ + NewBlacklistDialogInstance = AddAddressDialog(self) + if NewBlacklistDialogInstance.exec_(): + if NewBlacklistDialogInstance.labelAddressCheck.text() == \ _translate("MainWindow", "Address is valid."): address = addBMIfNotPresent(str( - self.NewBlacklistDialogInstance.lineEditAddress.text())) + 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. @@ -69,12 +72,12 @@ def click_pushButtonAddBlacklist(self): sql = '''select * from blacklist where address=?''' else: sql = '''select * from whitelist where address=?''' - queryreturn = sqlQuery(sql,*t) + 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')) + NewBlacklistDialogInstance.lineEditLabel.text().toUtf8(), 'utf-8')) newItem.setIcon(avatarize(address)) self.tableWidgetBlacklist.setItem(0, 0, newItem) newItem = QtGui.QTableWidgetItem(address) @@ -82,7 +85,7 @@ def click_pushButtonAddBlacklist(self): 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) + t = (str(NewBlacklistDialogInstance.lineEditLabel.text().toUtf8()), address, True) if config.get('bitmessagesettings', 'blackwhitelist') == 'black': sql = '''INSERT INTO blacklist VALUES (?,?,?)''' else: @@ -111,10 +114,10 @@ def tableWidgetBlacklistItemChanged(self, item): if isinstance(addressitem, QtGui.QTableWidgetItem): if self.radioButtonBlacklist.isChecked(): sqlExecute('''UPDATE blacklist SET label=? WHERE address=?''', - str(item.text()), str(addressitem.text())) + str(item.text()), str(addressitem.text())) else: sqlExecute('''UPDATE whitelist SET label=? WHERE address=?''', - str(item.text()), str(addressitem.text())) + str(item.text()), str(addressitem.text())) def init_blacklist_popup_menu(self, connectSignal=True): # Popup menu for the Blacklist page @@ -145,7 +148,7 @@ def init_blacklist_popup_menu(self, connectSignal=True): if connectSignal: self.connect(self.tableWidgetBlacklist, QtCore.SIGNAL( 'customContextMenuRequested(const QPoint&)'), - self.on_context_menuBlacklist) + self.on_context_menuBlacklist) self.popMenuBlacklist = QtGui.QMenu(self) # self.popMenuBlacklist.addAction( self.actionBlacklistNew ) self.popMenuBlacklist.addAction(self.actionBlacklistDelete) diff --git a/src/bitmessageqt/dialogs.py b/src/bitmessageqt/dialogs.py index dc31e26697..cdb5e06221 100644 --- a/src/bitmessageqt/dialogs.py +++ b/src/bitmessageqt/dialogs.py @@ -2,7 +2,7 @@ Custom dialog classes """ # pylint: disable=too-few-public-methods -from PyQt4 import QtGui +from PyQt4 import QtGui # pylint: disable=import-error import paths import widgets diff --git a/src/bitmessageqt/foldertree.py b/src/bitmessageqt/foldertree.py index c50b7d3d47..6984f094a7 100644 --- a/src/bitmessageqt/foldertree.py +++ b/src/bitmessageqt/foldertree.py @@ -6,7 +6,7 @@ from cgi import escape -from PyQt4 import QtCore, QtGui +from PyQt4 import QtCore, QtGui # pylint: disable=import-error from bmconfigparser import config from helper_sql import sqlExecute, sqlQuery @@ -232,8 +232,11 @@ def _setup(self, parent, pos): def _getLabel(self): if self.address is None: - return unicode(_translate( - "MainWindow", "All accounts").toUtf8(), 'utf-8', 'ignore') + # pylint: disable=no-member + return unicode(_translate("MainWindow", + "All accounts").toUtf8(), + 'utf-8', + 'ignore') else: try: return unicode( diff --git a/src/bitmessageqt/languagebox.py b/src/bitmessageqt/languagebox.py index 34f96b02a7..5e389d33c3 100644 --- a/src/bitmessageqt/languagebox.py +++ b/src/bitmessageqt/languagebox.py @@ -3,7 +3,7 @@ import glob import os -from PyQt4 import QtCore, QtGui +from PyQt4 import QtCore, QtGui # pylint: disable=import-error import paths from bmconfigparser import config diff --git a/src/bitmessageqt/messagecompose.py b/src/bitmessageqt/messagecompose.py index c51282f8aa..59c6b567f8 100644 --- a/src/bitmessageqt/messagecompose.py +++ b/src/bitmessageqt/messagecompose.py @@ -1,9 +1,8 @@ """ Message editor with a wheel zoom functionality """ -# pylint: disable=bad-continuation -from PyQt4 import QtCore, QtGui +from PyQt4 import QtCore, QtGui # pylint disable=import-error class MessageCompose(QtGui.QTextEdit): @@ -15,18 +14,22 @@ def __init__(self, parent=0): def wheelEvent(self, event): """Mouse wheel scroll event handler""" - if ( - QtGui.QApplication.queryKeyboardModifiers() & QtCore.Qt.ControlModifier - ) == QtCore.Qt.ControlModifier and event.orientation() == QtCore.Qt.Vertical: + if (QtGui.QApplication.queryKeyboardModifiers() + & QtCore.Qt.ControlModifier) == \ + QtCore.Qt.ControlModifier \ + and event.orientation() == QtCore.Qt.Vertical: if event.delta() > 0: self.zoomIn(1) else: self.zoomOut(1) - zoom = self.currentFont().pointSize() * 100 / self.defaultFontPointSize - QtGui.QApplication.activeWindow().statusBar().showMessage( - QtGui.QApplication.translate("MainWindow", "Zoom level %1%").arg( - str(zoom) - ) + 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 diff --git a/src/bitmessageqt/messageview.py b/src/bitmessageqt/messageview.py index 13ea16f97b..ff9018fb62 100644 --- a/src/bitmessageqt/messageview.py +++ b/src/bitmessageqt/messageview.py @@ -5,7 +5,7 @@ """ -from PyQt4 import QtCore, QtGui +from PyQt4 import QtCore, QtGui # pylint: disable=import-error from safehtmlparser import SafeHTMLParser from tr import _translate @@ -52,11 +52,17 @@ def wheelEvent(self, event): # 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))) + 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""" @@ -114,8 +120,10 @@ def lazyRender(self): self.rendering = True position = self.verticalScrollBar().value() cursor = QtGui.QTextCursor(self.document()) - while self.outpos < len(self.out) and self.verticalScrollBar().value( - ) >= self.document().size().height() - 2 * self.size().height(): + while self.outpos < len(self.out) \ + and self.verticalScrollBar().value() \ + >= self.document().size().height() \ + - 2 * self.size().height(): startpos = self.outpos self.outpos += 10240 # find next end of tag @@ -123,8 +131,10 @@ def lazyRender(self): 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])) + cursor.movePosition(QtGui.QTextCursor.End, + QtGui.QTextCursor.MoveAnchor) + cursor.insertHtml( + QtCore.QString(self.out[startpos:self.outpos])) self.verticalScrollBar().setValue(position) self.rendering = False diff --git a/src/bitmessageqt/migrationwizard.py b/src/bitmessageqt/migrationwizard.py index e4262762f9..83138059fc 100644 --- a/src/bitmessageqt/migrationwizard.py +++ b/src/bitmessageqt/migrationwizard.py @@ -1,5 +1,5 @@ #!/usr/bin/env python2.7 -from PyQt4 import QtGui +from PyQt4 import QtGui # pylint: disable=import-error class MigrationWizardIntroPage(QtGui.QWizardPage): @@ -21,7 +21,7 @@ def nextId(self): class MigrationWizardAddressesPage(QtGui.QWizardPage): - def __init__(self, addresses): + def __init__(self, addresses): # pylint: disable=unused-argument super(QtGui.QWizardPage, self).__init__() self.setTitle("Addresses") diff --git a/src/bitmessageqt/networkstatus.py b/src/bitmessageqt/networkstatus.py index 79ea415cff..f0ba01bdb6 100644 --- a/src/bitmessageqt/networkstatus.py +++ b/src/bitmessageqt/networkstatus.py @@ -4,7 +4,7 @@ import time -from PyQt4 import QtCore, QtGui +from PyQt4 import QtCore, QtGui # pylint: disable=import-error import l10n import network.stats diff --git a/src/bitmessageqt/newchandialog.py b/src/bitmessageqt/newchandialog.py index c0629cd797..8175771ce6 100644 --- a/src/bitmessageqt/newchandialog.py +++ b/src/bitmessageqt/newchandialog.py @@ -3,14 +3,15 @@ ================================= """ - +# pylint: disable=import-error,ungrouped-imports,wrong-import-order from PyQt4 import QtCore, QtGui import widgets from addresses import addBMIfNotPresent from addressvalidator import AddressValidator, PassPhraseValidator -from queues import ( - addressGeneratorQueue, apiAddressGeneratorReturnQueue, UISignalQueue) +from queues import (addressGeneratorQueue, + apiAddressGeneratorReturnQueue, + UISignalQueue) from tr import _translate from utils import str_chan @@ -37,8 +38,10 @@ def __init__(self, parent=None): False)) self.timer = QtCore.QTimer() - QtCore.QObject.connect( # pylint: disable=no-member - self.timer, QtCore.SIGNAL("timeout()"), self.delayedUpdateStatus) + # pylint: disable=no-member + QtCore.QObject.connect(self.timer, + QtCore.SIGNAL("timeout()"), + self.delayedUpdateStatus) self.timer.start(500) # milliseconds self.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.show() @@ -64,20 +67,28 @@ def accept(self): self.chanPassPhrase.text().toUtf8(), True)) addressGeneratorReturnValue = apiAddressGeneratorReturnQueue.get(True) - if addressGeneratorReturnValue and addressGeneratorReturnValue[0] != 'chan name does not match address': - UISignalQueue.put(('updateStatusBar', _translate( - "newchandialog", "Successfully created / joined chan %1").arg(unicode(self.chanPassPhrase.text())))) + if 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())))) # pylint: disable=undefined-variable 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"))) + 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"))) + UISignalQueue.put(('updateStatusBar', + _translate("newchandialog", + "Chan creation / joining cancelled"))) self.done(QtGui.QDialog.Rejected) diff --git a/src/bitmessageqt/retranslateui.py b/src/bitmessageqt/retranslateui.py index 62837ed5ee..0dc81fa22c 100644 --- a/src/bitmessageqt/retranslateui.py +++ b/src/bitmessageqt/retranslateui.py @@ -1,4 +1,4 @@ -from PyQt4 import QtGui +from PyQt4 import QtGui # pylint: disable=import-error import widgets diff --git a/src/bitmessageqt/settings.py b/src/bitmessageqt/settings.py index 2d3af319fb..20249b6f39 100644 --- a/src/bitmessageqt/settings.py +++ b/src/bitmessageqt/settings.py @@ -7,7 +7,7 @@ import tempfile import six -from PyQt4 import QtCore, QtGui +from PyQt4 import QtCore, QtGui # pylint: disable=import-error import debug import defaults @@ -176,12 +176,12 @@ def adjust_from_config(self, config): if self._proxy_type: for node, info in six.iteritems( - knownnodes.knownNodes.get( + knownnodes.knownNodes.get( min(connectionpool.pool.streams), []) ): if ( - node.host.endswith('.onion') and len(node.host) > 22 - and not info.get('self') + node.host.endswith('.onion') and len(node.host) > 22 + and not info.get('self') ): break else: @@ -346,9 +346,10 @@ def choose_font(self): if valid: self.save_font_setting(font) + # pylint: disable=too-many-branches,too-many-statements + # pylint: disable=too-many-locals 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') @@ -374,7 +375,7 @@ def accept(self): window_style = str(self.comboBoxStyle.currentText()) if self.app.get_windowstyle() != window_style or self.config.safeGet( - 'bitmessagesettings', 'font' + 'bitmessagesettings', 'font' ) != self.font_setting: self.config.set('bitmessagesettings', 'windowstyle', window_style) self.config.set('bitmessagesettings', 'font', self.font_setting) @@ -455,8 +456,8 @@ def accept(self): self.config.set('bitmessagesettings', 'sockslisten', str( self.checkBoxSocksListen.isChecked())) if ( - self.checkBoxOnionOnly.isChecked() - and not self.config.safeGetBoolean( + self.checkBoxOnionOnly.isChecked() + and not self.config.safeGetBoolean( 'bitmessagesettings', 'onionservicesonly') ): self.net_restart_needed = True @@ -519,40 +520,39 @@ def accept(self): acceptableDifficultyChanged = False - if ( - float(self.lineEditMaxAcceptableTotalDifficulty.text()) >= 1 - or float(self.lineEditMaxAcceptableTotalDifficulty.text()) == 0 - ): + max_total_diff = float( + self.lineEditMaxAcceptableTotalDifficulty.text()) + if max_total_diff >= 1 or max_total_diff == 0: + nonce_trials = str(int( + max_total_diff + * defaults.networkDefaultProofOfWorkNonceTrialsPerByte)) if self.config.get( - 'bitmessagesettings', 'maxacceptablenoncetrialsperbyte' - ) != str(int( - float(self.lineEditMaxAcceptableTotalDifficulty.text()) - * defaults.networkDefaultProofOfWorkNonceTrialsPerByte)): + 'bitmessagesettings', + 'maxacceptablenoncetrialsperbyte' + ) != nonce_trials: # 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 - ): + 'bitmessagesettings', + 'maxacceptablenoncetrialsperbyte', + nonce_trials) + + max_msg_diff = float( + self.lineEditMaxAcceptableSmallMessageDifficulty.text()) + if max_msg_diff >= 1 or max_msg_diff == 0: + extra_bytes = str(int( + max_msg_diff + * defaults.networkDefaultPayloadLengthExtraBytes)) if self.config.get( - 'bitmessagesettings', 'maxacceptablepayloadlengthextrabytes' - ) != str(int( - float(self.lineEditMaxAcceptableSmallMessageDifficulty.text()) - * defaults.networkDefaultPayloadLengthExtraBytes)): + 'bitmessagesettings', + 'maxacceptablepayloadlengthextrabytes' + ) != extra_bytes: # the user changed the max acceptable small message difficulty acceptableDifficultyChanged = True self.config.set( - 'bitmessagesettings', 'maxacceptablepayloadlengthextrabytes', - str(int( - float(self.lineEditMaxAcceptableSmallMessageDifficulty.text()) - * defaults.networkDefaultPayloadLengthExtraBytes)) - ) + 'bitmessagesettings', + 'maxacceptablepayloadlengthextrabytes', + extra_bytes) if acceptableDifficultyChanged: # It might now be possible to send msgs which were previously # marked as toodifficult. Let us change them to 'msgqueued'. @@ -629,8 +629,8 @@ def accept(self): self.parent.updateStartOnLogon() if ( - state.appdata != paths.lookupExeFolder() - and self.checkBoxPortableMode.isChecked() + state.appdata != paths.lookupExeFolder() + and self.checkBoxPortableMode.isChecked() ): # If we are NOT using portable mode now but the user selected # that we should... @@ -652,8 +652,8 @@ def accept(self): pass if ( - state.appdata == paths.lookupExeFolder() - and not self.checkBoxPortableMode.isChecked() + state.appdata == paths.lookupExeFolder() + and not self.checkBoxPortableMode.isChecked() ): # If we ARE using portable mode now but the user selected # that we shouldn't... diff --git a/src/bitmessageqt/settingsmixin.py b/src/bitmessageqt/settingsmixin.py index 3d5999e203..53a10cde66 100644 --- a/src/bitmessageqt/settingsmixin.py +++ b/src/bitmessageqt/settingsmixin.py @@ -5,7 +5,7 @@ """ -from PyQt4 import QtCore, QtGui +from PyQt4 import QtCore, QtGui # pylint: disable=import-error class SettingsMixin(object): diff --git a/src/bitmessageqt/statusbar.py b/src/bitmessageqt/statusbar.py index 2add604d2c..f04f47bf2a 100644 --- a/src/bitmessageqt/statusbar.py +++ b/src/bitmessageqt/statusbar.py @@ -2,7 +2,7 @@ """Status bar Module""" from time import time -from PyQt4 import QtGui +from PyQt4 import QtGui # pylint: disable=import-error class BMStatusBar(QtGui.QStatusBar): diff --git a/src/bitmessageqt/support.py b/src/bitmessageqt/support.py index a84affa46c..40da6d6a0c 100644 --- a/src/bitmessageqt/support.py +++ b/src/bitmessageqt/support.py @@ -6,7 +6,7 @@ import sys import time -from PyQt4 import QtCore +from PyQt4 import QtCore # pylint: disable=import-error import account import defaults @@ -133,6 +133,7 @@ def createSupportMessage(myapp): architecture = "32" if ctypes.sizeof(ctypes.c_voidp) == 4 else "64" pythonversion = sys.version + # pylint: disable=protected-access opensslversion = "%s (Python internal), %s (external for PyElliptic)" % ( ssl.OPENSSL_VERSION, OpenSSL._version) @@ -140,7 +141,7 @@ def createSupportMessage(myapp): if paths.frozen: frozen = paths.frozen portablemode = "True" if state.appdata == paths.lookupExeFolder() else "False" - cpow = "True" if proofofwork.bmpow else "False" + cpow = "True" if proofofwork.BMPOW else "False" openclpow = str( config.safeGet('bitmessagesettings', 'opencl') ) if openclEnabled() else "None" diff --git a/src/bitmessageqt/tests/addressbook.py b/src/bitmessageqt/tests/addressbook.py index cd86c5d665..8289ca825d 100644 --- a/src/bitmessageqt/tests/addressbook.py +++ b/src/bitmessageqt/tests/addressbook.py @@ -1,7 +1,10 @@ +""" +Test PyQt addressbook +""" import helper_addressbook from bitmessageqt.support import createAddressIfNeeded -from main import TestBase +from main import TestBase # pylint: disable=no-name-in-module class TestAddressbook(TestBase): diff --git a/src/bitmessageqt/tests/main.py b/src/bitmessageqt/tests/main.py index d3fda8aa71..daf5d6d226 100644 --- a/src/bitmessageqt/tests/main.py +++ b/src/bitmessageqt/tests/main.py @@ -1,5 +1,5 @@ """Common definitions for bitmessageqt tests""" - +# pylint: disable=import-error import sys import unittest diff --git a/src/bitmessageqt/tests/settings.py b/src/bitmessageqt/tests/settings.py index bad28ed736..577d71a894 100644 --- a/src/bitmessageqt/tests/settings.py +++ b/src/bitmessageqt/tests/settings.py @@ -1,4 +1,5 @@ """Tests for PyBitmessage settings""" +# pylint: disable=import-error import threading import time diff --git a/src/bitmessageqt/tests/support.py b/src/bitmessageqt/tests/support.py index ba28b73a1b..be5fb7a11b 100644 --- a/src/bitmessageqt/tests/support.py +++ b/src/bitmessageqt/tests/support.py @@ -1,11 +1,13 @@ +""" +PyQt4 test for support request dialog +""" # from PyQt4 import QtTest import sys +from main import TestBase # pylint: disable=no-name-in-module from shared import isAddressInMyAddressBook -from main import TestBase - class TestSupport(TestBase): """A test case for support module""" diff --git a/src/bitmessageqt/uisignaler.py b/src/bitmessageqt/uisignaler.py index c23ec3bc42..3ee968b161 100644 --- a/src/bitmessageqt/uisignaler.py +++ b/src/bitmessageqt/uisignaler.py @@ -1,5 +1,5 @@ -from PyQt4.QtCore import QThread, SIGNAL +from PyQt4.QtCore import QThread, SIGNAL # pylint: disable=import-error import sys import queues @@ -40,12 +40,14 @@ def run(self): elif command == 'displayNewInboxMessage': inventoryHash, toAddress, fromAddress, subject, body = data self.emit(SIGNAL( - "displayNewInboxMessage(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), + "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)"), + "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 diff --git a/src/bitmessageqt/utils.py b/src/bitmessageqt/utils.py index 9f849b3bb0..80e7945f57 100644 --- a/src/bitmessageqt/utils.py +++ b/src/bitmessageqt/utils.py @@ -1,7 +1,7 @@ import hashlib import os -from PyQt4 import QtGui +from PyQt4 import QtGui # pylint: disable=import-error import state from addresses import addBMIfNotPresent @@ -56,7 +56,7 @@ def identiconize(address): 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 + from pydenticon import Pydenticon # pylint: disable=import-error # 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: @@ -65,7 +65,7 @@ def identiconize(address): # https://python-pillow.org/ idcon_render = Pydenticon( addBMIfNotPresent(address) + identiconsuffix, size * 3) - rendering = idcon_render._render() + rendering = idcon_render._render() # pylint: disable=protected-access data = rendering.convert("RGBA").tostring("raw", "RGBA") qim = QtGui.QImage(data, size, size, QtGui.QImage.Format_ARGB32) pix = QtGui.QPixmap.fromImage(qim) diff --git a/src/bitmessageqt/widgets.py b/src/bitmessageqt/widgets.py index 8ef807f28d..4338d71f53 100644 --- a/src/bitmessageqt/widgets.py +++ b/src/bitmessageqt/widgets.py @@ -1,13 +1,15 @@ -from PyQt4 import uic +from PyQt4 import uic # pylint: disable=import-error 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)): + 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/bmconfigparser.py b/src/bmconfigparser.py index f0e24c25ca..68fb1e72f2 100644 --- a/src/bmconfigparser.py +++ b/src/bmconfigparser.py @@ -35,7 +35,7 @@ def set(self, section, option, value=None): raise ValueError("Invalid value %s" % value) return SafeConfigParser.set(self, section, option, value) - def get(self, section, option, **kwargs): + def get(self, section, option, **kwargs): # pylint: disable=arguments-differ """Try returning temporary value before using parent get()""" try: return self._temp[section][option] @@ -87,11 +87,11 @@ def safeGet(self, section, option, default=None): ValueError, AttributeError): return default - def items(self, section, raw=False, variables=None): + def items(self, section, raw=False, vars=None): # pylint: disable=redefined-builtin # pylint: disable=signature-differs """Return section variables as parent, but override the "raw" argument to always True""" - return SafeConfigParser.items(self, section, True, variables) + return SafeConfigParser.items(self, section, True, vars) def _reset(self): """ @@ -128,7 +128,7 @@ def save(self): shutil.copyfile(fileName, fileNameBak) # The backup succeeded. fileNameExisted = True - except(IOError, Exception): + except (IOError, Exception): # The backup failed. This can happen if the file # didn't exist before. fileNameExisted = False @@ -160,7 +160,7 @@ def validate_bitmessagesettings_maxoutboundconnections(value): 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] + address.lower()] if searched_text in x] def disable_address(self, address): """"Disabling the specific Address""" diff --git a/src/class_addressGenerator.py b/src/class_addressGenerator.py index 79b76e7300..db0fe9fd7a 100644 --- a/src/class_addressGenerator.py +++ b/src/class_addressGenerator.py @@ -32,10 +32,10 @@ def stopThread(self): super(addressGenerator, self).stopThread() + # pylint: disable=too-many-arguments,too-many-positional-arguments def save_address( - # pylint: disable=too-many-arguments,too-many-positional-arguments - self, version, stream, ripe, label, signing_key, encryption_key, - nonceTrialsPerByte, payloadLengthExtraBytes + self, version, stream, ripe, label, signing_key, encryption_key, + nonceTrialsPerByte, payloadLengthExtraBytes ): """Write essential address config values and reload cryptors""" address = encodeAddress(version, stream, ripe) @@ -183,8 +183,8 @@ def run(self): ripe = highlevelcrypto.to_ripe( pubSigningKey, potentialPubEncryptionKey) if ( - ripe[:numberOfNullBytesDemandedOnFrontOfRipeHash] - == b'\x00' * numberOfNullBytesDemandedOnFrontOfRipeHash + ripe[:numberOfNullBytesDemandedOnFrontOfRipeHash] + == b'\x00' * numberOfNullBytesDemandedOnFrontOfRipeHash ): break self.logger.info( @@ -221,8 +221,8 @@ def run(self): )) elif command in ( - 'createDeterministicAddresses', 'createChan', - 'getDeterministicAddress', 'joinChan' + 'createDeterministicAddresses', 'createChan', + 'getDeterministicAddress', 'joinChan' ): if not deterministicPassphrase: self.logger.warning( @@ -268,8 +268,8 @@ def run(self): ripe = highlevelcrypto.to_ripe( potentialPubSigningKey, potentialPubEncryptionKey) if ( - ripe[:numberOfNullBytesDemandedOnFrontOfRipeHash] - == b'\x00' * numberOfNullBytesDemandedOnFrontOfRipeHash + ripe[:numberOfNullBytesDemandedOnFrontOfRipeHash] + == b'\x00' * numberOfNullBytesDemandedOnFrontOfRipeHash ): break @@ -303,9 +303,9 @@ def run(self): saveAddressToDisk = False if saveAddressToDisk and live and self.save_address( - addressVersionNumber, streamNumber, ripe, label, - potentialPrivSigningKey, potentialPrivEncryptionKey, - nonceTrialsPerByte, payloadLengthExtraBytes + addressVersionNumber, streamNumber, ripe, label, + potentialPrivSigningKey, potentialPrivEncryptionKey, + nonceTrialsPerByte, payloadLengthExtraBytes ): if command in ('createChan', 'joinChan'): config.set(address, 'chan', 'true') @@ -326,7 +326,7 @@ def run(self): # Done generating addresses. if command in ( - 'createDeterministicAddresses', 'createChan', 'joinChan' + 'createDeterministicAddresses', 'createChan', 'joinChan' ): queues.apiAddressGeneratorReturnQueue.put( listOfNewAddressesToSendOutThroughTheAPI) diff --git a/src/class_objectProcessor.py b/src/class_objectProcessor.py index 30341a7e6b..72a1cfa0f8 100644 --- a/src/class_objectProcessor.py +++ b/src/class_objectProcessor.py @@ -4,6 +4,7 @@ """ # pylint: disable=too-many-locals,too-many-return-statements # pylint: disable=too-many-branches,too-many-statements +# pylint: disable=too-many-lines import hashlib import logging import os @@ -140,15 +141,19 @@ def checkackdata(data): # bypass nonce and time, retain object type/version/stream + body readPosition = 16 - if data[readPosition:] in state.ackdataForWhichImWatching: + # data may be a memoryview, which is not hashable and thus + # cannot be used as a dictionary key; convert the slice to bytes. + ackcheckdata = bytes(data[readPosition:]) + + if ackcheckdata in state.ackdataForWhichImWatching: logger.info('This object is an acknowledgement bound for me.') - del state.ackdataForWhichImWatching[data[readPosition:]] + del state.ackdataForWhichImWatching[ackcheckdata] sqlExecute( "UPDATE sent SET status='ackreceived', lastactiontime=?" - " WHERE ackdata=?", int(time.time()), data[readPosition:]) + " WHERE ackdata=?", int(time.time()), ackcheckdata) queues.UISignalQueue.put(( 'updateSentItemStatusByAckdata', ( - data[readPosition:], + ackcheckdata, _translate( "MainWindow", "Acknowledgement of the message received %1" @@ -207,7 +212,9 @@ def processgetpubkey(data): myAddress = '' if requestedAddressVersionNumber <= 3: - requestedHash = data[readPosition:readPosition + 20] + # data may be a memoryview; convert slices to bytes so they + # are hashable and can be used as dictionary keys. + requestedHash = bytes(data[readPosition:readPosition + 20]) if len(requestedHash) != 20: return logger.debug( 'The length of the requested hash is not 20 bytes.' @@ -219,7 +226,8 @@ def processgetpubkey(data): if requestedHash in shared.myAddressesByHash: myAddress = shared.myAddressesByHash[requestedHash] elif requestedAddressVersionNumber >= 4: - requestedTag = data[readPosition:readPosition + 32] + # data may be a memoryview; convert to bytes for hashability. + requestedTag = bytes(data[readPosition:readPosition + 32]) if len(requestedTag) != 32: return logger.debug( 'The length of the requested tag is not 32 bytes.' @@ -412,7 +420,8 @@ def processpubkey(self, data): '(within processpubkey) payloadLength less than 350.' ' Sanity check failed.') - tag = data[readPosition:readPosition + 32] + # data may be a memoryview; convert to bytes for hashability. + tag = bytes(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.') @@ -718,10 +727,10 @@ def processmsg(self, data): # 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') + 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 = \ @@ -806,7 +815,8 @@ def processbroadcast(self, data): ' v4 broadcast: %s seconds.', time.time() - messageProcessingStartTime) elif broadcastVersion == 5: - embeddedTag = data[readPosition:readPosition + 32] + # data may be a memoryview; convert to bytes for hashability. + embeddedTag = bytes(data[readPosition:readPosition + 32]) readPosition += 32 if embeddedTag not in shared.MyECSubscriptionCryptorObjects: logger.debug('We\'re not interested in this broadcast.') diff --git a/src/class_singleWorker.py b/src/class_singleWorker.py index da9d64c6ad..508eb4b00a 100644 --- a/src/class_singleWorker.py +++ b/src/class_singleWorker.py @@ -3,6 +3,7 @@ """ # pylint: disable=protected-access,too-many-branches,too-many-statements # pylint: disable=no-self-use,too-many-lines,too-many-locals +# pylint: disable=import-error,redefined-builtin from __future__ import division @@ -221,9 +222,9 @@ def _getKeysForAddress(self, address): @classmethod def _doPOWDefaults( - cls, payload, TTL, - nonceTrialsPerByte=None, payloadLengthExtraBytes=None, - log_prefix='', log_time=False + cls, payload, TTL, + nonceTrialsPerByte=None, payloadLengthExtraBytes=None, + log_prefix='', log_time=False ): if not nonceTrialsPerByte: nonceTrialsPerByte = \ @@ -491,7 +492,7 @@ def sendOutOrStoreMyV4Pubkey(self, myAddress): def sendOnionPeerObj(self, peer=None): """Send onionpeer object representing peer""" if not peer: # find own onionhostname - for peer in state.ownAddresses: + for peer in state.ownAddresses: # pylint: disable=redefined-argument-from-local if peer.host.endswith('.onion'): break else: @@ -755,9 +756,9 @@ def sendMsg(self): # 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 + '''UPDATE sent SET status='doingmsgpow' ''' + ''' WHERE toaddress=? AND status='msgqueued' AND folder='sent' ''', + toaddress ): continue status = 'doingmsgpow' @@ -771,9 +772,9 @@ def sendMsg(self): 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 + '''UPDATE sent SET status='doingmsgpow' ''' + ''' WHERE toaddress=? AND status='msgqueued' AND folder='sent' ''', + toaddress ): continue status = 'doingmsgpow' diff --git a/src/depends.py b/src/depends.py index 15ddc94ac2..6ddff80b57 100755 --- a/src/depends.py +++ b/src/depends.py @@ -3,6 +3,8 @@ and suggest how it may be installed """ +# flake8: noqa:E402 + import os import re import sys @@ -186,13 +188,13 @@ def try_import(module, log_extra=False): logger.error( 'On %s, try running "%s %s" as root.', dist, PACKAGE_MANAGER[dist], PACKAGES[module][dist]) - return False + return None def check_ripemd160(): """Check availability of the RIPEMD160 hash function""" try: - from fallback import RIPEMD160Hash # pylint: disable=relative-import + from fallback import RIPEMD160Hash except ImportError: return False return RIPEMD160Hash is not None @@ -272,6 +274,7 @@ def check_openssl(): if sys.platform == 'win32': paths = ['libeay32.dll'] if getattr(sys, 'frozen', False): + # pylint: disable=no-member paths.insert(0, os.path.join(sys._MEIPASS, 'libeay32.dll')) else: paths = ['libcrypto.so', 'libcrypto.so.1.0.0'] diff --git a/src/fallback/umsgpack/umsgpack.py b/src/fallback/umsgpack/umsgpack.py index ff11fc2c4e..9378cbe506 100644 --- a/src/fallback/umsgpack/umsgpack.py +++ b/src/fallback/umsgpack/umsgpack.py @@ -410,8 +410,6 @@ def _pack2(obj, fp, **options): >>> umsgpack.pack({u"compact": True, u"schema": 0}, f) >>> """ - global compatibility - ext_handlers = options.get("ext_handlers") if obj is None: @@ -480,8 +478,6 @@ def _pack3(obj, fp, **options): >>> umsgpack.pack({u"compact": True, u"schema": 0}, f) >>> """ - global compatibility - ext_handlers = options.get("ext_handlers") if obj is None: @@ -665,7 +661,6 @@ def _unpack_string(code, fp, options): 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) diff --git a/src/helper_search.py b/src/helper_search.py index 85a9e97172..0ffdd01070 100644 --- a/src/helper_search.py +++ b/src/helper_search.py @@ -8,8 +8,8 @@ def search_sql( - xAddress='toaddress', account=None, folder='inbox', where=None, - what=None, unreadOnly=False + xAddress='toaddress', account=None, folder='inbox', where=None, + what=None, unreadOnly=False ): """ Search for messages from given account and folder having search term @@ -88,24 +88,24 @@ def check_match( return True if where in ( - _translate("MainWindow", "To"), _translate("MainWindow", "All") + _translate("MainWindow", "To"), _translate("MainWindow", "All") ): if what.lower() not in toAddress.lower(): return False elif where in ( - _translate("MainWindow", "From"), _translate("MainWindow", "All") + _translate("MainWindow", "From"), _translate("MainWindow", "All") ): if what.lower() not in fromAddress.lower(): return False elif where in ( - _translate("MainWindow", "Subject"), - _translate("MainWindow", "All") + _translate("MainWindow", "Subject"), + _translate("MainWindow", "All") ): if what.lower() not in subject.lower(): return False elif where in ( - _translate("MainWindow", "Message"), - _translate("MainWindow", "All") + _translate("MainWindow", "Message"), + _translate("MainWindow", "All") ): if what.lower() not in message.lower(): return False diff --git a/src/helper_sent.py b/src/helper_sent.py index aa76e756a8..dda5d8f023 100644 --- a/src/helper_sent.py +++ b/src/helper_sent.py @@ -44,8 +44,7 @@ def insert(msgid=None, toAddress='[Broadcast subscribers]', fromAddress=None, su sqlExecute('''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''', *t) return ackdata - else: - return None + return None def delete(ack_data): diff --git a/src/helper_startup.py b/src/helper_startup.py index 52e1bf7ae4..541dab7206 100644 --- a/src/helper_startup.py +++ b/src/helper_startup.py @@ -32,6 +32,11 @@ logger = logging.getLogger('default') +#: The latest version of the keys.dat settings schema. Bump this +#: when adding a new migration step in :func:`updateConfig` or +#: :class:`class_sqlThread.sqlThread`. +LATEST_SETTINGS_VERSION = 10 + # 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 @@ -72,7 +77,9 @@ def loadConfig(): # no config file (or it cannot be accessed). Create config file. # config.add_section('bitmessagesettings') config.read() - config.set('bitmessagesettings', 'settingsversion', '10') + config.set( + 'bitmessagesettings', 'settingsversion', + str(LATEST_SETTINGS_VERSION)) if 'linux' in sys.platform: config.set('bitmessagesettings', 'minimizetotray', 'false') # This isn't implimented yet and when True on diff --git a/src/main-android-live.py b/src/main-android-live.py index e164443614..c880280e4a 100644 --- a/src/main-android-live.py +++ b/src/main-android-live.py @@ -1,9 +1,11 @@ """This module is for thread start.""" +# pylint: disable=superfluous-parens 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')) +print(colored('kivy is not supported at the moment for this version..', + 'red')) sys.exit() diff --git a/src/main.py b/src/main.py index ce042b84d3..f39b6fd2e9 100644 --- a/src/main.py +++ b/src/main.py @@ -4,13 +4,13 @@ """Mock kivy app with mock threads.""" import os -from kivy.config import Config -from mockbm import multiqueue -import state +from kivy.config import Config # pylint: disable=import-error -from mockbm.class_addressGenerator import FakeAddressGenerator # noqa:E402 -from bitmessagekivy.mpybit import NavigateApp # noqa:E402 -from mockbm import network # noqa:E402 +import pybitmessage.state as state +from pybitmessage.bitmessagekivy.mpybit import NavigateApp # noqa:E402 +from pybitmessage.mockbm import multiqueue +from pybitmessage.mockbm.class_addressGenerator import FakeAddressGenerator # noqa:E402 +from pybitmessage.mockbm import network # noqa:E402 stats = network.stats objectracker = network.objectracker diff --git a/src/network/asyncore_pollchoose.py b/src/network/asyncore_pollchoose.py index b7d1049b79..2a23bb797d 100644 --- a/src/network/asyncore_pollchoose.py +++ b/src/network/asyncore_pollchoose.py @@ -560,12 +560,12 @@ def del_channel(self): try: kqueue_poller.pollster.control([select.kevent( fd, select.KQ_FILTER_READ, select.KQ_EV_DELETE)], 0) - except(AttributeError, KeyError, TypeError, IOError, OSError): + 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): + except (AttributeError, KeyError, TypeError, IOError, OSError): pass try: epoll_poller.pollster.unregister(fd) diff --git a/src/network/bmproto.py b/src/network/bmproto.py index aa830cf5aa..13d6f4e77b 100644 --- a/src/network/bmproto.py +++ b/src/network/bmproto.py @@ -67,6 +67,25 @@ def __init__(self, address=None, sock=None): # track port check requests, only allow one per connection # completely disable port checks for now self.portCheckRequested = True + self.payloadLength = 0 + self.payloadOffset = 0 + self.invalid = False + self.timeOffset = 0 + self.verackSent = False + self.verackReceived = False + self.payload = None + self.isSSL = False + self.remoteProtocolVersion = 0 + self.nonce = 0 + self.peerNode = None + self.streams = None + self.timestamp = 0 + self.object = None + self.services = None + self.sockNode = None + self.magic = None + self.checksum = None + self.command = None def bm_proto_reset(self): """Reset the bitmessage object parser""" @@ -422,7 +441,7 @@ def bm_command_object(self): try: self.object.checkObjectByType() objectProcessorQueue.put(( - self.object.objectType, memoryview(self.object.data))) + self.object.objectType, bytes(self.object.data))) except BMObjectInvalidError: self.stopDownloadingObject(self.object.inventoryHash, True) else: @@ -438,8 +457,8 @@ def bm_command_object(self): state.Inventory[self.object.inventoryHash] = ( self.object.objectType, self.object.streamNumber, - memoryview(self.payload[objectOffset:]), self.object.expiresTime, - memoryview(self.object.tag) + bytes(self.payload[objectOffset:]), self.object.expiresTime, + bytes(self.object.tag) ) self.handleReceivedObject( self.object.streamNumber, self.object.inventoryHash) diff --git a/src/network/http.py b/src/network/http.py deleted file mode 100644 index 8a49e760c8..0000000000 --- a/src/network/http.py +++ /dev/null @@ -1,89 +0,0 @@ -import socket - -from .advanceddispatcher import AdvancedDispatcher -from . 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 8b5153239a..0000000000 --- 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 '_map' not in self.__dict__: - 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 '_map' not in self.__dict__: - 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 '_map' not in self.__dict__: - 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 '_map' not in self.__dict__: - 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 a5cc2ac6cb..0000000000 --- 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 '_map' not in self.__dict__: - 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/tls.py b/src/network/tls.py index 2ffdbaf2d8..a1059d2b6a 100644 --- a/src/network/tls.py +++ b/src/network/tls.py @@ -57,6 +57,7 @@ def __init__(self, _=None, sock=None, certfile=None, keyfile=None, self.tlsDone = False self.tlsVersion = "N/A" self.isSSL = False + self.sslSocket = None def state_tls_init(self): """Prepare sockets for TLS handshake""" diff --git a/src/pathmagic.py b/src/pathmagic.py index 3f32c0c1ef..0fcf44d801 100644 --- a/src/pathmagic.py +++ b/src/pathmagic.py @@ -1,3 +1,6 @@ +""" +Hack to work around relative imports +""" import os import sys diff --git a/src/plugins/indicator_libmessaging.py b/src/plugins/indicator_libmessaging.py index b471d2efc2..c73b06adbe 100644 --- a/src/plugins/indicator_libmessaging.py +++ b/src/plugins/indicator_libmessaging.py @@ -3,8 +3,11 @@ Indicator plugin using libmessaging """ +# flake8: noqa:E402 +# pycodestyle: disable=E402 +# pylint: disable=import-error,no-name-in-module import gi -gi.require_version('MessagingMenu', '1.0') # noqa:E402 +gi.require_version('MessagingMenu', '1.0') from gi.repository import MessagingMenu from pybitmessage.bitmessageqt.utils import str_broadcast_subscribers diff --git a/src/plugins/menu_qrcode.py b/src/plugins/menu_qrcode.py index ea322a4940..e04acd706a 100644 --- a/src/plugins/menu_qrcode.py +++ b/src/plugins/menu_qrcode.py @@ -6,7 +6,7 @@ import urllib import qrcode -from PyQt4 import QtCore, QtGui +from PyQt4 import QtCore, QtGui # pylint: disable=import-error from pybitmessage.tr import _translate diff --git a/src/plugins/notification_notify2.py b/src/plugins/notification_notify2.py index f851737da1..e5e7e6e559 100644 --- a/src/plugins/notification_notify2.py +++ b/src/plugins/notification_notify2.py @@ -5,7 +5,7 @@ import gi gi.require_version('Notify', '0.7') -from gi.repository import Notify # noqa:E402 +from gi.repository import Notify # noqa:E402 pylint: disable=import-error Notify.init('pybitmessage') diff --git a/src/plugins/proxyconfig_stem.py b/src/plugins/proxyconfig_stem.py index 25f75f697f..f770e3a337 100644 --- a/src/plugins/proxyconfig_stem.py +++ b/src/plugins/proxyconfig_stem.py @@ -11,6 +11,7 @@ * otherwise use stem's 'BEST' version and save onion keys to the new section using *onionhostname* as name for future use. """ +# pylint: disable=import-error import logging import os import random diff --git a/src/plugins/sound_canberra.py b/src/plugins/sound_canberra.py index 9fea8197c8..69ec77863a 100644 --- a/src/plugins/sound_canberra.py +++ b/src/plugins/sound_canberra.py @@ -3,7 +3,7 @@ Sound theme plugin using pycanberra """ -import pycanberra +import pycanberra # pylint: disable=import-error from pybitmessage.bitmessageqt import sound _canberra = pycanberra.Canberra() diff --git a/src/plugins/sound_gstreamer.py b/src/plugins/sound_gstreamer.py index 8f3606dd87..002d106074 100644 --- a/src/plugins/sound_gstreamer.py +++ b/src/plugins/sound_gstreamer.py @@ -2,6 +2,7 @@ """ Sound notification plugin using gstreamer """ +# pylint: disable=import-error,no-name-in-module import gi gi.require_version('Gst', '1.0') from gi.repository import Gst # noqa: E402 diff --git a/src/plugins/sound_playfile.py b/src/plugins/sound_playfile.py index c6b70f66b1..26db0e2840 100644 --- a/src/plugins/sound_playfile.py +++ b/src/plugins/sound_playfile.py @@ -22,7 +22,8 @@ def _subprocess(*args): def connect_plugin(sound_file): """This function implements the entry point.""" - global play_cmd # pylint: disable=global-statement + # pylint: disable=global-statement,global-variable-not-assigned + global play_cmd ext = os.path.splitext(sound_file)[-1] try: diff --git a/src/proofofwork.py b/src/proofofwork.py index 539db710b2..70733eb99b 100644 --- a/src/proofofwork.py +++ b/src/proofofwork.py @@ -25,8 +25,8 @@ from tr import _translate -bitmsglib = 'bitmsghash.so' -bmpow = None +BITMSGLIB = 'bitmsghash.so' +BMPOW = None class LogOutput(object): # pylint: disable=too-few-public-methods @@ -173,7 +173,7 @@ def _doCPoW(target, initialHash): 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) + nonce = BMPOW(out_h, out_m) trialValue = trial_value(nonce, initialHash) if state.shutdown != 0: @@ -200,7 +200,7 @@ def _doGPUPoW(target, initialHash): 'Your GPUs (%s) did not calculate correctly, disabling OpenCL.' ' Please report to the developers.', deviceNames) openclpow.enabledGpus = [] - raise Exception("GPU did not calculate correctly.") + raise RuntimeError("GPU did not calculate correctly.") if state.shutdown != 0: raise StopIteration("Interrupted") logger.debug('GPU PoW done') @@ -240,7 +240,7 @@ def getPowType(): if openclpow.openclEnabled(): return "OpenCL" - if bmpow: + if BMPOW: return "C" return "python" @@ -250,7 +250,7 @@ def notifyBuild(tried=False): Notify the user of the success or otherwise of building the PoW C module """ - if bmpow: + if BMPOW: queues.UISignalQueue.put(('updateStatusBar', (_translate( "proofofwork", "C PoW module built successfully."), 1))) elif tried: @@ -264,7 +264,7 @@ def notifyBuild(tried=False): def buildCPoW(): """Attempt to build the PoW C module""" - if bmpow is not None: + if BMPOW is not None: return if paths.frozen or sys.platform.startswith('win'): notifyBuild(False) @@ -279,7 +279,9 @@ def buildCPoW(): subprocess.check_call(make_cmd) # nosec B603 if os.path.exists( - os.path.join(paths.codePath(), 'bitmsghash', 'bitmsghash.so') + os.path.join(paths.codePath(), + 'bitmsghash', + 'bitmsghash.so') ): init() except (OSError, subprocess.CalledProcessError): @@ -299,7 +301,7 @@ def run(target, initialHash): target = int(target) if openclpow.openclEnabled(): return _doGPUPoW(target, initialHash) - if bmpow: + if BMPOW: return _doCPoW(target, initialHash) if paths.frozen == "macosx_app" or not paths.frozen: # on my (Peter Surda) Windows 10, Windows Defender @@ -323,14 +325,14 @@ def getTarget(payloadLength, ttl, nonceTrialsPerByte, payloadLengthExtraBytes): def calculate( - payload, ttl, - nonceTrialsPerByte=networkDefaultProofOfWorkNonceTrialsPerByte, - payloadLengthExtraBytes=networkDefaultPayloadLengthExtraBytes + payload, ttl, + nonceTrialsPerByte=networkDefaultProofOfWorkNonceTrialsPerByte, + payloadLengthExtraBytes=networkDefaultPayloadLengthExtraBytes ): """Do the PoW for the payload and TTL with optional difficulty params""" - return run(getTarget( - len(payload), ttl, nonceTrialsPerByte, payloadLengthExtraBytes), - hashlib.sha512(payload).digest()) + return run(getTarget(len(payload), ttl, nonceTrialsPerByte, + payloadLengthExtraBytes), + hashlib.sha512(payload).digest()) def resetPoW(): @@ -344,34 +346,34 @@ def resetPoW(): def init(): """Initialise PoW""" # pylint: disable=broad-exception-caught,global-statement - global bitmsglib, bmpow + global BITMSGLIB, BMPOW openclpow.initCL() if sys.platform.startswith('win'): - bitmsglib = ( + BITMSGLIB = ( 'bitmsghash32.dll' if ctypes.sizeof(ctypes.c_voidp) == 4 else 'bitmsghash64.dll') - libfile = os.path.join(paths.codePath(), 'bitmsghash', bitmsglib) + libfile = os.path.join(paths.codePath(), 'bitmsghash', BITMSGLIB) 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 + 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) + 'Successfully tested C PoW DLL (stdcall) %s', BITMSGLIB) except ValueError: try: # MinGW bso = ctypes.CDLL(libfile) - logger.info('Loaded C PoW DLL (cdecl) %s', bitmsglib) - bmpow = bso.BitmessagePOW - bmpow.restype = ctypes.c_ulonglong + 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) + 'Successfully tested C PoW DLL (cdecl) %s', BITMSGLIB) except Exception as e: logger.error('Error: %s', e, exc_info=True) except Exception as e: @@ -379,7 +381,7 @@ def init(): else: try: bso = ctypes.CDLL( - os.path.join(paths.codePath(), 'bitmsghash', bitmsglib)) + os.path.join(paths.codePath(), 'bitmsghash', BITMSGLIB)) except OSError: import glob try: @@ -391,15 +393,15 @@ def init(): except Exception: bso = None else: - logger.info('Loaded C PoW DLL %s', bitmsglib) + logger.info('Loaded C PoW DLL %s', BITMSGLIB) if bso: try: - bmpow = bso.BitmessagePOW - bmpow.restype = ctypes.c_ulonglong + BMPOW = bso.BitmessagePOW + BMPOW.restype = ctypes.c_ulonglong except Exception: logger.warning( - 'Failed to setup bmpow lib %s', bso, exc_info=True) + 'Failed to setup BMPOW lib %s', bso, exc_info=True) return - if bmpow is None: + if BMPOW is None: buildCPoW() diff --git a/src/protocol.py b/src/protocol.py index 5f693a13b9..399ab5bad5 100644 --- a/src/protocol.py +++ b/src/protocol.py @@ -347,8 +347,8 @@ def assembleAddrMessage(peerList): def assembleVersionMessage( - remoteHost, remotePort, participatingStreams, - dandelion_enabled=True, server=False, nodeid=None + remoteHost, remotePort, participatingStreams, + dandelion_enabled=True, server=False, nodeid=None ): """ Construct the payload of a version message, @@ -395,9 +395,10 @@ def assembleVersionMessage( # 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)) + 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 diff --git a/src/pyelliptic/openssl.py b/src/pyelliptic/openssl.py index 851dfa1525..6cc90fd43c 100644 --- a/src/pyelliptic/openssl.py +++ b/src/pyelliptic/openssl.py @@ -786,6 +786,7 @@ def loadOpenSSL(): elif 'win32' in sys.platform or 'win64' in sys.platform: libdir.append(path.join(sys._MEIPASS, 'libeay32.dll')) else: + # pylint: disable=no-member libdir.extend([ path.join(sys._MEIPASS, 'libcrypto.so'), path.join(sys._MEIPASS, 'libssl.so'), diff --git a/src/pyelliptic/tests/test_openssl.py b/src/pyelliptic/tests/test_openssl.py index cb78927760..36070a26ff 100644 --- a/src/pyelliptic/tests/test_openssl.py +++ b/src/pyelliptic/tests/test_openssl.py @@ -8,11 +8,7 @@ except ImportError: from pybitmessage.pyelliptic import OpenSSL -try: - OpenSSL.BN_bn2binpad - have_pad = True -except AttributeError: - have_pad = None +have_pad = hasattr(OpenSSL, 'BN_bn2binpad') class TestOpenSSL(unittest.TestCase): diff --git a/src/qidenticon.py b/src/qidenticon.py index 13be357806..ef4a46821c 100644 --- a/src/qidenticon.py +++ b/src/qidenticon.py @@ -262,9 +262,9 @@ def decode(self, code, twoColor): else: secondColor = foreColor - return (middleType, middleInvert, 0),\ - (cornerType, cornerInvert, cornerTurn),\ - (sideType, sideInvert, sideTurn),\ + return (middleType, middleInvert, 0), \ + (cornerType, cornerInvert, cornerTurn), \ + (sideType, sideInvert, sideTurn), \ foreColor, secondColor, swap_cross diff --git a/src/shared.py b/src/shared.py index 9357a4ee4f..afb614dda5 100644 --- a/src/shared.py +++ b/src/shared.py @@ -15,7 +15,7 @@ import sys from binascii import hexlify -from six.moves.reprlib import repr +from six.moves.reprlib import repr # pylint: disable=redefined-builtin,import-error # Project imports. import highlevelcrypto @@ -102,9 +102,9 @@ def reloadMyAddressHashes(): # Returns a simple 32 bytes of information encoded in 64 Hex characters try: privEncryptionKey = hexlify( - highlevelcrypto.decodeWalletImportFormat(config.get( - addressInKeysFile, 'privencryptionkey').encode() - )) + highlevelcrypto.decodeWalletImportFormat( + config.get(addressInKeysFile, + 'privencryptionkey').encode())) except ValueError: logger.error( 'Error in reloadMyAddressHashes: failed to decode' diff --git a/src/shutdown.py b/src/shutdown.py index 441d655eef..72ff36f43d 100644 --- a/src/shutdown.py +++ b/src/shutdown.py @@ -55,9 +55,9 @@ def doCleanShutdown(): for thread in threading.enumerate(): if ( - thread is not threading.currentThread() - and isinstance(thread, StoppableThread) - and thread.name != 'SQL' + thread is not threading.currentThread() + and isinstance(thread, StoppableThread) + and thread.name != 'SQL' ): logger.debug("Waiting for thread %s", thread.name) thread.join() diff --git a/src/storage/sqlite.py b/src/storage/sqlite.py index eb5df098d8..2d769dfb02 100644 --- a/src/storage/sqlite.py +++ b/src/storage/sqlite.py @@ -109,7 +109,12 @@ def flush(self): for objectHash, value in self._inventory.items(): sql.execute( 'INSERT INTO inventory VALUES (?, ?, ?, ?, ?, ?)', - sqlite3.Binary(objectHash), *value) + sqlite3.Binary(objectHash), + value.type, + value.stream, + sqlite3.Binary(value.payload), + value.expires, + sqlite3.Binary(value.tag)) self._inventory.clear() def clean(self): diff --git a/src/tests/core.py b/src/tests/core.py index 0ef7b2ef46..5858505143 100644 --- a/src/tests/core.py +++ b/src/tests/core.py @@ -209,8 +209,8 @@ def _check_connection(self, full=False, timeout=360): time.sleep(1) for peer, con in connectionpool.pool.outboundConnections.items(): if ( - peer.host.startswith('bootstrap') - or peer.host == 'quzwelsuziwqgpt2.onion' + peer.host.startswith('bootstrap') + or peer.host == 'quzwelsuziwqgpt2.onion' ): continue self.assertIsInstance(con, connection_base) @@ -278,8 +278,8 @@ def test_onionservicesonly(self): 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') + 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'): diff --git a/src/tests/partial.py b/src/tests/partial.py index f008e847e5..71347c6d67 100644 --- a/src/tests/partial.py +++ b/src/tests/partial.py @@ -24,7 +24,7 @@ def setUpClass(cls): from debug import logger # noqa:F401 pylint: disable=unused-variable if sys.hexversion >= 0x3000000: - # pylint: disable=no-name-in-module,relative-import + # pylint: disable=no-name-in-module from mockbm import network as network_mock import network network.stats = network_mock.stats diff --git a/src/tests/test_config_process.py b/src/tests/test_config_process.py index 9322a2f07f..daaf88bba9 100644 --- a/src/tests/test_config_process.py +++ b/src/tests/test_config_process.py @@ -5,6 +5,7 @@ import os import tempfile from pybitmessage.bmconfigparser import config +from pybitmessage.helper_startup import LATEST_SETTINGS_VERSION from .test_process import TestProcessProto from .common import skip_python3 @@ -22,7 +23,8 @@ def test_config_defaults(self): config.read(os.path.join(self.home, 'keys.dat')) self.assertEqual(config.safeGetInt( - 'bitmessagesettings', 'settingsversion'), 10) + 'bitmessagesettings', 'settingsversion'), + LATEST_SETTINGS_VERSION) self.assertEqual(config.safeGetInt( 'bitmessagesettings', 'port'), 8444) # don't connect diff --git a/src/tests/test_inventory_flush.py b/src/tests/test_inventory_flush.py new file mode 100644 index 0000000000..3e851ec19e --- /dev/null +++ b/src/tests/test_inventory_flush.py @@ -0,0 +1,146 @@ +"""Tests for SqliteInventory.flush()""" +# pylint: disable=protected-access,wrong-import-order,wrong-import-position +# pylint: disable=import-outside-toplevel + +import os +import tempfile +import threading +import time + +from .common import skip_python3 +from .partial import TestPartialRun + +skip_python3() + + +class TestInventoryFlush(TestPartialRun): + """ + Integration test: exercises flush() end-to-end with the real sqlThread + consumer running, so that type errors in parameter binding surface here + rather than silently killing a production thread. + """ + + @classmethod + def setUpClass(cls): + os.environ['BITMESSAGE_HOME'] = tempfile.gettempdir() + super(TestInventoryFlush, cls).setUpClass() + + import helper_sql + from bmconfigparser import config, config_ready + from class_sqlThread import sqlThread + from helper_startup import LATEST_SETTINGS_VERSION + from storage.sqlite import SqliteInventory + + cls._sqlStoredProcedure = staticmethod(helper_sql.sqlStoredProcedure) + + # sqlThread.run() waits on config_ready and then reads + # settingsversion; normally helper_startup.loadConfig() handles + # both, but TestPartialRun only calls config.read() which loads + # default.ini (no settingsversion). Set the minimum the + # sqlThread needs so it can initialise the database. + if not config.has_option( + 'bitmessagesettings', 'settingsversion'): + config.set( + 'bitmessagesettings', 'settingsversion', + str(LATEST_SETTINGS_VERSION)) + config_ready.set() + + # test_api_thread replaces helper_sql.sql_ready with a mock + # that only has wait(); restore a real Event so sqlThread can + # call .set() on it. In Python 2 threading.Event is a factory + # function, not a class, so we duck-type the check. + cls._original_sql_ready = helper_sql.sql_ready + if not hasattr(helper_sql.sql_ready, 'set'): + helper_sql.sql_ready = threading.Event() + + sql_lookup = sqlThread() + sql_lookup.daemon = True + sql_lookup.start() + helper_sql.sql_ready.wait() + cls.inventory = SqliteInventory() + + @classmethod + def tearDownClass(cls): + import helper_sql + from bmconfigparser import config_ready + + cls._sqlStoredProcedure('exit') + for thread in threading.enumerate(): + if thread.name == "SQL": + thread.join(timeout=10) + helper_sql.sql_ready = cls._original_sql_ready + # Reset config to default.ini so added settingsversion does + # not leak into subsequent tests. Also clear config_ready + # since it is a one-shot event set by loadConfig(). + cls.config.read() + config_ready.clear() + super(TestInventoryFlush, cls).tearDownClass() + + # -- helpers ---------------------------------------------------------- + + @staticmethod + def _make_hash(seed): + """Return a 32-byte hash derived from *seed*.""" + return (b'\x00' * 31 + bytes([seed & 0xFF]))[-32:] + + def _flush_and_check(self, obj_hash, expected_payload=None): + """ + Flush the inventory to the database, clear both in-memory + caches so that __contains__ and __getitem__ are forced to + hit sqlite, then verify the hash is found and (optionally) + that the payload content survived the round-trip. + """ + self.inventory.flush() + self.inventory._objects.clear() + self.assertIn(obj_hash, self.inventory) + if expected_payload is not None: + value = self.inventory[obj_hash] + self.assertEqual( + bytes(value.payload), expected_payload, + "Payload content corrupted after flush") + + # -- test cases ------------------------------------------------------- + + def test_flush_payload_roundtrip(self): + """Payload content must survive the flush round-trip.""" + h = self._make_hash(1) + payload = b'\x80\x01' + os.urandom(64) + self.inventory[h] = ( + 2, 1, payload, + int(time.time()) + 3600, b'\xff' * 32) + self._flush_and_check(h, payload) + + def test_flush_with_empty_tag(self): + """Empty tag (b'') must not break the INSERT.""" + h = self._make_hash(2) + payload = b'\x80\x02' + os.urandom(64) + self.inventory[h] = ( + 2, 1, payload, + int(time.time()) + 3600, b'') + self._flush_and_check(h, payload) + + def test_flush_multiple_items(self): + """Flush a batch and verify every row arrives.""" + count = 20 + hashes = [self._make_hash(0x10 + i) for i in range(count)] + expires = int(time.time()) + 3600 + + for i, h in enumerate(hashes): + self.inventory[h] = ( + 2, 1, os.urandom(64), expires, b'\x00' * 32) + + self.inventory.flush() + self.inventory._objects.clear() + + for i, h in enumerate(hashes): + self.assertIn( + h, self.inventory, + "Item {} missing after batch flush".format(i)) + + def test_flush_clears_memory_cache(self): + """After flush the in-memory _inventory dict must be empty.""" + h = self._make_hash(0xF0) + self.inventory[h] = ( + 2, 1, b'\x00' * 32, int(time.time()) + 3600, b'') + self.inventory.flush() + self.assertEqual(len(self.inventory._inventory), 0) diff --git a/src/tests/test_network.py b/src/tests/test_network.py index 11de1329b4..ac757dee89 100644 --- a/src/tests/test_network.py +++ b/src/tests/test_network.py @@ -90,9 +90,10 @@ def test_stats(self): 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 + self.stats.receivedBytes() > 0 + and self.stats.sentBytes() > 0 + and pl > 0 + # and len(self.stats.connectedHostsList()) > 0 ): break time.sleep(1) diff --git a/src/tests/test_shared.py b/src/tests/test_shared.py index ae90a9d441..6fdecb1705 100644 --- a/src/tests/test_shared.py +++ b/src/tests/test_shared.py @@ -174,7 +174,7 @@ def test_check_sensitive_file_permissions(self, mock_sys, mock_os_stat): @patch("pybitmessage.shared.os.chmod") @patch("pybitmessage.shared.os.stat") def test_fix_sensitive_file_permissions( # pylint: disable=no-self-use - self, mock_os_stat, mock_chmod + self, mock_os_stat, mock_chmod ): """Test to fix file permissions""" fake_filename = "path/to/file" diff --git a/tests-kivy.py b/tests-kivy.py index 9bc08880e6..3ddc962e5f 100644 --- a/tests-kivy.py +++ b/tests-kivy.py @@ -22,7 +22,7 @@ def unittest_discover(): if in_docker: try: os.mkdir("../out") - except FileExistsError: # noqa:F821 + except FileExistsError: # noqa:F821 pylint: disable=undefined-variable pass ffmpeg = subprocess.Popen([ # pylint: disable=consider-using-with @@ -38,7 +38,7 @@ def unittest_discover(): if in_docker: ffmpeg.terminate() try: - ffmpeg.wait(10) - except subprocess.TimeoutExpired: + ffmpeg.wait(10) # pylint: disable=too-many-function-args + except subprocess.TimeoutExpired: # pylint: disable=no-member ffmpeg.kill() sys.exit(not result.wasSuccessful()) diff --git a/tox.ini b/tox.ini index de80ed9962..7cf892c42a 100644 --- a/tox.ini +++ b/tox.ini @@ -34,6 +34,74 @@ deps = pylint commands = pylint --rcfile=tox.ini --exit-zero pybitmessage +[testenv:bandit] +skip_install = true +basepython = python3 +deps = + bandit +commands = + bandit -r -s B101,B411,B413,B608 \ + -x checkdeps.*,bitmessagecurses,bitmessageqt,tests pybitmessage + +[testenv:flake8] +skip_install = true +basepython = python3 +deps = + flake8 +commands = + flake8 --config=tox.ini pybitmessage --count --select=E9,F63,F7,F82 \ + --show-source --statistics + +[testenv:pylint] +skip_install = true +basepython = python3 +deps = + -rrequirements.txt + pylint +commands = pylint --rcfile=tox.ini --exit-zero pybitmessage + +[testenv:pycodestyle] +skip_install = true +basepython = python3 +deps = + pycodestyle +commands = + pycodestyle --config=tox.ini pybitmessage + +[testenv:bandit-py27] +skip_install = true +basepython = python2.7 +deps = + bandit<1.7.0 +commands = + bandit -r -s B101,B411,B413,B603,B608 \ + -x checkdeps.*,bitmessagecurses,bitmessageqt,tests pybitmessage + +[testenv:flake8-py27] +skip_install = true +basepython = python2.7 +deps = + flake8<4.0.0 +commands = + flake8 --config=tox.ini pybitmessage --count --select=E9,F63,F7,F82 \ + --show-source --statistics + +[testenv:pycodestyle-py27] +skip_install = true +basepython = python2.7 +deps = + pycodestyle<2.6.0 +commands = + pycodestyle --config=tox.ini pybitmessage + +[testenv:pylint-py27] +skip_install = true +basepython = python2.7 +deps = + -rrequirements.txt + pylint<2.0.0 +commands = pylint --rcfile=tox.ini --exit-zero --ignore=bitmessagekivy pybitmessage + [testenv:py27] sitepackages = true @@ -79,14 +147,47 @@ omit = [coverage:report] ignore_errors = true +[pycodestyle] +max-line-length = 119 +ignore = E402,E722,W503 + +[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 (modern, >= 2.14) -- pylint.*-prefixed sections for setup.cfg/tox.ini + [pylint.main] +ignore = bitmessagecurses,bitmessagekivy,bitmessageqt,messagetypes,mockbm, + network,plugins,umsgpack,bitmessagecli.py + +[pylint.messages_control] disable = invalid-name,consider-using-f-string,fixme,raise-missing-from, - super-with-arguments,unnecessary-pass,unknown-option-value, - unspecified-encoding,useless-object-inheritance,useless-option-value + relative-import,super-with-arguments,unnecessary-pass,unknown-option-value, + unspecified-encoding,useless-object-inheritance,useless-option-value, + bad-option-value + +[pylint.design] +max-args = 8 +max-positional-arguments = 8 +max-attributes = 8 + +# -- pylint (legacy, < 2.0 / python 2.7) -- old .pylintrc-style section names + +[MASTER] ignore = bitmessagecurses,bitmessagekivy,bitmessageqt,messagetypes,mockbm, network,plugins,umsgpack,bitmessagecli.py +[MESSAGES CONTROL] +disable = + invalid-name,bare-except,broad-except,relative-import, + superfluous-parens,bad-option-value + +[DESIGN] max-args = 8 -max-positional-arguments = 8 max-attributes = 8