mock changes

This commit is contained in:
shekhar-cis 2021-12-23 12:19:14 +05:30
parent 871157d0aa
commit e9054a4d37
Signed by untrusted user: shekhar-cis
GPG Key ID: 8B2A6C8D5F7F1635
97 changed files with 16238 additions and 0 deletions

View File

View File

@ -0,0 +1,304 @@
"""
Bitmessage mock
"""
from pybitmessage.class_addressGenerator import addressGenerator
from pybitmessage.class_singleWorker import singleWorker
from pybitmessage.class_objectProcessor import objectProcessor
from pybitmessage.inventory import Inventory
from pybitmessage.bmconfigparser import BMConfigParser
from pybitmessage.class_singleCleaner import singleCleaner
from pybitmessage import state
from pybitmessage.network.threads import StoppableThread
# from pybitmessage.network.connectionpool import BMConnectionPool
# from pybitmessage.network.networkthread import BMNetworkThread
# from pybitmessage.network.receivequeuethread import ReceiveQueueThread
# pylint: disable=too-few-public-methods,no-init,old-style-class
class MockMain:
"""Mock main function"""
# pylint: disable=no-self-use
def start(self):
"""Start main application"""
# pylint: disable=too-many-statements,too-many-branches,too-many-locals, unused-variable
config = BMConfigParser()
daemon = config.safeGetBoolean('bitmessagesettings', 'daemon')
# Start the address generation thread
addressGeneratorThread = addressGenerator()
# close the main program even if there are threads left
addressGeneratorThread.daemon = True
addressGeneratorThread.start()
# Start the thread that calculates POWs
singleWorkerThread = singleWorker()
# close the main program even if there are threads left
singleWorkerThread.daemon = True
singleWorkerThread.start()
# Start the thread that calculates POWs
objectProcessorThread = objectProcessor()
# DON'T close the main program even the thread remains.
# This thread checks the shutdown variable after processing
# each object.
objectProcessorThread.daemon = False
objectProcessorThread.start()
Inventory() # init
# # Start the cleanerThread
singleCleanerThread = singleCleaner()
# # close the main program even if there are threads left
singleCleanerThread.daemon = True
singleCleanerThread.start()
# Not needed if objproc disabled
# if state.enableObjProc:
# shared.reloadMyAddressHashes()
# shared.reloadBroadcastSendersForWhichImWatching()
# API is also objproc dependent
# if config.safeGetBoolean('bitmessagesettings', 'apienabled'):
# # pylint: disable=relative-import
# from pybitmessage import api
# singleAPIThread = api.singleAPI()
# # close the main program even if there are threads left
# singleAPIThread.daemon = True
# singleAPIThread.start()
# # start network components if networking is enabled
# if state.enableNetwork:
# # start_proxyconfig()
# # BMConnectionPool().connectToStream(1)
# asyncoreThread = BMNetworkThread()
# asyncoreThread.daemon = True
# asyncoreThread.start()
# for i in range(config.safeGet('threads', 'receive')):
# receiveQueueThread = ReceiveQueueThread(i)
# receiveQueueThread.daemon = True
# receiveQueueThread.start()
# announceThread = AnnounceThread()
# announceThread.daemon = True
# announceThread.start()
# state.invThread = InvThread()
# state.invThread.daemon = True
# state.invThread.start()
# state.addrThread = AddrThread()
# state.addrThread.daemon = True
# state.addrThread.start()
# state.downloadThread = DownloadThread()
# state.downloadThread.daemon = True
# state.downloadThread.start()
# state.uploadThread = UploadThread()
# state.uploadThread.daemon = True
# state.uploadThread.start()
# if config.safeGetBoolean('bitmessagesettings', 'upnp'):
# import upnp
# upnpThread = upnp.uPnPThread()
# upnpThread.start()
# else:
# # Populate with hardcoded value (same as connectToStream above)
# state.streamsInWhichIAmParticipating.append(1)
# if not daemon and state.enableGUI:
# if state.curses:
# if not depends.check_curses():
# sys.exit()
# print('Running with curses')
# import bitmessagecurses
# bitmessagecurses.runwrapper()
# config.remove_option('bitmessagesettings', 'dontconnect')
# pylint: disable=no-member,import-error,no-name-in-module,relative-import
from pybitmessage.mpybit import NavigateApp
state.kivyapp = NavigateApp()
print('NavigateApp() ----------------------')
state.kivyapp.run()
print('state.kivyapp.run() ----------------------')
# else:
# config.remove_option('bitmessagesettings', 'dontconnect')
# if daemon:
# while state.shutdown == 0:
# time.sleep(1)
# if (
# state.testmode
# and time.time() - state.last_api_response >= 30
# ):
# self.stop()
# elif not state.enableGUI:
# state.enableGUI = True
# # pylint: disable=relative-import
# from tests import core as test_core
# test_core_result = test_core.run()
# state.enableGUI = True
# self.stop()
# test_core.cleanup()
# sys.exit(
# 'Core tests failed!'
# if test_core_result.errors or test_core_result.failures
# else 0
# )
# @staticmethod
# def daemonize():
# """Running as a daemon. Send signal in end."""
# grandfatherPid = os.getpid()
# parentPid = None
# try:
# if os.fork():
# # unlock
# state.thisapp.cleanup()
# # wait until grandchild ready
# while True:
# time.sleep(1)
# os._exit(0) # pylint: disable=protected-access
# except AttributeError:
# # fork not implemented
# pass
# else:
# parentPid = os.getpid()
# state.thisapp.lock() # relock
# os.umask(0)
# try:
# os.setsid()
# except AttributeError:
# # setsid not implemented
# pass
# try:
# if os.fork():
# # unlock
# state.thisapp.cleanup()
# # wait until child ready
# while True:
# time.sleep(1)
# os._exit(0) # pylint: disable=protected-access
# except AttributeError:
# # fork not implemented
# pass
# else:
# state.thisapp.lock() # relock
# state.thisapp.lockPid = None # indicate we're the final child
# sys.stdout.flush()
# sys.stderr.flush()
# if not sys.platform.startswith('win'):
# si = file(os.devnull, 'r')
# so = file(os.devnull, 'a+')
# se = file(os.devnull, 'a+', 0)
# os.dup2(si.fileno(), sys.stdin.fileno())
# os.dup2(so.fileno(), sys.stdout.fileno())
# os.dup2(se.fileno(), sys.stderr.fileno())
# if parentPid:
# # signal ready
# os.kill(parentPid, signal.SIGTERM)
# os.kill(grandfatherPid, signal.SIGTERM)
# @staticmethod
# def setSignalHandler():
# """Setting the Signal Handler"""
# signal.signal(signal.SIGINT, signal_handler)
# signal.signal(signal.SIGTERM, signal_handler)
# # signal.signal(signal.SIGINT, signal.SIG_DFL)
# @staticmethod
# def usage():
# """Displaying the usages"""
# print('Usage: ' + sys.argv[0] + ' [OPTIONS]')
# print('''
# Options:
# -h, --help show this help message and exit
# -c, --curses use curses (text mode) interface
# -d, --daemon run in daemon (background) mode
# -t, --test dryrun, make testing
# All parameters are optional.
# ''')
# @staticmethod
# def stop():
# """Stop main application"""
# with printLock:
# print('Stopping Bitmessage Deamon.')
# shutdown.doCleanShutdown()
# # .. todo:: nice function but no one is using this
# @staticmethod
# def getApiAddress():
# """This function returns API address and port"""
# if not BMConfigParser().safeGetBoolean(
# 'bitmessagesettings', 'apienabled'):
# return None
# address = BMConfigParser().get('bitmessagesettings', 'apiinterface')
# port = BMConfigParser().getint('bitmessagesettings', 'apiport')
# return {'address': address, 'port': port}
# def start_proxyconfig():
# """Check socksproxytype and start any proxy configuration plugin"""
# if not get_plugin:
# return
# config = BMConfigParser()
# proxy_type = config.safeGet('bitmessagesettings', 'socksproxytype')
# if proxy_type and proxy_type not in ('none', 'SOCKS4a', 'SOCKS5'):
# try:
# proxyconfig_start = time.time()
# if not get_plugin('proxyconfig', name=proxy_type)(config):
# raise TypeError()
# except TypeError:
# # cannot import shutdown here ):
# logger.error(
# 'Failed to run proxy config plugin %s',
# proxy_type, exc_info=True)
# os._exit(0) # pylint: disable=protected-access
# else:
# logger.info(
# 'Started proxy config plugin %s in %s sec',
# proxy_type, time.time() - proxyconfig_start)
# class AnnounceThread(StoppableThread):
# """A thread to manage regular announcing of this node"""
# name = "Announcer"
# def run(self):
# lastSelfAnnounced = 0
# while not self._stopped and state.shutdown == 0:
# processed = 0
# if lastSelfAnnounced < time.time() - UDPSocket.announceInterval:
# self.announceSelf()
# lastSelfAnnounced = time.time()
# if processed == 0:
# self.stop.wait(10)
# @staticmethod
# def announceSelf():
# """Announce our presence"""
# for connection in [udpSockets for udpSockets in BMConnectionPool().udpSockets.values()]:
# if not connection.announcing:
# continue
# for stream in state.streamsInWhichIAmParticipating:
# addr = (
# stream,
# # state.Peer('127.0.0.1',int( BMConfigParser().safeGet("bitmessagesettings", "port"))),
# # int(time.time()))
# # connection.append_write_buf(BMProto.assembleAddr([addr]))
# Peer(
# '127.0.0.1',
# BMConfigParser().safeGetInt(
# 'bitmessagesettings', 'port')),
# time.time())
# connection.append_write_buf(assemble_addr([addr]))
def main():
"""Triggers main module"""
mainprogram = MockMain()
mainprogram.start()
if __name__ == "__main__":
main()

View File

@ -0,0 +1,304 @@
"""
Bitmessage mock
"""
from pybitmessage.class_addressGenerator import addressGenerator
from pybitmessage.class_singleWorker import singleWorker
from pybitmessage.class_objectProcessor import objectProcessor
from pybitmessage.inventory import Inventory
from pybitmessage.bmconfigparser import BMConfigParser
from pybitmessage.class_singleCleaner import singleCleaner
from pybitmessage import state
from pybitmessage.network.threads import StoppableThread
# from pybitmessage.network.connectionpool import BMConnectionPool
# from pybitmessage.network.networkthread import BMNetworkThread
# from pybitmessage.network.receivequeuethread import ReceiveQueueThread
# pylint: disable=too-few-public-methods,no-init,old-style-class
class MockMain:
"""Mock main function"""
# pylint: disable=no-self-use
def start(self):
"""Start main application"""
# pylint: disable=too-many-statements,too-many-branches,too-many-locals, unused-variable
config = BMConfigParser()
daemon = config.safeGetBoolean('bitmessagesettings', 'daemon')
# Start the address generation thread
addressGeneratorThread = addressGenerator()
# close the main program even if there are threads left
addressGeneratorThread.daemon = True
addressGeneratorThread.start()
# Start the thread that calculates POWs
singleWorkerThread = singleWorker()
# close the main program even if there are threads left
singleWorkerThread.daemon = True
singleWorkerThread.start()
# Start the thread that calculates POWs
objectProcessorThread = objectProcessor()
# DON'T close the main program even the thread remains.
# This thread checks the shutdown variable after processing
# each object.
objectProcessorThread.daemon = False
objectProcessorThread.start()
Inventory() # init
# # Start the cleanerThread
singleCleanerThread = singleCleaner()
# # close the main program even if there are threads left
singleCleanerThread.daemon = True
singleCleanerThread.start()
# Not needed if objproc disabled
# if state.enableObjProc:
# shared.reloadMyAddressHashes()
# shared.reloadBroadcastSendersForWhichImWatching()
# API is also objproc dependent
# if config.safeGetBoolean('bitmessagesettings', 'apienabled'):
# # pylint: disable=relative-import
# from pybitmessage import api
# singleAPIThread = api.singleAPI()
# # close the main program even if there are threads left
# singleAPIThread.daemon = True
# singleAPIThread.start()
# # start network components if networking is enabled
# if state.enableNetwork:
# # start_proxyconfig()
# # BMConnectionPool().connectToStream(1)
# asyncoreThread = BMNetworkThread()
# asyncoreThread.daemon = True
# asyncoreThread.start()
# for i in range(config.safeGet('threads', 'receive')):
# receiveQueueThread = ReceiveQueueThread(i)
# receiveQueueThread.daemon = True
# receiveQueueThread.start()
# announceThread = AnnounceThread()
# announceThread.daemon = True
# announceThread.start()
# state.invThread = InvThread()
# state.invThread.daemon = True
# state.invThread.start()
# state.addrThread = AddrThread()
# state.addrThread.daemon = True
# state.addrThread.start()
# state.downloadThread = DownloadThread()
# state.downloadThread.daemon = True
# state.downloadThread.start()
# state.uploadThread = UploadThread()
# state.uploadThread.daemon = True
# state.uploadThread.start()
# if config.safeGetBoolean('bitmessagesettings', 'upnp'):
# import upnp
# upnpThread = upnp.uPnPThread()
# upnpThread.start()
# else:
# # Populate with hardcoded value (same as connectToStream above)
# state.streamsInWhichIAmParticipating.append(1)
# if not daemon and state.enableGUI:
# if state.curses:
# if not depends.check_curses():
# sys.exit()
# print('Running with curses')
# import bitmessagecurses
# bitmessagecurses.runwrapper()
# config.remove_option('bitmessagesettings', 'dontconnect')
# pylint: disable=no-member,import-error,no-name-in-module,relative-import
from pybitmessage.mpybit import NavigateApp
state.kivyapp = NavigateApp()
print('NavigateApp() ----------------------')
state.kivyapp.run()
print('state.kivyapp.run() ----------------------')
# else:
# config.remove_option('bitmessagesettings', 'dontconnect')
# if daemon:
# while state.shutdown == 0:
# time.sleep(1)
# if (
# state.testmode
# and time.time() - state.last_api_response >= 30
# ):
# self.stop()
# elif not state.enableGUI:
# state.enableGUI = True
# # pylint: disable=relative-import
# from tests import core as test_core
# test_core_result = test_core.run()
# state.enableGUI = True
# self.stop()
# test_core.cleanup()
# sys.exit(
# 'Core tests failed!'
# if test_core_result.errors or test_core_result.failures
# else 0
# )
# @staticmethod
# def daemonize():
# """Running as a daemon. Send signal in end."""
# grandfatherPid = os.getpid()
# parentPid = None
# try:
# if os.fork():
# # unlock
# state.thisapp.cleanup()
# # wait until grandchild ready
# while True:
# time.sleep(1)
# os._exit(0) # pylint: disable=protected-access
# except AttributeError:
# # fork not implemented
# pass
# else:
# parentPid = os.getpid()
# state.thisapp.lock() # relock
# os.umask(0)
# try:
# os.setsid()
# except AttributeError:
# # setsid not implemented
# pass
# try:
# if os.fork():
# # unlock
# state.thisapp.cleanup()
# # wait until child ready
# while True:
# time.sleep(1)
# os._exit(0) # pylint: disable=protected-access
# except AttributeError:
# # fork not implemented
# pass
# else:
# state.thisapp.lock() # relock
# state.thisapp.lockPid = None # indicate we're the final child
# sys.stdout.flush()
# sys.stderr.flush()
# if not sys.platform.startswith('win'):
# si = file(os.devnull, 'r')
# so = file(os.devnull, 'a+')
# se = file(os.devnull, 'a+', 0)
# os.dup2(si.fileno(), sys.stdin.fileno())
# os.dup2(so.fileno(), sys.stdout.fileno())
# os.dup2(se.fileno(), sys.stderr.fileno())
# if parentPid:
# # signal ready
# os.kill(parentPid, signal.SIGTERM)
# os.kill(grandfatherPid, signal.SIGTERM)
# @staticmethod
# def setSignalHandler():
# """Setting the Signal Handler"""
# signal.signal(signal.SIGINT, signal_handler)
# signal.signal(signal.SIGTERM, signal_handler)
# # signal.signal(signal.SIGINT, signal.SIG_DFL)
# @staticmethod
# def usage():
# """Displaying the usages"""
# print('Usage: ' + sys.argv[0] + ' [OPTIONS]')
# print('''
# Options:
# -h, --help show this help message and exit
# -c, --curses use curses (text mode) interface
# -d, --daemon run in daemon (background) mode
# -t, --test dryrun, make testing
# All parameters are optional.
# ''')
# @staticmethod
# def stop():
# """Stop main application"""
# with printLock:
# print('Stopping Bitmessage Deamon.')
# shutdown.doCleanShutdown()
# # .. todo:: nice function but no one is using this
# @staticmethod
# def getApiAddress():
# """This function returns API address and port"""
# if not BMConfigParser().safeGetBoolean(
# 'bitmessagesettings', 'apienabled'):
# return None
# address = BMConfigParser().get('bitmessagesettings', 'apiinterface')
# port = BMConfigParser().getint('bitmessagesettings', 'apiport')
# return {'address': address, 'port': port}
# def start_proxyconfig():
# """Check socksproxytype and start any proxy configuration plugin"""
# if not get_plugin:
# return
# config = BMConfigParser()
# proxy_type = config.safeGet('bitmessagesettings', 'socksproxytype')
# if proxy_type and proxy_type not in ('none', 'SOCKS4a', 'SOCKS5'):
# try:
# proxyconfig_start = time.time()
# if not get_plugin('proxyconfig', name=proxy_type)(config):
# raise TypeError()
# except TypeError:
# # cannot import shutdown here ):
# logger.error(
# 'Failed to run proxy config plugin %s',
# proxy_type, exc_info=True)
# os._exit(0) # pylint: disable=protected-access
# else:
# logger.info(
# 'Started proxy config plugin %s in %s sec',
# proxy_type, time.time() - proxyconfig_start)
# class AnnounceThread(StoppableThread):
# """A thread to manage regular announcing of this node"""
# name = "Announcer"
# def run(self):
# lastSelfAnnounced = 0
# while not self._stopped and state.shutdown == 0:
# processed = 0
# if lastSelfAnnounced < time.time() - UDPSocket.announceInterval:
# self.announceSelf()
# lastSelfAnnounced = time.time()
# if processed == 0:
# self.stop.wait(10)
# @staticmethod
# def announceSelf():
# """Announce our presence"""
# for connection in [udpSockets for udpSockets in BMConnectionPool().udpSockets.values()]:
# if not connection.announcing:
# continue
# for stream in state.streamsInWhichIAmParticipating:
# addr = (
# stream,
# # state.Peer('127.0.0.1',int( BMConfigParser().safeGet("bitmessagesettings", "port"))),
# # int(time.time()))
# # connection.append_write_buf(BMProto.assembleAddr([addr]))
# Peer(
# '127.0.0.1',
# BMConfigParser().safeGetInt(
# 'bitmessagesettings', 'port')),
# time.time())
# connection.append_write_buf(assemble_addr([addr]))
def main():
"""Triggers main module"""
mainprogram = MockMain()
mainprogram.start()
if __name__ == "__main__":
main()

View File

@ -0,0 +1,30 @@
# """Mock kivy app with mock threads."""
# from pybitmessage import state
# from pybitmessage.mpybit import NavigateApp
# from pybitmessage.class_addressGenerator import addressGenerator
# def main():
# """main method for starting threads"""
# # Start the address generation thread
# addressGeneratorThread = addressGenerator()
# # close the main program even if there are threads left
# addressGeneratorThread.daemon = True
# addressGeneratorThread.start()
# state.kivyapp = NavigateApp()
# state.kivyapp.run()
# if __name__ == '__main__':
# main()
"""This module is for thread start."""
from pybitmessage import state
if __name__ == '__main__':
state.kivy = True
print("Kivy Loading......")
from bitmessagemock import main
main()

View File

View File

@ -0,0 +1,1504 @@
"""
This is not what you run to run the Bitmessage API. Instead, enable the API
( https://bitmessage.org/wiki/API ) and optionally enable daemon mode
( https://bitmessage.org/wiki/Daemon ) then run bitmessagemain.py.
"""
# Copyright (c) 2012-2016 Jonathan Warren
# Copyright (c) 2012-2020 The Bitmessage developers
# pylint: disable=too-many-lines,no-self-use,unused-variable,unused-argument
import base64
import errno
import hashlib
import json
import random # nosec
import socket
import subprocess
import time
from binascii import hexlify, unhexlify
from xmlrpc.server import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler, \
SimpleXMLRPCServer
from struct import pack
import defaults
import helper_inbox
import helper_sent
import network.stats
import proofofwork
import queues
import shared
import shutdown
import state
import threads
from addresses import (
addBMIfNotPresent,
calculateInventoryHash,
decodeAddress,
decodeVarint,
varintDecodeError
)
from bmconfigparser import BMConfigParser
from debug import logger
from helper_ackPayload import genAckPayload
from helper_sql import SqlBulkExecute, sqlExecute, sqlQuery, sqlStoredProcedure
from inventory import Inventory
from network.threads import StoppableThread
from version import softwareVersion
str_chan = '[chan]'
class APIError(Exception):
"""APIError exception class"""
def __init__(self, error_number, error_message):
super(APIError, self).__init__()
self.error_number = error_number
self.error_message = error_message
def __str__(self):
return "API Error %04i: %s" % (self.error_number, self.error_message)
class StoppableXMLRPCServer(SimpleXMLRPCServer):
"""A SimpleXMLRPCServer that honours state.shutdown"""
# pylint:disable=too-few-public-methods
allow_reuse_address = True
def serve_forever(self):
"""Start the SimpleXMLRPCServer"""
# pylint: disable=arguments-differ
while state.shutdown == 0:
self.handle_request()
# This thread, of which there is only one, runs the API.
class singleAPI(StoppableThread):
"""API thread"""
name = "singleAPI"
def stopThread(self):
super(singleAPI, self).stopThread()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect((
BMConfigParser().get('bitmessagesettings', 'apiinterface'),
BMConfigParser().getint('bitmessagesettings', 'apiport')
))
s.shutdown(socket.SHUT_RDWR)
s.close()
except BaseException:
pass
def run(self):
port = BMConfigParser().getint('bitmessagesettings', 'apiport')
try:
getattr(errno, 'WSAEADDRINUSE')
except AttributeError:
errno.WSAEADDRINUSE = errno.EADDRINUSE
for attempt in range(50):
try:
if attempt > 0:
logger.warning(
'Failed to start API listener on port %s', port)
port = random.randint(32767, 65535)
se = StoppableXMLRPCServer(
(BMConfigParser().get(
'bitmessagesettings', 'apiinterface'),
port),
MySimpleXMLRPCRequestHandler, True, True)
except socket.error as e:
if e.errno in (errno.EADDRINUSE, errno.WSAEADDRINUSE):
continue
else:
if attempt > 0:
logger.warning('Setting apiport to %s', port)
BMConfigParser().set(
'bitmessagesettings', 'apiport', str(port))
BMConfigParser().save()
break
# se.register_introspection_functions()
# apiNotifyPath = BMConfigParser().safeGet(
# 'bitmessagesettings', 'apinotifypath')
# if apiNotifyPath:
# logger.info('Trying to call %s', apiNotifyPath)
# try:
# subprocess.call([apiNotifyPath, "startingUp"])
# except OSError:
# logger.warning(
# 'Failed to call %s, removing apinotifypath setting',
# apiNotifyPath)
# BMConfigParser().remove_option(
# 'bitmessagesettings', 'apinotifypath')
# se.serve_forever()
class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
"""
This is one of several classes that constitute the API
This class was written by Vaibhav Bhatia.
Modified by Jonathan Warren (Atheros).
http://code.activestate.com/recipes/501148-xmlrpc-serverclient-which-does-cookie-handling-and/
"""
# pylint: disable=too-many-public-methods
def do_POST(self):
"""
Handles the HTTP POST request.
Attempts to interpret all HTTP POST requests as XML-RPC calls,
which are forwarded to the server's _dispatch method for handling.
Note: this method is the same as in SimpleXMLRPCRequestHandler,
just hacked to handle cookies
"""
# Check that the path is legal
if not self.is_rpc_path_valid():
self.report_404()
return
try:
# Get arguments by reading body of request.
# We read this in chunks to avoid straining
# socket.read(); around the 10 or 15Mb mark, some platforms
# begin to have problems (bug #792570).
max_chunk_size = 10 * 1024 * 1024
size_remaining = int(self.headers["content-length"])
L = []
while size_remaining:
chunk_size = min(size_remaining, max_chunk_size)
L.append(self.rfile.read(chunk_size))
size_remaining -= len(L[-1])
data = ''.join(L)
# In previous versions of SimpleXMLRPCServer, _dispatch
# could be overridden in this class, instead of in
# SimpleXMLRPCDispatcher. To maintain backwards compatibility,
# check to see if a subclass implements _dispatch and dispatch
# using that method if present.
# pylint: disable=protected-access
response = self.server._marshaled_dispatch(
data, getattr(self, '_dispatch', None)
)
# This should only happen if the module is buggy
except BaseException:
# internal error, report as HTTP server error
self.send_response(500)
self.end_headers()
else:
# got a valid XML RPC response
self.send_response(200)
self.send_header("Content-type", "text/xml")
self.send_header("Content-length", str(len(response)))
# HACK :start -> sends cookies here
if self.cookies:
for cookie in self.cookies:
self.send_header('Set-Cookie', cookie.output(header=''))
# HACK :end
self.end_headers()
self.wfile.write(response)
# shut down the connection
self.wfile.flush()
self.connection.shutdown(1)
# actually handle shutdown command after sending response
if state.shutdown is False:
shutdown.doCleanShutdown()
def APIAuthenticateClient(self):
"""Predicate to check for valid API credentials in the request header"""
if 'Authorization' in self.headers:
# handle Basic authentication
_, encstr = self.headers.get('Authorization').split()
emailid, password = encstr.decode('base64').split(':')
return (
emailid == BMConfigParser().get(
'bitmessagesettings', 'apiusername') and
password == BMConfigParser().get(
'bitmessagesettings', 'apipassword')
)
else:
logger.warning(
'Authentication failed because header lacks'
' Authentication field')
time.sleep(2)
return False
def _decode(self, text, decode_type):
try:
if decode_type == 'hex':
return unhexlify(text)
elif decode_type == 'base64':
return base64.b64decode(text)
except Exception as e:
raise APIError(
22, "Decode error - %s. Had trouble while decoding string: %r"
% (e, text)
)
return None
def _verifyAddress(self, address):
status, addressVersionNumber, streamNumber, ripe = \
decodeAddress(address)
if status != 'success':
logger.warning(
'API Error 0007: Could not decode address %s. Status: %s.',
address, status
)
if status == 'checksumfailed':
raise APIError(8, 'Checksum failed for address: ' + address)
if status == 'invalidcharacters':
raise APIError(9, 'Invalid characters in address: ' + address)
if status == 'versiontoohigh':
raise APIError(
10,
'Address version number too high (or zero) in address: ' +
address)
if status == 'varintmalformed':
raise APIError(26, 'Malformed varint in address: ' + address)
raise APIError(
7, 'Could not decode address: %s : %s' % (address, status))
if addressVersionNumber < 2 or addressVersionNumber > 4:
raise APIError(
11, 'The address version number currently must be 2, 3 or 4.'
' Others aren\'t supported. Check the address.'
)
if streamNumber != 1:
raise APIError(
12, 'The stream number must be 1. Others aren\'t supported.'
' Check the address.'
)
return (status, addressVersionNumber, streamNumber, ripe)
# Request Handlers
def HandleListAddresses(self, method):
"""Handle a request to list addresses"""
data = '{"addresses":['
for addressInKeysFile in BMConfigParser().addresses():
status, addressVersionNumber, streamNumber, hash01 = decodeAddress(
addressInKeysFile)
if len(data) > 20:
data += ','
if BMConfigParser().has_option(addressInKeysFile, 'chan'):
chan = BMConfigParser().getboolean(addressInKeysFile, 'chan')
else:
chan = False
label = BMConfigParser().get(addressInKeysFile, 'label')
if method == 'listAddresses2':
label = base64.b64encode(label)
data += json.dumps({
'label': label,
'address': addressInKeysFile,
'stream': streamNumber,
'enabled':
BMConfigParser().getboolean(addressInKeysFile, 'enabled'),
'chan': chan
}, indent=4, separators=(',', ': '))
data += ']}'
return data
def HandleListAddressBookEntries(self, params):
"""Handle a request to list address book entries"""
if len(params) == 1:
label, = params
label = self._decode(label, "base64")
queryreturn = sqlQuery(
"SELECT label, address from addressbook WHERE label = ?",
label)
elif len(params) > 1:
raise APIError(0, "Too many paremeters, max 1")
else:
queryreturn = sqlQuery("SELECT label, address from addressbook")
data = '{"addresses":['
for row in queryreturn:
label, address = row
label = shared.fixPotentiallyInvalidUTF8Data(label)
if len(data) > 20:
data += ','
data += json.dumps({
'label': base64.b64encode(label),
'address': address}, indent=4, separators=(',', ': '))
data += ']}'
return data
def HandleAddAddressBookEntry(self, params):
"""Handle a request to add an address book entry"""
if len(params) != 2:
raise APIError(0, "I need label and address")
address, label = params
label = self._decode(label, "base64")
address = addBMIfNotPresent(address)
self._verifyAddress(address)
queryreturn = sqlQuery(
"SELECT address FROM addressbook WHERE address=?", address)
if queryreturn != []:
raise APIError(
16, 'You already have this address in your address book.')
sqlExecute("INSERT INTO addressbook VALUES(?,?)", label, address)
queues.UISignalQueue.put(('rerenderMessagelistFromLabels', ''))
queues.UISignalQueue.put(('rerenderMessagelistToLabels', ''))
queues.UISignalQueue.put(('rerenderAddressBook', ''))
return "Added address %s to address book" % address
def HandleDeleteAddressBookEntry(self, params):
"""Handle a request to delete an address book entry"""
if len(params) != 1:
raise APIError(0, "I need an address")
address, = params
address = addBMIfNotPresent(address)
self._verifyAddress(address)
sqlExecute('DELETE FROM addressbook WHERE address=?', address)
queues.UISignalQueue.put(('rerenderMessagelistFromLabels', ''))
queues.UISignalQueue.put(('rerenderMessagelistToLabels', ''))
queues.UISignalQueue.put(('rerenderAddressBook', ''))
return "Deleted address book entry for %s if it existed" % address
def HandleCreateRandomAddress(self, params):
"""Handle a request to create a random address"""
if not params:
raise APIError(0, 'I need parameters!')
elif len(params) == 1:
label, = params
eighteenByteRipe = False
nonceTrialsPerByte = BMConfigParser().get(
'bitmessagesettings', 'defaultnoncetrialsperbyte')
payloadLengthExtraBytes = BMConfigParser().get(
'bitmessagesettings', 'defaultpayloadlengthextrabytes')
elif len(params) == 2:
label, eighteenByteRipe = params
nonceTrialsPerByte = BMConfigParser().get(
'bitmessagesettings', 'defaultnoncetrialsperbyte')
payloadLengthExtraBytes = BMConfigParser().get(
'bitmessagesettings', 'defaultpayloadlengthextrabytes')
elif len(params) == 3:
label, eighteenByteRipe, totalDifficulty = params
nonceTrialsPerByte = int(
defaults.networkDefaultProofOfWorkNonceTrialsPerByte *
totalDifficulty)
payloadLengthExtraBytes = BMConfigParser().get(
'bitmessagesettings', 'defaultpayloadlengthextrabytes')
elif len(params) == 4:
label, eighteenByteRipe, totalDifficulty, \
smallMessageDifficulty = params
nonceTrialsPerByte = int(
defaults.networkDefaultProofOfWorkNonceTrialsPerByte *
totalDifficulty)
payloadLengthExtraBytes = int(
defaults.networkDefaultPayloadLengthExtraBytes *
smallMessageDifficulty)
else:
raise APIError(0, 'Too many parameters!')
label = self._decode(label, "base64")
try:
unicode(label, 'utf-8')
except BaseException:
raise APIError(17, 'Label is not valid UTF-8 data.')
queues.apiAddressGeneratorReturnQueue.queue.clear()
streamNumberForAddress = 1
queues.addressGeneratorQueue.put((
'createRandomAddress', 4, streamNumberForAddress, label, 1, "",
eighteenByteRipe, nonceTrialsPerByte, payloadLengthExtraBytes
))
return queues.apiAddressGeneratorReturnQueue.get()
def HandleCreateDeterministicAddresses(self, params):
"""Handle a request to create a deterministic address"""
# pylint: disable=too-many-branches, too-many-statements
if not params:
raise APIError(0, 'I need parameters!')
elif len(params) == 1:
passphrase, = params
numberOfAddresses = 1
addressVersionNumber = 0
streamNumber = 0
eighteenByteRipe = False
nonceTrialsPerByte = BMConfigParser().get(
'bitmessagesettings', 'defaultnoncetrialsperbyte')
payloadLengthExtraBytes = BMConfigParser().get(
'bitmessagesettings', 'defaultpayloadlengthextrabytes')
elif len(params) == 2:
passphrase, numberOfAddresses = params
addressVersionNumber = 0
streamNumber = 0
eighteenByteRipe = False
nonceTrialsPerByte = BMConfigParser().get(
'bitmessagesettings', 'defaultnoncetrialsperbyte')
payloadLengthExtraBytes = BMConfigParser().get(
'bitmessagesettings', 'defaultpayloadlengthextrabytes')
elif len(params) == 3:
passphrase, numberOfAddresses, addressVersionNumber = params
streamNumber = 0
eighteenByteRipe = False
nonceTrialsPerByte = BMConfigParser().get(
'bitmessagesettings', 'defaultnoncetrialsperbyte')
payloadLengthExtraBytes = BMConfigParser().get(
'bitmessagesettings', 'defaultpayloadlengthextrabytes')
elif len(params) == 4:
passphrase, numberOfAddresses, addressVersionNumber, \
streamNumber = params
eighteenByteRipe = False
nonceTrialsPerByte = BMConfigParser().get(
'bitmessagesettings', 'defaultnoncetrialsperbyte')
payloadLengthExtraBytes = BMConfigParser().get(
'bitmessagesettings', 'defaultpayloadlengthextrabytes')
elif len(params) == 5:
passphrase, numberOfAddresses, addressVersionNumber, \
streamNumber, eighteenByteRipe = params
nonceTrialsPerByte = BMConfigParser().get(
'bitmessagesettings', 'defaultnoncetrialsperbyte')
payloadLengthExtraBytes = BMConfigParser().get(
'bitmessagesettings', 'defaultpayloadlengthextrabytes')
elif len(params) == 6:
passphrase, numberOfAddresses, addressVersionNumber, \
streamNumber, eighteenByteRipe, totalDifficulty = params
nonceTrialsPerByte = int(
defaults.networkDefaultProofOfWorkNonceTrialsPerByte *
totalDifficulty)
payloadLengthExtraBytes = BMConfigParser().get(
'bitmessagesettings', 'defaultpayloadlengthextrabytes')
elif len(params) == 7:
passphrase, numberOfAddresses, addressVersionNumber, \
streamNumber, eighteenByteRipe, totalDifficulty, \
smallMessageDifficulty = params
nonceTrialsPerByte = int(
defaults.networkDefaultProofOfWorkNonceTrialsPerByte *
totalDifficulty)
payloadLengthExtraBytes = int(
defaults.networkDefaultPayloadLengthExtraBytes *
smallMessageDifficulty)
else:
raise APIError(0, 'Too many parameters!')
if not passphrase:
raise APIError(1, 'The specified passphrase is blank.')
if not isinstance(eighteenByteRipe, bool):
raise APIError(
23, 'Bool expected in eighteenByteRipe, saw %s instead' %
type(eighteenByteRipe))
passphrase = self._decode(passphrase, "base64")
# 0 means "just use the proper addressVersionNumber"
if addressVersionNumber == 0:
addressVersionNumber = 4
# if addressVersionNumber != 3 and addressVersionNumber != 4:
if addressVersionNumber not in (3, 4):
raise APIError(
2, 'The address version number currently must be 3, 4, or 0'
' (which means auto-select). %i isn\'t supported.' %
addressVersionNumber)
if streamNumber == 0: # 0 means "just use the most available stream"
streamNumber = 1
if streamNumber != 1:
raise APIError(
3, 'The stream number must be 1 (or 0 which means'
' auto-select). Others aren\'t supported.')
if numberOfAddresses == 0:
raise APIError(
4, 'Why would you ask me to generate 0 addresses for you?')
if numberOfAddresses > 999:
raise APIError(
5, 'You have (accidentally?) specified too many addresses to'
' make. Maximum 999. This check only exists to prevent'
' mischief; if you really want to create more addresses than'
' this, contact the Bitmessage developers and we can modify'
' the check or you can do it yourself by searching the source'
' code for this message.')
queues.apiAddressGeneratorReturnQueue.queue.clear()
logger.debug(
'Requesting that the addressGenerator create %s addresses.',
numberOfAddresses)
queues.addressGeneratorQueue.put((
'createDeterministicAddresses', addressVersionNumber, streamNumber,
'unused API address', numberOfAddresses, passphrase,
eighteenByteRipe, nonceTrialsPerByte, payloadLengthExtraBytes
))
data = '{"addresses":['
queueReturn = queues.apiAddressGeneratorReturnQueue.get()
for item in queueReturn:
if len(data) > 20:
data += ','
data += "\"" + item + "\""
data += ']}'
return data
def HandleGetDeterministicAddress(self, params):
"""Handle a request to get a deterministic address"""
if len(params) != 3:
raise APIError(0, 'I need exactly 3 parameters.')
passphrase, addressVersionNumber, streamNumber = params
numberOfAddresses = 1
eighteenByteRipe = False
if not passphrase:
raise APIError(1, 'The specified passphrase is blank.')
passphrase = self._decode(passphrase, "base64")
# if addressVersionNumber != 3 and addressVersionNumber != 4:
if addressVersionNumber not in (3, 4):
raise APIError(
2, 'The address version number currently must be 3 or 4. %i'
' isn\'t supported.' % addressVersionNumber)
if streamNumber != 1:
raise APIError(
3, ' The stream number must be 1. Others aren\'t supported.')
queues.apiAddressGeneratorReturnQueue.queue.clear()
logger.debug(
'Requesting that the addressGenerator create %s addresses.',
numberOfAddresses)
queues.addressGeneratorQueue.put((
'getDeterministicAddress', addressVersionNumber, streamNumber,
'unused API address', numberOfAddresses, passphrase,
eighteenByteRipe
))
return queues.apiAddressGeneratorReturnQueue.get()
def HandleCreateChan(self, params):
"""Handle a request to create a chan"""
if not params:
raise APIError(0, 'I need parameters.')
elif len(params) == 1:
passphrase, = params
passphrase = self._decode(passphrase, "base64")
if not passphrase:
raise APIError(1, 'The specified passphrase is blank.')
# It would be nice to make the label the passphrase but it is
# possible that the passphrase contains non-utf-8 characters.
try:
unicode(passphrase, 'utf-8')
label = str_chan + ' ' + passphrase
except BaseException:
label = str_chan + ' ' + repr(passphrase)
addressVersionNumber = 4
streamNumber = 1
queues.apiAddressGeneratorReturnQueue.queue.clear()
logger.debug(
'Requesting that the addressGenerator create chan %s.', passphrase)
queues.addressGeneratorQueue.put((
'createChan', addressVersionNumber, streamNumber, label,
passphrase, True
))
queueReturn = queues.apiAddressGeneratorReturnQueue.get()
if not queueReturn:
raise APIError(24, 'Chan address is already present.')
address = queueReturn[0]
return address
def HandleJoinChan(self, params):
"""Handle a request to join a chan"""
if len(params) < 2:
raise APIError(0, 'I need two parameters.')
elif len(params) == 2:
passphrase, suppliedAddress = params
passphrase = self._decode(passphrase, "base64")
if not passphrase:
raise APIError(1, 'The specified passphrase is blank.')
# It would be nice to make the label the passphrase but it is
# possible that the passphrase contains non-utf-8 characters.
try:
unicode(passphrase, 'utf-8')
label = str_chan + ' ' + passphrase
except BaseException:
label = str_chan + ' ' + repr(passphrase)
status, addressVersionNumber, streamNumber, toRipe = (
self._verifyAddress(suppliedAddress))
suppliedAddress = addBMIfNotPresent(suppliedAddress)
queues.apiAddressGeneratorReturnQueue.queue.clear()
queues.addressGeneratorQueue.put((
'joinChan', suppliedAddress, label, passphrase, True
))
addressGeneratorReturnValue = \
queues.apiAddressGeneratorReturnQueue.get()
if addressGeneratorReturnValue[0] == \
'chan name does not match address':
raise APIError(18, 'Chan name does not match address.')
if not addressGeneratorReturnValue:
raise APIError(24, 'Chan address is already present.')
return "success"
def HandleLeaveChan(self, params):
"""Handle a request to leave a chan"""
if not params:
raise APIError(0, 'I need parameters.')
elif len(params) == 1:
address, = params
status, addressVersionNumber, streamNumber, toRipe = (
self._verifyAddress(address))
address = addBMIfNotPresent(address)
if not BMConfigParser().has_section(address):
raise APIError(
13, 'Could not find this address in your keys.dat file.')
if not BMConfigParser().safeGetBoolean(address, 'chan'):
raise APIError(
25, 'Specified address is not a chan address.'
' Use deleteAddress API call instead.')
BMConfigParser().remove_section(address)
with open(state.appdata + 'keys.dat', 'wb') as configfile:
BMConfigParser().write(configfile)
return 'success'
def HandleDeleteAddress(self, params):
"""Handle a request to delete an address"""
if not params:
raise APIError(0, 'I need parameters.')
elif len(params) == 1:
address, = params
status, addressVersionNumber, streamNumber, toRipe = (
self._verifyAddress(address))
address = addBMIfNotPresent(address)
if not BMConfigParser().has_section(address):
raise APIError(
13, 'Could not find this address in your keys.dat file.')
BMConfigParser().remove_section(address)
with open(state.appdata + 'keys.dat', 'wb') as configfile:
BMConfigParser().write(configfile)
queues.UISignalQueue.put(('writeNewAddressToTable', ('', '', '')))
shared.reloadMyAddressHashes()
return 'success'
def HandleGetAllInboxMessages(self, params):
"""Handle a request to get all inbox messages"""
queryreturn = sqlQuery(
"SELECT msgid, toaddress, fromaddress, subject, received, message,"
" encodingtype, read FROM inbox where folder='inbox'"
" ORDER BY received"
)
data = '{"inboxMessages":['
for row in queryreturn:
msgid, toAddress, fromAddress, subject, received, message, \
encodingtype, read = row
subject = shared.fixPotentiallyInvalidUTF8Data(subject)
message = shared.fixPotentiallyInvalidUTF8Data(message)
if len(data) > 25:
data += ','
data += json.dumps({
'msgid': hexlify(msgid),
'toAddress': toAddress,
'fromAddress': fromAddress,
'subject': base64.b64encode(subject),
'message': base64.b64encode(message),
'encodingType': encodingtype,
'receivedTime': received,
'read': read}, indent=4, separators=(',', ': '))
data += ']}'
return data
def HandleGetAllInboxMessageIds(self, params):
"""Handle a request to get all inbox message IDs"""
queryreturn = sqlQuery(
"SELECT msgid FROM inbox where folder='inbox' ORDER BY received")
data = '{"inboxMessageIds":['
for row in queryreturn:
msgid = row[0]
if len(data) > 25:
data += ','
data += json.dumps(
{'msgid': hexlify(msgid)}, indent=4, separators=(',', ': '))
data += ']}'
return data
def HandleGetInboxMessageById(self, params):
"""Handle a request to get an inbox messsage by ID"""
if not params:
raise APIError(0, 'I need parameters!')
elif len(params) == 1:
msgid = self._decode(params[0], "hex")
elif len(params) >= 2:
msgid = self._decode(params[0], "hex")
readStatus = params[1]
if not isinstance(readStatus, bool):
raise APIError(
23, 'Bool expected in readStatus, saw %s instead.' %
type(readStatus))
queryreturn = sqlQuery(
"SELECT read FROM inbox WHERE msgid=?", msgid)
# UPDATE is slow, only update if status is different
if queryreturn != [] and (queryreturn[0][0] == 1) != readStatus:
sqlExecute(
"UPDATE inbox set read = ? WHERE msgid=?",
readStatus, msgid)
queues.UISignalQueue.put(('changedInboxUnread', None))
queryreturn = sqlQuery(
"SELECT msgid, toaddress, fromaddress, subject, received, message,"
" encodingtype, read FROM inbox WHERE msgid=?", msgid
)
data = '{"inboxMessage":['
for row in queryreturn:
msgid, toAddress, fromAddress, subject, received, message, \
encodingtype, read = row
subject = shared.fixPotentiallyInvalidUTF8Data(subject)
message = shared.fixPotentiallyInvalidUTF8Data(message)
data += json.dumps({
'msgid': hexlify(msgid),
'toAddress': toAddress,
'fromAddress': fromAddress,
'subject': base64.b64encode(subject),
'message': base64.b64encode(message),
'encodingType': encodingtype,
'receivedTime': received,
'read': read}, indent=4, separators=(',', ': '))
data += ']}'
return data
def HandleGetAllSentMessages(self, params):
"""Handle a request to get all sent messages"""
queryreturn = sqlQuery(
"SELECT msgid, toaddress, fromaddress, subject, lastactiontime,"
" message, encodingtype, status, ackdata FROM sent"
" WHERE folder='sent' ORDER BY lastactiontime"
)
data = '{"sentMessages":['
for row in queryreturn:
msgid, toAddress, fromAddress, subject, lastactiontime, message, \
encodingtype, status, ackdata = row
subject = shared.fixPotentiallyInvalidUTF8Data(subject)
message = shared.fixPotentiallyInvalidUTF8Data(message)
if len(data) > 25:
data += ','
data += json.dumps({
'msgid': hexlify(msgid),
'toAddress': toAddress,
'fromAddress': fromAddress,
'subject': base64.b64encode(subject),
'message': base64.b64encode(message),
'encodingType': encodingtype,
'lastActionTime': lastactiontime,
'status': status,
'ackData': hexlify(ackdata)}, indent=4, separators=(',', ': '))
data += ']}'
return data
def HandleGetAllSentMessageIds(self, params):
"""Handle a request to get all sent message IDs"""
queryreturn = sqlQuery(
"SELECT msgid FROM sent where folder='sent'"
" ORDER BY lastactiontime"
)
data = '{"sentMessageIds":['
for row in queryreturn:
msgid = row[0]
if len(data) > 25:
data += ','
data += json.dumps(
{'msgid': hexlify(msgid)}, indent=4, separators=(',', ': '))
data += ']}'
return data
def HandleInboxMessagesByReceiver(self, params):
"""Handle a request to get inbox messages by receiver"""
if not params:
raise APIError(0, 'I need parameters!')
toAddress = params[0]
queryreturn = sqlQuery(
"SELECT msgid, toaddress, fromaddress, subject, received, message,"
" encodingtype FROM inbox WHERE folder='inbox' AND toAddress=?",
toAddress)
data = '{"inboxMessages":['
for row in queryreturn:
msgid, toAddress, fromAddress, subject, received, message, \
encodingtype = row
subject = shared.fixPotentiallyInvalidUTF8Data(subject)
message = shared.fixPotentiallyInvalidUTF8Data(message)
if len(data) > 25:
data += ','
data += json.dumps({
'msgid': hexlify(msgid),
'toAddress': toAddress,
'fromAddress': fromAddress,
'subject': base64.b64encode(subject),
'message': base64.b64encode(message),
'encodingType': encodingtype,
'receivedTime': received}, indent=4, separators=(',', ': '))
data += ']}'
return data
def HandleGetSentMessageById(self, params):
"""Handle a request to get a sent message by ID"""
if not params:
raise APIError(0, 'I need parameters!')
msgid = self._decode(params[0], "hex")
queryreturn = sqlQuery(
"SELECT msgid, toaddress, fromaddress, subject, lastactiontime,"
" message, encodingtype, status, ackdata FROM sent WHERE msgid=?",
msgid
)
data = '{"sentMessage":['
for row in queryreturn:
msgid, toAddress, fromAddress, subject, lastactiontime, message, \
encodingtype, status, ackdata = row
subject = shared.fixPotentiallyInvalidUTF8Data(subject)
message = shared.fixPotentiallyInvalidUTF8Data(message)
data += json.dumps({
'msgid': hexlify(msgid),
'toAddress': toAddress,
'fromAddress': fromAddress,
'subject': base64.b64encode(subject),
'message': base64.b64encode(message),
'encodingType': encodingtype,
'lastActionTime': lastactiontime,
'status': status,
'ackData': hexlify(ackdata)}, indent=4, separators=(',', ': '))
data += ']}'
return data
def HandleGetSentMessagesByAddress(self, params):
"""Handle a request to get sent messages by address"""
if not params:
raise APIError(0, 'I need parameters!')
fromAddress = params[0]
queryreturn = sqlQuery(
"SELECT msgid, toaddress, fromaddress, subject, lastactiontime,"
" message, encodingtype, status, ackdata FROM sent"
" WHERE folder='sent' AND fromAddress=? ORDER BY lastactiontime",
fromAddress
)
data = '{"sentMessages":['
for row in queryreturn:
msgid, toAddress, fromAddress, subject, lastactiontime, message, \
encodingtype, status, ackdata = row
subject = shared.fixPotentiallyInvalidUTF8Data(subject)
message = shared.fixPotentiallyInvalidUTF8Data(message)
if len(data) > 25:
data += ','
data += json.dumps({
'msgid': hexlify(msgid),
'toAddress': toAddress,
'fromAddress': fromAddress,
'subject': base64.b64encode(subject),
'message': base64.b64encode(message),
'encodingType': encodingtype,
'lastActionTime': lastactiontime,
'status': status,
'ackData': hexlify(ackdata)}, indent=4, separators=(',', ': '))
data += ']}'
return data
def HandleGetSentMessagesByAckData(self, params):
"""Handle a request to get sent messages by ack data"""
if not params:
raise APIError(0, 'I need parameters!')
ackData = self._decode(params[0], "hex")
queryreturn = sqlQuery(
"SELECT msgid, toaddress, fromaddress, subject, lastactiontime,"
" message, encodingtype, status, ackdata FROM sent"
" WHERE ackdata=?", ackData
)
data = '{"sentMessage":['
for row in queryreturn:
msgid, toAddress, fromAddress, subject, lastactiontime, message, \
encodingtype, status, ackdata = row
subject = shared.fixPotentiallyInvalidUTF8Data(subject)
message = shared.fixPotentiallyInvalidUTF8Data(message)
data += json.dumps({
'msgid': hexlify(msgid),
'toAddress': toAddress,
'fromAddress': fromAddress,
'subject': base64.b64encode(subject),
'message': base64.b64encode(message),
'encodingType': encodingtype,
'lastActionTime': lastactiontime,
'status': status,
'ackData': hexlify(ackdata)}, indent=4, separators=(',', ': '))
data += ']}'
return data
def HandleTrashMessage(self, params):
"""Handle a request to trash a message by ID"""
if not params:
raise APIError(0, 'I need parameters!')
msgid = self._decode(params[0], "hex")
# Trash if in inbox table
helper_inbox.trash(msgid)
# Trash if in sent table
sqlExecute('''UPDATE sent SET folder='trash' WHERE msgid=?''', msgid)
return 'Trashed message (assuming message existed).'
def HandleTrashInboxMessage(self, params):
"""Handle a request to trash an inbox message by ID"""
if not params:
raise APIError(0, 'I need parameters!')
msgid = self._decode(params[0], "hex")
helper_inbox.trash(msgid)
return 'Trashed inbox message (assuming message existed).'
def HandleTrashSentMessage(self, params):
"""Handle a request to trash a sent message by ID"""
if not params:
raise APIError(0, 'I need parameters!')
msgid = self._decode(params[0], "hex")
sqlExecute('''UPDATE sent SET folder='trash' WHERE msgid=?''', msgid)
return 'Trashed sent message (assuming message existed).'
def HandleSendMessage(self, params): # pylint: disable=too-many-locals
"""Handle a request to send a message"""
if not params:
raise APIError(0, 'I need parameters!')
elif len(params) == 4:
toAddress, fromAddress, subject, message = params
encodingType = 2
TTL = 4 * 24 * 60 * 60
elif len(params) == 5:
toAddress, fromAddress, subject, message, encodingType = params
TTL = 4 * 24 * 60 * 60
elif len(params) == 6:
toAddress, fromAddress, subject, message, encodingType, TTL = \
params
if encodingType not in [2, 3]:
raise APIError(6, 'The encoding type must be 2 or 3.')
subject = self._decode(subject, "base64")
message = self._decode(message, "base64")
if len(subject + message) > (2 ** 18 - 500):
raise APIError(27, 'Message is too long.')
if TTL < 60 * 60:
TTL = 60 * 60
if TTL > 28 * 24 * 60 * 60:
TTL = 28 * 24 * 60 * 60
toAddress = addBMIfNotPresent(toAddress)
fromAddress = addBMIfNotPresent(fromAddress)
status, addressVersionNumber, streamNumber, toRipe = \
self._verifyAddress(toAddress)
self._verifyAddress(fromAddress)
try:
fromAddressEnabled = BMConfigParser().getboolean(
fromAddress, 'enabled')
except BaseException:
raise APIError(
13, 'Could not find your fromAddress in the keys.dat file.')
if not fromAddressEnabled:
raise APIError(14, 'Your fromAddress is disabled. Cannot send.')
stealthLevel = BMConfigParser().safeGetInt(
'bitmessagesettings', 'ackstealthlevel')
ackdata = genAckPayload(streamNumber, stealthLevel)
t = ('',
toAddress,
toRipe,
fromAddress,
subject,
message,
ackdata,
int(time.time()), # sentTime (this won't change)
int(time.time()), # lastActionTime
0,
'msgqueued',
0,
'sent',
2,
TTL)
helper_sent.insert(t)
toLabel = ''
queryreturn = sqlQuery(
"SELECT label FROM addressbook WHERE address=?", toAddress)
if queryreturn != []:
for row in queryreturn:
toLabel, = row
queues.UISignalQueue.put(('displayNewSentMessage', (
toAddress, toLabel, fromAddress, subject, message, ackdata)))
queues.workerQueue.put(('sendmessage', toAddress))
return hexlify(ackdata)
def HandleSendBroadcast(self, params):
"""Handle a request to send a broadcast message"""
if not params:
raise APIError(0, 'I need parameters!')
if len(params) == 3:
fromAddress, subject, message = params
encodingType = 2
TTL = 4 * 24 * 60 * 60
elif len(params) == 4:
fromAddress, subject, message, encodingType = params
TTL = 4 * 24 * 60 * 60
elif len(params) == 5:
fromAddress, subject, message, encodingType, TTL = params
if encodingType not in [2, 3]:
raise APIError(6, 'The encoding type must be 2 or 3.')
subject = self._decode(subject, "base64")
message = self._decode(message, "base64")
if len(subject + message) > (2 ** 18 - 500):
raise APIError(27, 'Message is too long.')
if TTL < 60 * 60:
TTL = 60 * 60
if TTL > 28 * 24 * 60 * 60:
TTL = 28 * 24 * 60 * 60
fromAddress = addBMIfNotPresent(fromAddress)
self._verifyAddress(fromAddress)
try:
BMConfigParser().getboolean(fromAddress, 'enabled')
except BaseException:
raise APIError(
13, 'could not find your fromAddress in the keys.dat file.')
streamNumber = decodeAddress(fromAddress)[2]
ackdata = genAckPayload(streamNumber, 0)
toAddress = '[Broadcast subscribers]'
ripe = ''
t = ('',
toAddress,
ripe,
fromAddress,
subject,
message,
ackdata,
int(time.time()), # sentTime (this doesn't change)
int(time.time()), # lastActionTime
0,
'broadcastqueued',
0,
'sent',
2,
TTL)
helper_sent.insert(t)
toLabel = '[Broadcast subscribers]'
queues.UISignalQueue.put(('displayNewSentMessage', (
toAddress, toLabel, fromAddress, subject, message, ackdata)))
queues.workerQueue.put(('sendbroadcast', ''))
return hexlify(ackdata)
def HandleGetStatus(self, params):
"""Handle a request to get the status of a sent message"""
if len(params) != 1:
raise APIError(0, 'I need one parameter!')
ackdata, = params
if len(ackdata) < 76:
# The length of ackData should be at least 38 bytes (76 hex digits)
raise APIError(15, 'Invalid ackData object size.')
ackdata = self._decode(ackdata, "hex")
queryreturn = sqlQuery(
"SELECT status FROM sent where ackdata=?", ackdata)
if queryreturn == []:
return 'notfound'
for row in queryreturn:
status, = row
return status
def HandleAddSubscription(self, params):
"""Handle a request to add a subscription"""
if not params:
raise APIError(0, 'I need parameters!')
if len(params) == 1:
address, = params
label = ''
if len(params) == 2:
address, label = params
label = self._decode(label, "base64")
try:
unicode(label, 'utf-8')
except BaseException:
raise APIError(17, 'Label is not valid UTF-8 data.')
if len(params) > 2:
raise APIError(0, 'I need either 1 or 2 parameters!')
address = addBMIfNotPresent(address)
self._verifyAddress(address)
# First we must check to see if the address is already in the
# subscriptions list.
queryreturn = sqlQuery(
"SELECT * FROM subscriptions WHERE address=?", address)
if queryreturn != []:
raise APIError(16, 'You are already subscribed to that address.')
sqlExecute(
"INSERT INTO subscriptions VALUES (?,?,?)", label, address, True)
shared.reloadBroadcastSendersForWhichImWatching()
queues.UISignalQueue.put(('rerenderMessagelistFromLabels', ''))
queues.UISignalQueue.put(('rerenderSubscriptions', ''))
return 'Added subscription.'
def HandleDeleteSubscription(self, params):
"""Handle a request to delete a subscription"""
if len(params) != 1:
raise APIError(0, 'I need 1 parameter!')
address, = params
address = addBMIfNotPresent(address)
sqlExecute('''DELETE FROM subscriptions WHERE address=?''', address)
shared.reloadBroadcastSendersForWhichImWatching()
queues.UISignalQueue.put(('rerenderMessagelistFromLabels', ''))
queues.UISignalQueue.put(('rerenderSubscriptions', ''))
return 'Deleted subscription if it existed.'
def ListSubscriptions(self, params):
"""Handle a request to list susbcriptions"""
queryreturn = sqlQuery(
"SELECT label, address, enabled FROM subscriptions")
data = {'subscriptions': []}
for row in queryreturn:
label, address, enabled = row
label = shared.fixPotentiallyInvalidUTF8Data(label)
data['subscriptions'].append({
'label': base64.b64encode(label),
'address': address,
'enabled': enabled == 1
})
return json.dumps(data, indent=4, separators=(',', ': '))
def HandleDisseminatePreEncryptedMsg(self, params):
"""Handle a request to disseminate an encrypted message"""
# The device issuing this command to PyBitmessage supplies a msg
# object that has already been encrypted but which still needs the POW
# to be done. PyBitmessage accepts this msg object and sends it out
# to the rest of the Bitmessage network as if it had generated
# the message itself. Please do not yet add this to the api doc.
if len(params) != 3:
raise APIError(0, 'I need 3 parameter!')
encryptedPayload, requiredAverageProofOfWorkNonceTrialsPerByte, \
requiredPayloadLengthExtraBytes = params
encryptedPayload = self._decode(encryptedPayload, "hex")
# Let us do the POW and attach it to the front
target = 2**64 / (
(
len(encryptedPayload) + requiredPayloadLengthExtraBytes + 8
) * requiredAverageProofOfWorkNonceTrialsPerByte
)
with threads.printLock:
print(
'(For msg message via API) Doing proof of work.'
'Total required difficulty:',
float(
requiredAverageProofOfWorkNonceTrialsPerByte
) / defaults.networkDefaultProofOfWorkNonceTrialsPerByte,
'Required small message difficulty:',
float(
requiredPayloadLengthExtraBytes
) / defaults.networkDefaultPayloadLengthExtraBytes,
)
powStartTime = time.time()
initialHash = hashlib.sha512(encryptedPayload).digest()
trialValue, nonce = proofofwork.run(target, initialHash)
with threads.printLock:
print('(For msg message via API) Found proof of work', trialValue, 'Nonce:', nonce)
try:
print(
'POW took', int(time.time() - powStartTime),
'seconds.', nonce / (time.time() - powStartTime),
'nonce trials per second.',
)
except BaseException:
pass
encryptedPayload = pack('>Q', nonce) + encryptedPayload
toStreamNumber = decodeVarint(encryptedPayload[16:26])[0]
inventoryHash = calculateInventoryHash(encryptedPayload)
objectType = 2
TTL = 2.5 * 24 * 60 * 60
Inventory()[inventoryHash] = (
objectType, toStreamNumber, encryptedPayload,
int(time.time()) + TTL, ''
)
with threads.printLock:
print('Broadcasting inv for msg(API disseminatePreEncryptedMsg command):', hexlify(inventoryHash))
queues.invQueue.put((toStreamNumber, inventoryHash))
def HandleTrashSentMessageByAckDAta(self, params):
"""Handle a request to trash a sent message by ackdata"""
# This API method should only be used when msgid is not available
if not params:
raise APIError(0, 'I need parameters!')
ackdata = self._decode(params[0], "hex")
sqlExecute("UPDATE sent SET folder='trash' WHERE ackdata=?", ackdata)
return 'Trashed sent message (assuming message existed).'
def HandleDissimatePubKey(self, params):
"""Handle a request to disseminate a public key"""
# The device issuing this command to PyBitmessage supplies a pubkey
# object to be disseminated to the rest of the Bitmessage network.
# PyBitmessage accepts this pubkey object and sends it out to the rest
# of the Bitmessage network as if it had generated the pubkey object
# itself. Please do not yet add this to the api doc.
if len(params) != 1:
raise APIError(0, 'I need 1 parameter!')
payload, = params
payload = self._decode(payload, "hex")
# Let us do the POW
target = 2 ** 64 / ((
len(payload) + defaults.networkDefaultPayloadLengthExtraBytes + 8
) * defaults.networkDefaultProofOfWorkNonceTrialsPerByte)
print('(For pubkey message via API) Doing proof of work...')
initialHash = hashlib.sha512(payload).digest()
trialValue, nonce = proofofwork.run(target, initialHash)
print('(For pubkey message via API) Found proof of work', trialValue, 'Nonce:', nonce)
payload = pack('>Q', nonce) + payload
pubkeyReadPosition = 8 # bypass the nonce
if payload[pubkeyReadPosition:pubkeyReadPosition + 4] == \
'\x00\x00\x00\x00': # if this pubkey uses 8 byte time
pubkeyReadPosition += 8
else:
pubkeyReadPosition += 4
addressVersion, addressVersionLength = decodeVarint(
payload[pubkeyReadPosition:pubkeyReadPosition + 10])
pubkeyReadPosition += addressVersionLength
pubkeyStreamNumber = decodeVarint(
payload[pubkeyReadPosition:pubkeyReadPosition + 10])[0]
inventoryHash = calculateInventoryHash(payload)
objectType = 1 # .. todo::: support v4 pubkeys
TTL = 28 * 24 * 60 * 60
Inventory()[inventoryHash] = (
objectType, pubkeyStreamNumber, payload, int(time.time()) + TTL, ''
)
with threads.printLock:
print('broadcasting inv within API command disseminatePubkey with hash:', hexlify(inventoryHash))
queues.invQueue.put((pubkeyStreamNumber, inventoryHash))
def HandleGetMessageDataByDestinationHash(self, params):
"""Handle a request to get message data by destination hash"""
# Method will eventually be used by a particular Android app to
# select relevant messages. Do not yet add this to the api
# doc.
if len(params) != 1:
raise APIError(0, 'I need 1 parameter!')
requestedHash, = params
if len(requestedHash) != 32:
raise APIError(
19, 'The length of hash should be 32 bytes (encoded in hex'
' thus 64 characters).')
requestedHash = self._decode(requestedHash, "hex")
# This is not a particularly commonly used API function. Before we
# use it we'll need to fill out a field in our inventory database
# which is blank by default (first20bytesofencryptedmessage).
queryreturn = sqlQuery(
"SELECT hash, payload FROM inventory WHERE tag = ''"
" and objecttype = 2")
with SqlBulkExecute() as sql:
for row in queryreturn:
hash01, payload = row
readPosition = 16 # Nonce length + time length
# Stream Number length
readPosition += decodeVarint(
payload[readPosition:readPosition + 10])[1]
t = (payload[readPosition:readPosition + 32], hash01)
sql.execute("UPDATE inventory SET tag=? WHERE hash=?", *t)
queryreturn = sqlQuery(
"SELECT payload FROM inventory WHERE tag = ?", requestedHash)
data = '{"receivedMessageDatas":['
for row in queryreturn:
payload, = row
if len(data) > 25:
data += ','
data += json.dumps(
{'data': hexlify(payload)}, indent=4, separators=(',', ': '))
data += ']}'
return data
def HandleClientStatus(self, params):
"""Handle a request to get the status of the client"""
connections_num = len(network.stats.connectedHostsList())
if connections_num == 0:
networkStatus = 'notConnected'
elif state.clientHasReceivedIncomingConnections:
networkStatus = 'connectedAndReceivingIncomingConnections'
else:
networkStatus = 'connectedButHaveNotReceivedIncomingConnections'
return json.dumps({
'networkConnections': connections_num,
'numberOfMessagesProcessed': state.numberOfMessagesProcessed,
'numberOfBroadcastsProcessed': state.numberOfBroadcastsProcessed,
'numberOfPubkeysProcessed': state.numberOfPubkeysProcessed,
'networkStatus': networkStatus,
'softwareName': 'PyBitmessage',
'softwareVersion': softwareVersion
}, indent=4, separators=(',', ': '))
def HandleDecodeAddress(self, params):
"""Handle a request to decode an address"""
# Return a meaningful decoding of an address.
if len(params) != 1:
raise APIError(0, 'I need 1 parameter!')
address, = params
status, addressVersion, streamNumber, ripe = decodeAddress(address)
return json.dumps({
'status': status,
'addressVersion': addressVersion,
'streamNumber': streamNumber,
'ripe': base64.b64encode(ripe)
}, indent=4, separators=(',', ': '))
def HandleHelloWorld(self, params):
"""Test two string params"""
a, b = params
return a + '-' + b
def HandleAdd(self, params):
"""Test two numeric params"""
a, b = params
return a + b
def HandleStatusBar(self, params):
"""Handle a request to update the status bar"""
message, = params
queues.UISignalQueue.put(('updateStatusBar', message))
def HandleDeleteAndVacuum(self, params):
"""Handle a request to run the deleteandvacuum stored procedure"""
if not params:
sqlStoredProcedure('deleteandvacuume')
return 'done'
return None
def HandleShutdown(self, params):
"""Handle a request to shutdown the node"""
if not params:
# backward compatible trick because False == 0 is True
state.shutdown = False
return 'done'
return None
handlers = {}
handlers['helloWorld'] = HandleHelloWorld
handlers['add'] = HandleAdd
handlers['statusBar'] = HandleStatusBar
handlers['listAddresses'] = HandleListAddresses
handlers['listAddressBookEntries'] = HandleListAddressBookEntries
# the listAddressbook alias should be removed eventually.
handlers['listAddressbook'] = HandleListAddressBookEntries
handlers['addAddressBookEntry'] = HandleAddAddressBookEntry
# the addAddressbook alias should be deleted eventually.
handlers['addAddressbook'] = HandleAddAddressBookEntry
handlers['deleteAddressBookEntry'] = HandleDeleteAddressBookEntry
# The deleteAddressbook alias should be deleted eventually.
handlers['deleteAddressbook'] = HandleDeleteAddressBookEntry
handlers['createRandomAddress'] = HandleCreateRandomAddress
handlers['createDeterministicAddresses'] = \
HandleCreateDeterministicAddresses
handlers['getDeterministicAddress'] = HandleGetDeterministicAddress
handlers['createChan'] = HandleCreateChan
handlers['joinChan'] = HandleJoinChan
handlers['leaveChan'] = HandleLeaveChan
handlers['deleteAddress'] = HandleDeleteAddress
handlers['getAllInboxMessages'] = HandleGetAllInboxMessages
handlers['getAllInboxMessageIds'] = HandleGetAllInboxMessageIds
handlers['getAllInboxMessageIDs'] = HandleGetAllInboxMessageIds
handlers['getInboxMessageById'] = HandleGetInboxMessageById
handlers['getInboxMessageByID'] = HandleGetInboxMessageById
handlers['getAllSentMessages'] = HandleGetAllSentMessages
handlers['getAllSentMessageIds'] = HandleGetAllSentMessageIds
handlers['getAllSentMessageIDs'] = HandleGetAllSentMessageIds
handlers['getInboxMessagesByReceiver'] = HandleInboxMessagesByReceiver
# after some time getInboxMessagesByAddress should be removed
handlers['getInboxMessagesByAddress'] = HandleInboxMessagesByReceiver
handlers['getSentMessageById'] = HandleGetSentMessageById
handlers['getSentMessageByID'] = HandleGetSentMessageById
handlers['getSentMessagesByAddress'] = HandleGetSentMessagesByAddress
handlers['getSentMessagesBySender'] = HandleGetSentMessagesByAddress
handlers['getSentMessageByAckData'] = HandleGetSentMessagesByAckData
handlers['trashMessage'] = HandleTrashMessage
handlers['trashInboxMessage'] = HandleTrashInboxMessage
handlers['trashSentMessage'] = HandleTrashSentMessage
handlers['trashSentMessageByAckData'] = HandleTrashSentMessageByAckDAta
handlers['sendMessage'] = HandleSendMessage
handlers['sendBroadcast'] = HandleSendBroadcast
handlers['getStatus'] = HandleGetStatus
handlers['addSubscription'] = HandleAddSubscription
handlers['deleteSubscription'] = HandleDeleteSubscription
handlers['listSubscriptions'] = ListSubscriptions
handlers['disseminatePreEncryptedMsg'] = HandleDisseminatePreEncryptedMsg
handlers['disseminatePubkey'] = HandleDissimatePubKey
handlers['getMessageDataByDestinationHash'] = \
HandleGetMessageDataByDestinationHash
handlers['getMessageDataByDestinationTag'] = \
HandleGetMessageDataByDestinationHash
handlers['clientStatus'] = HandleClientStatus
handlers['decodeAddress'] = HandleDecodeAddress
handlers['deleteAndVacuum'] = HandleDeleteAndVacuum
handlers['shutdown'] = HandleShutdown
def _handle_request(self, method, params):
if method not in self.handlers:
raise APIError(20, 'Invalid method: %s' % method)
result = self.handlers[method](self, params)
state.last_api_response = time.time()
return result
def _dispatch(self, method, params):
# pylint: disable=attribute-defined-outside-init
self.cookies = []
validuser = self.APIAuthenticateClient()
if not validuser:
time.sleep(2)
return "RPC Username or password incorrect or HTTP header lacks authentication at all."
try:
return self._handle_request(method, params)
except APIError as e:
return str(e)
except varintDecodeError as e:
logger.error(e)
return "API Error 0026: Data contains a malformed varint. Some details: %s" % e
except Exception as e:
logger.exception(e)
return "API Error 0021: Unexpected API Failure - %s" % e

View File

@ -0,0 +1,191 @@
from turtle import pd
from bitmessagekivy.get_platform import platform
from bitmessagekivy import kivy_helper_search
from helper_sql import sqlExecute
from functools import partial
from kivy.clock import Clock
from kivy.properties import (
ListProperty,
StringProperty
)
from kivymd.uix.button import MDRaisedButton
from kivymd.uix.dialog import MDDialog
from kivymd.uix.label import MDLabel
from kivy.uix.screenmanager import Screen
import state
from bitmessagekivy.baseclass.common import (
avatarImageFirstLetter, toast,
ThemeClsColor, SwipeToDeleteItem
)
from bitmessagekivy.baseclass.popup import AddbookDetailPopup
class AddressBook(Screen):
"""AddressBook Screen class for kivy Ui"""
queryreturn = ListProperty()
has_refreshed = True
address_label = StringProperty()
address = StringProperty()
def __init__(self, *args, **kwargs):
"""Getting AddressBook Details"""
super(AddressBook, self).__init__(*args, **kwargs)
self.addbook_popup = None
Clock.schedule_once(self.init_ui, 0)
def init_ui(self, dt=0):
"""Clock Schdule for method AddressBook"""
self.loadAddresslist(None, 'All', '')
print(dt)
def loadAddresslist(self, account, where="", what=""):
"""Clock Schdule for method AddressBook"""
if state.searcing_text:
self.ids.scroll_y.scroll_y = 1.0
where = ['label', 'address']
what = state.searcing_text
xAddress = ''
self.ids.tag_label.text = ''
self.queryreturn = kivy_helper_search.search_sql(
xAddress, account, "addressbook", where, what, False)
self.queryreturn = [obj for obj in reversed(self.queryreturn)]
if self.queryreturn:
self.ids.tag_label.text = 'Address Book'
self.has_refreshed = True
self.set_mdList(0, 20)
self.ids.scroll_y.bind(scroll_y=self.check_scroll_y)
else:
content = MDLabel(
font_style='Caption',
theme_text_color='Primary',
text="No contact found!" if state.searcing_text
else "No contact found yet...... ",
halign='center',
size_hint_y=None,
valign='top')
self.ids.ml.add_widget(content)
def set_mdList(self, start_index, end_index):
"""Creating the mdList"""
for item in self.queryreturn[start_index:end_index]:
message_row = SwipeToDeleteItem(
text=item[0],
)
listItem = message_row.ids.content
listItem.secondary_text = item[1]
listItem.theme_text_color = "Custom"
listItem.text_color = ThemeClsColor
# listItem.add_widget(AvatarSampleWidget(
# source=state.imageDir + '/text_images/{}.png'.format(
# avatarImageFirstLetter(item[0].strip()))))
image = state.imageDir + "/text_images/{}.png".format(
avatarImageFirstLetter(item[0].strip()))
message_row.ids.avater_img.source = image
listItem.bind(on_release=partial(
self.addBook_detail, item[1], item[0], message_row))
message_row.ids.delete_msg.bind(on_press=partial(self.delete_address, item[1]))
self.ids.ml.add_widget(message_row)
def check_scroll_y(self, instance, somethingelse):
"""Load data on scroll"""
if self.ids.scroll_y.scroll_y <= -0.0 and self.has_refreshed:
self.ids.scroll_y.scroll_y = 0.06
exist_addresses = len(self.ids.ml.children)
if exist_addresses != len(self.queryreturn):
self.update_addressBook_on_scroll(exist_addresses)
self.has_refreshed = (
True if exist_addresses != len(self.queryreturn) else False
)
def update_addressBook_on_scroll(self, exist_addresses):
"""Load more data on scroll down"""
self.set_mdList(exist_addresses, exist_addresses + 5)
@staticmethod
def refreshs(*args):
"""Refresh the Widget"""
# state.navinstance.ids.sc11.ids.ml.clear_widgets()
# state.navinstance.ids.sc11.loadAddresslist(None, 'All', '')
# @staticmethod
def addBook_detail(self, address, label, instance, *args):
"""Addressbook details"""
if instance.state == 'closed':
instance.ids.delete_msg.disabled = True
if instance.open_progress == 0.0:
obj = AddbookDetailPopup()
self.address_label = obj.address_label = label
self.address = obj.address = address
width = .9 if platform == 'android' else .8
self.addbook_popup = MDDialog(
type="custom",
size_hint=(width, .25),
content_cls=obj,
buttons=[
MDRaisedButton(
text="Send message to",
on_release=self.send_message_to,
),
MDRaisedButton(
text="Save",
on_release=self.update_addbook_label,
),
MDRaisedButton(
text="Cancel",
on_release=self.close_pop,
),
],
)
# self.addbook_popup.set_normal_height()
self.addbook_popup.auto_dismiss = False
self.addbook_popup.open()
else:
instance.ids.delete_msg.disabled = False
def delete_address(self, address, instance, *args):
"""Delete inbox mail from inbox listing"""
self.ids.ml.remove_widget(instance.parent.parent)
# if len(self.ids.ml.children) == 0:
if self.ids.ml.children is not None:
self.ids.tag_label.text = ''
sqlExecute(
"DELETE FROM addressbook WHERE address = '{}';".format(address))
toast('Address Deleted')
def close_pop(self, instance):
"""Pop is Canceled"""
self.addbook_popup.dismiss()
toast('Canceled')
def update_addbook_label(self, instance):
"""Updating the label of address book address"""
address_list = kivy_helper_search.search_sql(folder="addressbook")
stored_labels = [labels[0] for labels in address_list]
add_dict = dict(address_list)
label = str(self.addbook_popup.content_cls.ids.add_label.text)
if label in stored_labels and self.address == add_dict[label]:
stored_labels.remove(label)
if label and label not in stored_labels:
sqlExecute(
"UPDATE addressbook SET label = '{}' WHERE"
" address = '{}';".format(
label, self.addbook_popup.content_cls.address))
state.kivyapp.root.ids.sc11.ids.ml.clear_widgets()
state.kivyapp.root.ids.sc11.loadAddresslist(None, 'All', '')
self.addbook_popup.dismiss()
toast('Saved')
def send_message_to(self, instance):
"""Method used to fill to_address of composer autofield"""
state.kivyapp.set_navbar_for_composer()
window_obj = state.kivyapp.root.ids
window_obj.sc3.children[1].ids.txt_input.text = self.address
window_obj.sc3.children[1].ids.ti.text = ''
window_obj.sc3.children[1].ids.btn.text = 'Select'
window_obj.sc3.children[1].ids.subject.text = ''
window_obj.sc3.children[1].ids.body.text = ''
window_obj.scr_mngr.current = 'create'
self.addbook_popup.dismiss()

View File

@ -0,0 +1,216 @@
from bmconfigparser import BMConfigParser
from helper_sql import sqlExecute, sqlQuery
from functools import partial
from kivy.clock import Clock
from kivy.properties import (
ListProperty,
StringProperty
)
from kivy.uix.screenmanager import Screen
from kivymd.uix.label import MDLabel
import state
from bitmessagekivy.baseclass.common import (
showLimitedCnt, toast, ThemeClsColor,
avatarImageFirstLetter, CutsomSwipeToDeleteItem,
ShowTimeHistoy
)
from bitmessagekivy.baseclass.maildetail import MailDetail
# from bitmessagekivy.baseclass.trash import Trash
class Allmails(Screen):
"""Allmails Screen for kivy Ui"""
data = ListProperty()
has_refreshed = True
all_mails = ListProperty()
account = StringProperty()
def __init__(self, *args, **kwargs):
"""Method Parsing the address"""
super(Allmails, self).__init__(*args, **kwargs)
if state.association == '':
if state.kivyapp.variable_1:
state.association = state.kivyapp.variable_1[0]
Clock.schedule_once(self.init_ui, 0)
def init_ui(self, dt=0):
"""Clock Schdule for method all mails"""
self.loadMessagelist()
print(dt)
def loadMessagelist(self):
"""Load Inbox, Sent anf Draft list of messages"""
self.account = state.association
self.ids.tag_label.text = ''
self.allMessageQuery(0, 20)
if self.all_mails:
self.ids.tag_label.text = 'All Mails'
state.kivyapp.get_inbox_count()
state.kivyapp.get_sent_count()
state.all_count = str(
int(state.sent_count) + int(state.inbox_count))
self.set_AllmailCnt(state.all_count)
self.set_mdlist()
# self.ids.refresh_layout.bind(scroll_y=self.check_scroll_y)
self.ids.scroll_y.bind(scroll_y=self.check_scroll_y)
else:
self.set_AllmailCnt('0')
content = MDLabel(
font_style='Caption',
theme_text_color='Primary',
text="yet no message for this account!!!!!!!!!!!!!",
halign='center',
size_hint_y=None,
valign='top')
self.ids.ml.add_widget(content)
def allMessageQuery(self, start_indx, end_indx):
"""Retrieving data from inbox or sent both tables"""
self.all_mails = sqlQuery(
"SELECT toaddress, fromaddress, subject, message, folder, ackdata"
" As id, DATE(senttime) As actionTime, senttime as msgtime FROM sent WHERE"
" folder = 'sent' and fromaddress = '{0}'"
" UNION SELECT toaddress, fromaddress, subject, message, folder,"
" msgid As id, DATE(received) As actionTime, received as msgtime FROM inbox"
" WHERE folder = 'inbox' and toaddress = '{0}'"
" ORDER BY actionTime DESC limit {1}, {2}".format(
self.account, start_indx, end_indx))
def set_AllmailCnt(self, Count): # pylint: disable=no-self-use
"""This method is used to set allmails message count"""
allmailCnt_obj = state.kivyapp.root.ids.content_drawer.ids.allmail_cnt
allmailCnt_obj.ids.badge_txt.text = showLimitedCnt(int(Count))
def set_mdlist(self):
"""This method is used to create mdList for allmaills"""
data_exist = len(self.ids.ml.children)
for item in self.all_mails:
body = item[3].decode() if isinstance(item[3], bytes) else item[3]
subject = item[2].decode() if isinstance(item[2], bytes) else item[2]
message_row = CutsomSwipeToDeleteItem(
text=item[1],
)
listItem = message_row.ids.content
secondary_text = (subject[:50] + '........' if len(
subject) >= 50 else (
subject + ',' + body)[0:50] + '........').replace('\t', '').replace(' ', '')
listItem.secondary_text = secondary_text
listItem.theme_text_color = "Custom"
listItem.text_color = ThemeClsColor
img_latter = state.imageDir + '/text_images/{}.png'.format(
avatarImageFirstLetter(body.strip()))
message_row.ids.avater_img.source = img_latter
listItem.bind(on_release=partial(
self.mail_detail, item[5], item[4], message_row))
message_row.ids.time_tag.text = str(ShowTimeHistoy(item[7]))
message_row.ids.chip_tag.text = item[4]
# listItem = message_row.ids.content
# secondary_text = (subject[:50] + '........' if len(
# subject) >= 50 else (
# subject + ',' + body)[0:50] + '........').replace('\t', '').replace(' ', '')
# listItem.secondary_text = secondary_text
# listItem.theme_text_color = "Custom"
# listItem.text_color = ThemeClsColor
# listItem.add_widget(AvatarSampleWidget(
# source=state.imageDir + '/text_images/{}.png'.format(
# avatarImageFirstLetter(body.strip()))))
# listItem.bind(on_release=partial(
# self.mail_detail, item[5], item[4], message_row))
# listItem.add_widget(AddTimeWidget(item[7]))
# listItem.add_widget(chipTag(item[4]))
message_row.ids.delete_msg.bind(on_press=partial(
self.swipe_delete, item[5], item[4]))
self.ids.ml.add_widget(message_row)
updated_data = len(self.ids.ml.children)
self.has_refreshed = True if data_exist != updated_data else False
def check_scroll_y(self, instance, somethingelse):
"""Scroll fixed length"""
if self.ids.scroll_y.scroll_y <= -0.00 and self.has_refreshed:
self.ids.scroll_y.scroll_y = .06
load_more = len(self.ids.ml.children)
self.updating_allmail(load_more)
def updating_allmail(self, load_more):
"""This method is used to update the all mail
listing value on the scroll of screen"""
self.allMessageQuery(load_more, 5)
self.set_mdlist()
def mail_detail(self, unique_id, folder, instance, *args):
"""Load sent and inbox mail details"""
if instance.state == 'closed':
instance.ids.delete_msg.disabled = True
if instance.open_progress == 0.0:
state.detailPageType = folder
state.is_allmail = True
state.mail_id = unique_id
if self.manager:
src_mng_obj = self.manager
else:
src_mng_obj = self.parent.parent
src_mng_obj.screens[11].clear_widgets()
src_mng_obj.screens[11].add_widget(MailDetail())
src_mng_obj.current = 'mailDetail'
else:
instance.ids.delete_msg.disabled = False
def swipe_delete(self, unique_id, folder, instance, *args):
"""Delete inbox mail from all mail listing"""
if folder == 'inbox':
sqlExecute(
"UPDATE inbox SET folder = 'trash' WHERE msgid = ?;",
unique_id)
else:
sqlExecute(
"UPDATE sent SET folder = 'trash' WHERE ackdata = ?;",
unique_id)
self.ids.ml.remove_widget(instance.parent.parent)
try:
msg_count_objs = self.parent.parent.ids.content_drawer.ids
nav_lay_obj = self.parent.parent.ids
except Exception:
msg_count_objs = self.parent.parent.parent.ids.content_drawer.ids
nav_lay_obj = self.parent.parent.parent.ids
if folder == 'inbox':
msg_count_objs.inbox_cnt.ids.badge_txt.text = showLimitedCnt(int(state.inbox_count) - 1)
state.inbox_count = str(int(state.inbox_count) - 1)
nav_lay_obj.sc1.ids.ml.clear_widgets()
nav_lay_obj.sc1.loadMessagelist(state.association)
else:
msg_count_objs.send_cnt.ids.badge_txt.text = showLimitedCnt(int(state.sent_count) - 1)
state.sent_count = str(int(state.sent_count) - 1)
nav_lay_obj.sc4.ids.ml.clear_widgets()
nav_lay_obj.sc4.loadSent(state.association)
if folder != 'inbox':
msg_count_objs.allmail_cnt.ids.badge_txt.text = showLimitedCnt(int(state.all_count) - 1)
state.all_count = str(int(state.all_count) - 1)
msg_count_objs.trash_cnt.ids.badge_txt.text = showLimitedCnt(int(state.trash_count) + 1)
state.trash_count = str(int(state.trash_count) + 1)
if int(state.all_count) <= 0:
self.ids.tag_label.text = ''
# nav_lay_obj.sc5.clear_widgets()
# nav_lay_obj.sc5.add_widget(Trash())
nav_lay_obj.sc17.remove_widget(instance.parent.parent)
toast('Deleted')
def refresh_callback(self, *args):
"""Method updates the state of application,
While the spinner remains on the screen"""
def refresh_callback(interval):
"""Load the allmails screen data"""
self.ids.ml.clear_widgets()
self.remove_widget(self.children[1])
try:
screens_obj = self.parent.screens[16]
except Exception:
screens_obj = self.parent.parent.screens[16]
screens_obj.add_widget(Allmails())
self.ids.refresh_layout.refresh_done()
self.tick = 0
Clock.schedule_once(refresh_callback, 1)

View File

@ -0,0 +1,144 @@
from datetime import datetime
from kivy.core.window import Window
from kivy.lang import Builder
from kivy.metrics import dp
from kivymd.uix.list import (
ILeftBody,
IRightBodyTouch,
)
from kivy.uix.image import Image
from kivymd.uix.label import MDLabel
from bitmessagekivy.get_platform import platform
from kivymd.toast import kivytoast
from kivymd.uix.card import MDCardSwipe
from kivymd.uix.chip import MDChip
from kivy.properties import (
NumericProperty,
StringProperty
)
ThemeClsColor = [0.12, 0.58, 0.95, 1]
data_screens = {
"MailDetail": {
"kv_string": "maildetail",
"Factory": "MailDetail()",
"name_screen": "mailDetail",
"object": 0,
"Import": "from bitmessagekivy.baseclass.maildetail import MailDetail",
},
}
def chipTag(text):
"""This method is used for showing chip tag"""
obj = MDChip()
# obj.size_hint = (None, None)
obj.size_hint = (0.16 if platform == "android" else 0.08, None)
obj.text = text
obj.icon = ""
obj.pos_hint = {
"center_x": 0.91 if platform == "android" else 0.94,
"center_y": 0.3
}
obj.height = dp(18)
obj.text_color = (1, 1, 1, 1)
obj.radius = [8]
return obj
# def initailize_detail_page(manager):
# if not manager.has_screen(
# data_screens['MailDetail']["name_screen"]
# ):
# Builder.load_file(
# os.path.join(
# # os.environ["KITCHEN_SINK_ROOT"],
# os.path.dirname(os.path.dirname(__file__)),
# "kv",
# "maildetail.kv",
# )
# )
# if "Import" in data_screens['MailDetail']:
# exec(data_screens['MailDetail']["Import"])
# screen_object = eval(data_screens['MailDetail']["Factory"])
# data_screens['MailDetail']["object"] = screen_object
# manager.add_widget(screen_object)
# manager.current = data_screens['MailDetail']["name_screen"]
def toast(text):
"""Method will display the toast message"""
kivytoast.toast(text)
def showLimitedCnt(total_msg):
"""This method set the total count limit in badge_text"""
return "99+" if total_msg > 99 else str(total_msg)
def avatarImageFirstLetter(letter_string):
"""This function is used to the first letter for the avatar image"""
try:
if letter_string[0].upper() >= 'A' and letter_string[0].upper() <= 'Z':
img_latter = letter_string[0].upper()
elif int(letter_string[0]) >= 0 and int(letter_string[0]) <= 9:
img_latter = letter_string[0]
else:
img_latter = '!'
except ValueError:
img_latter = '!'
return img_latter if img_latter else '!'
def AddTimeWidget(time): # pylint: disable=redefined-outer-name, W0201
"""This method is used to create TimeWidget"""
action_time = TimeTagRightSampleWidget(
text=str(ShowTimeHistoy(time)),
font_style="Caption",
size=[120, 140] if platform == "android" else [64, 80],
)
action_time.font_size = "11sp"
return action_time
def ShowTimeHistoy(act_time):
"""This method is used to return the message sent or receive time"""
action_time = datetime.fromtimestamp(int(act_time))
crnt_date = datetime.now()
duration = crnt_date - action_time
display_data = (
action_time.strftime("%d/%m/%Y")
if duration.days >= 365
else action_time.strftime("%I:%M %p").lstrip("0")
if duration.days == 0 and crnt_date.strftime("%d/%m/%Y") == action_time.strftime("%d/%m/%Y")
else action_time.strftime("%d %b")
)
return display_data
# pylint: disable=too-few-public-methods
class AvatarSampleWidget(ILeftBody, Image):
"""AvatarSampleWidget class for kivy Ui"""
class TimeTagRightSampleWidget(IRightBodyTouch, MDLabel):
"""TimeTagRightSampleWidget class for Ui"""
class SwipeToDeleteItem(MDCardSwipe):
"""Swipe delete class for App UI"""
text = StringProperty()
cla = Window.size[0] / 2
# cla = 800
swipe_distance = NumericProperty(cla)
opening_time = NumericProperty(0.5)
class CutsomSwipeToDeleteItem(MDCardSwipe):
"""Custom swipe delete class for App UI"""
text = StringProperty()
cla = Window.size[0] / 2
swipe_distance = NumericProperty(cla)
opening_time = NumericProperty(0.5)

View File

@ -0,0 +1,199 @@
import time
from bitmessagekivy import kivy_helper_search
from bmconfigparser import BMConfigParser
from helper_sql import sqlExecute
from functools import partial
from addresses import decodeAddress
from kivy.clock import Clock
from kivy.properties import (
ListProperty,
StringProperty
)
from kivy.uix.screenmanager import Screen
from kivymd.uix.label import MDLabel
import state
from bitmessagekivy.baseclass.common import (
showLimitedCnt, toast, ThemeClsColor,
SwipeToDeleteItem, ShowTimeHistoy
)
from bitmessagekivy.baseclass.maildetail import MailDetail
class Draft(Screen):
"""Draft screen class for kivy Ui"""
data = ListProperty()
account = StringProperty()
queryreturn = ListProperty()
has_refreshed = True
def __init__(self, *args, **kwargs):
"""Method used for storing draft messages"""
super(Draft, self).__init__(*args, **kwargs)
if state.association == '':
if state.kivyapp.variable_1:
state.association = state.kivyapp.variable_1[0]
Clock.schedule_once(self.init_ui, 0)
def init_ui(self, dt=0):
"""Clock Schdule for method draft accounts"""
self.sentaccounts()
print(dt)
def sentaccounts(self):
"""Load draft accounts"""
# self.account = state.association
self.loadDraft()
def loadDraft(self, where="", what=""):
"""Load draft list for Draft messages"""
self.account = state.association
xAddress = 'fromaddress'
self.ids.tag_label.text = ''
self.draftDataQuery(xAddress, where, what)
# if state.msg_counter_objs:
# state.msg_counter_objs.draft_cnt.children[0].children[0].text = showLimitedCnt(len(self.queryreturn))
if self.queryreturn:
self.ids.tag_label.text = 'Draft'
self.set_draftCnt(state.draft_count)
self.set_mdList()
self.ids.scroll_y.bind(scroll_y=self.check_scroll_y)
else:
self.set_draftCnt('0')
content = MDLabel(
font_style='Caption',
theme_text_color='Primary',
text="yet no message for this account!!!!!!!!!!!!!",
halign='center',
size_hint_y=None,
valign='top')
self.ids.ml.add_widget(content)
def draftDataQuery(self, xAddress, where, what, start_indx=0, end_indx=20):
"""This methosd is for retrieving draft messages"""
self.queryreturn = kivy_helper_search.search_sql(
xAddress, self.account, "draft", where, what,
False, start_indx, end_indx)
def set_draftCnt(self, Count): # pylint: disable=no-self-use
"""This method set the count of draft mails"""
draftCnt_obj = state.kivyapp.root.ids.content_drawer.ids.draft_cnt
draftCnt_obj.ids.badge_txt.text = showLimitedCnt(int(Count))
def set_mdList(self):
"""This method is used to create mdlist"""
data = []
total_draft_msg = len(self.ids.ml.children)
for mail in self.queryreturn:
third_text = mail[3].replace('\n', ' ')
data.append({
'text': mail[1].strip(),
'secondary_text': mail[2][:10] + '...........' if len(
mail[2]) > 10 else mail[2] + '\n' + " " + (
third_text[:25] + '...!') if len(
third_text) > 25 else third_text,
'ackdata': mail[5], 'senttime': mail[6]})
for item in data:
message_row = SwipeToDeleteItem(
text='Draft',
)
listItem = message_row.ids.content
listItem.secondary_text = item["text"]
listItem.theme_text_color = "Custom"
listItem.text_color = ThemeClsColor
message_row.ids.avater_img.source = state.imageDir + '/avatar.png'
listItem.bind(on_release=partial(
self.draft_detail, item['ackdata'], message_row))
message_row.ids.time_tag.text = str(ShowTimeHistoy(item['senttime']))
message_row.ids.delete_msg.bind(on_press=partial(self.delete_draft, item['ackdata']))
self.ids.ml.add_widget(message_row)
updated_msg = len(self.ids.ml.children)
self.has_refreshed = True if total_draft_msg != updated_msg else False
def check_scroll_y(self, instance, somethingelse):
"""Load data on scroll"""
if self.ids.scroll_y.scroll_y <= -0.0 and self.has_refreshed:
self.ids.scroll_y.scroll_y = 0.06
total_draft_msg = len(self.ids.ml.children)
self.update_draft_screen_on_scroll(total_draft_msg)
def update_draft_screen_on_scroll(self, total_draft_msg, where='', what=''):
"""Load more data on scroll down"""
self.draftDataQuery('fromaddress', where, what, total_draft_msg, 5)
self.set_mdList()
def draft_detail(self, ackdata, instance, *args):
"""Show draft Details"""
if instance.state == 'closed':
instance.ids.delete_msg.disabled = True
if instance.open_progress == 0.0:
state.detailPageType = 'draft'
state.mail_id = ackdata
if self.manager:
src_mng_obj = self.manager
else:
src_mng_obj = self.parent.parent
src_mng_obj.screens[11].clear_widgets()
src_mng_obj.screens[11].add_widget(MailDetail())
src_mng_obj.current = 'mailDetail'
else:
instance.ids.delete_msg.disabled = False
def delete_draft(self, data_index, instance, *args):
"""Delete draft message permanently"""
sqlExecute("DELETE FROM sent WHERE ackdata = ?;", data_index)
if int(state.draft_count) > 0:
state.draft_count = str(int(state.draft_count) - 1)
self.set_draftCnt(state.draft_count)
if int(state.draft_count) <= 0:
# self.ids.identi_tag.children[0].text = ''
self.ids.tag_label.text = ''
self.ids.ml.remove_widget(instance.parent.parent)
toast('Deleted')
@staticmethod
def draft_msg(src_object):
"""Save draft mails"""
composer_object = state.kivyapp.root.ids.sc3.children[1].ids
fromAddress = str(composer_object.ti.text)
toAddress = str(composer_object.txt_input.text)
subject = str(composer_object.subject.text)
message = str(composer_object.body.text)
encoding = 3
sendMessageToPeople = True
if sendMessageToPeople:
streamNumber, ripe = decodeAddress(toAddress)[2:]
from addresses import addBMIfNotPresent
toAddress = addBMIfNotPresent(toAddress)
stealthLevel = BMConfigParser().safeGetInt(
'bitmessagesettings', 'ackstealthlevel')
from helper_ackPayload import genAckPayload
ackdata = genAckPayload(streamNumber, stealthLevel)
sqlExecute(
'''INSERT INTO sent VALUES
(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''',
'',
toAddress,
ripe,
fromAddress,
subject,
message,
ackdata,
int(time.time()),
int(time.time()),
0,
'msgqueued',
0,
'draft',
encoding,
BMConfigParser().safeGetInt('bitmessagesettings', 'ttl'))
state.msg_counter_objs = src_object.children[2].children[0].ids
state.draft_count = str(int(state.draft_count) + 1) \
if state.association == fromAddress else state.draft_count
src_object.ids.sc16.clear_widgets()
src_object.ids.sc16.add_widget(Draft())
toast('Save draft')
return

View File

@ -0,0 +1,246 @@
# from bitmessagekivy.get_platform import platform
# from bitmessagekivy import identiconGeneration
from bitmessagekivy import kivy_helper_search
from bmconfigparser import BMConfigParser
from helper_sql import sqlExecute
from functools import partial
from kivy.clock import Clock
from kivy.metrics import dp
from kivy.properties import (
ListProperty,
StringProperty
)
from kivy.uix.screenmanager import Screen
from kivymd.uix.label import MDLabel
import state
from bitmessagekivy.baseclass.common import (
showLimitedCnt, avatarImageFirstLetter,
ThemeClsColor, toast, SwipeToDeleteItem,
ShowTimeHistoy
)
from bitmessagekivy.baseclass.maildetail import MailDetail
from bitmessagekivy.baseclass.trash import Trash
class Inbox(Screen):
"""Inbox Screen class for kivy Ui"""
queryreturn = ListProperty()
has_refreshed = True
account = StringProperty()
def __init__(self, *args, **kwargs):
"""Method Parsing the address"""
super(Inbox, self).__init__(*args, **kwargs)
Clock.schedule_once(self.init_ui, 0)
@staticmethod
def set_defaultAddress():
"""This method set's default address"""
if state.association == "":
if state.kivyapp.variable_1:
state.association = state.kivyapp.variable_1[0]
def init_ui(self, dt=0):
"""Clock schdule for method inbox accounts"""
self.loadMessagelist()
def loadMessagelist(self, where="", what=""):
"""Load Inbox list for Inbox messages"""
self.set_defaultAddress()
self.account = state.association
if state.searcing_text:
# self.children[2].children[0].children[0].scroll_y = 1.0
self.ids.scroll_y.scroll_y = 1.0
where = ["subject", "message"]
what = state.searcing_text
xAddress = "toaddress"
data = []
self.ids.tag_label.text = ""
self.inboxDataQuery(xAddress, where, what)
self.ids.tag_label.text = ""
if self.queryreturn:
self.ids.tag_label.text = "Inbox"
state.kivyapp.get_inbox_count()
self.set_inboxCount(state.inbox_count)
for mail in self.queryreturn:
# third_text = mail[3].replace('\n', ' ')
body = mail[3].decode() if isinstance(mail[3], bytes) else mail[3]
subject = mail[5].decode() if isinstance(mail[5], bytes) else mail[5]
data.append(
{
"text": mail[4].strip(),
"secondary_text": (
subject[:50] + "........"
if len(subject) >= 50
else (subject + "," + body)[0:50] + "........"
)
.replace("\t", "")
.replace(" ", ""),
"msgid": mail[1],
"received": mail[6]
}
)
self.has_refreshed = True
self.set_mdList(data)
self.ids.scroll_y.bind(scroll_y=self.check_scroll_y)
else:
self.set_inboxCount("0")
content = MDLabel(
font_style="Caption",
theme_text_color="Primary",
text="No message found!"
if state.searcing_text
else "yet no message for this account!!!!!!!!!!!!!",
halign="center",
size_hint_y=None,
valign="top"
)
self.ids.ml.add_widget(content)
def set_inboxCount(self, msgCnt): # pylint: disable=no-self-use
"""This method is used to sent inbox message count"""
src_mng_obj = state.kivyapp.root.ids.content_drawer.ids
src_mng_obj.inbox_cnt.ids.badge_txt.text = showLimitedCnt(int(msgCnt))
state.kivyapp.get_sent_count()
state.all_count = str(
int(state.sent_count) + int(state.inbox_count))
src_mng_obj.allmail_cnt.ids.badge_txt.text = showLimitedCnt(int(state.all_count))
def inboxDataQuery(self, xAddress, where, what, start_indx=0, end_indx=20):
"""This method is used for retrieving inbox data"""
self.queryreturn = kivy_helper_search.search_sql(
xAddress, self.account, "inbox", where, what, False, start_indx, end_indx
)
def set_mdList(self, data):
"""This method is used to create the mdList"""
total_message = len(self.ids.ml.children)
for item in data:
message_row = SwipeToDeleteItem(
text=item["text"],
)
listItem = message_row.ids.content
listItem.secondary_text = item["secondary_text"]
listItem.theme_text_color = "Custom"
listItem.text_color = ThemeClsColor
listItem._txt_right_pad = dp(70)
image = state.imageDir + "/text_images/{}.png".format(
avatarImageFirstLetter(item["secondary_text"].strip()))
message_row.ids.avater_img.source = image
listItem.bind(on_release=partial(self.inbox_detail, item["msgid"], message_row))
message_row.ids.time_tag.text = str(ShowTimeHistoy(item["received"]))
message_row.ids.delete_msg.bind(on_press=partial(self.delete, item["msgid"]))
self.ids.ml.add_widget(message_row)
update_message = len(self.ids.ml.children)
self.has_refreshed = True if total_message != update_message else False
def check_scroll_y(self, instance, somethingelse):
"""Loads data on scroll"""
if self.ids.scroll_y.scroll_y <= -0.0 and self.has_refreshed:
self.ids.scroll_y.scroll_y = 0.06
total_message = len(self.ids.ml.children)
self.update_inbox_screen_on_scroll(total_message)
def update_inbox_screen_on_scroll(self, total_message, where="", what=""):
"""This method is used to load more data on scroll down"""
data = []
if state.searcing_text:
where = ["subject", "message"]
what = state.searcing_text
self.inboxDataQuery("toaddress", where, what, total_message, 5)
for mail in self.queryreturn:
# third_text = mail[3].replace('\n', ' ')
subject = mail[3].decode() if isinstance(mail[3], bytes) else mail[3]
body = mail[5].decode() if isinstance(mail[5], bytes) else mail[5]
data.append(
{
"text": mail[4].strip(),
"secondary_text": body[:50] + "........"
if len(body) >= 50
else (body + "," + subject.replace("\n", ""))[0:50] + "........",
"msgid": mail[1],
"received": mail[6]
}
)
self.set_mdList(data)
def inbox_detail(self, msg_id, instance, *args):
"""Load inbox page details"""
if instance.state == 'closed':
instance.ids.delete_msg.disabled = True
if instance.open_progress == 0.0:
state.detailPageType = "inbox"
state.mail_id = msg_id
if self.manager:
src_mng_obj = self.manager
else:
src_mng_obj = self.parent.parent
src_mng_obj.screens[11].clear_widgets()
src_mng_obj.screens[11].add_widget(MailDetail())
src_mng_obj.current = "mailDetail"
else:
instance.ids.delete_msg.disabled = False
def delete(self, data_index, instance, *args):
"""Delete inbox mail from inbox listing"""
sqlExecute("UPDATE inbox SET folder = 'trash' WHERE msgid = ?;", data_index)
msg_count_objs = self.parent.parent.ids.content_drawer.ids
if int(state.inbox_count) > 0:
msg_count_objs.inbox_cnt.ids.badge_txt.text = showLimitedCnt(
int(state.inbox_count) - 1
)
msg_count_objs.trash_cnt.ids.badge_txt.text = showLimitedCnt(
int(state.trash_count) + 1
)
state.inbox_count = str(int(state.inbox_count) - 1)
state.trash_count = str(int(state.trash_count) + 1)
if int(state.all_count) > 0:
msg_count_objs.allmail_cnt.ids.badge_txt.text = showLimitedCnt(
int(state.all_count) - 1
)
state.all_count = str(int(state.all_count) - 1)
if int(state.inbox_count) <= 0:
# self.ids.identi_tag.children[0].text = ''
self.ids.tag_label.text = ''
self.ids.ml.remove_widget(
instance.parent.parent)
toast('Deleted')
# self.update_trash()
def archive(self, data_index, instance, *args):
"""Archive inbox mail from inbox listing"""
sqlExecute("UPDATE inbox SET folder = 'trash' WHERE msgid = ?;", data_index)
self.ids.ml.remove_widget(instance.parent.parent)
self.update_trash()
def update_trash(self):
"""Update trash screen mails which is deleted from inbox"""
self.manager.parent.ids.sc5.clear_widgets()
self.manager.parent.ids.sc5.add_widget(Trash())
# try:
# self.parent.screens[4].clear_widgets()
# self.parent.screens[4].add_widget(Trash())
# except Exception:
# self.parent.parent.screens[4].clear_widgets()
# self.parent.parent.screens[4].add_widget(Trash())
def refresh_callback(self, *args):
"""Method updates the state of application,
While the spinner remains on the screen"""
def refresh_callback(interval):
"""Method used for loading the inbox screen data"""
state.searcing_text = ""
self.children[2].children[1].ids.search_field.text = ""
self.ids.ml.clear_widgets()
self.loadMessagelist(state.association)
self.has_refreshed = True
self.ids.refresh_layout.refresh_done()
self.tick = 0
Clock.schedule_once(refresh_callback, 1)

View File

@ -0,0 +1,111 @@
import queues
from bmconfigparser import BMConfigParser
from kivy.clock import Clock
from kivy.properties import StringProperty, BooleanProperty
from kivy.uix.boxlayout import BoxLayout
from kivymd.uix.behaviors.elevation import RectangularElevationBehavior
from kivy.uix.screenmanager import Screen
import state
from bitmessagekivy.baseclass.common import toast
class Login(Screen):
"""Login Screeen class for kivy Ui"""
# pylint: disable=too-few-public-methods
log_text1 = (
'You may generate addresses by using either random numbers'
' or by using a passphrase If you use a passphrase, the address'
' is called a deterministic; address The Random Number option is'
' selected by default but deterministic addresses have several pros'
' and cons:')
log_text2 = ('If talk about pros You can recreate your addresses on any computer'
' from memory, You need-not worry about backing up your keys.dat file'
' as long as you can remember your passphrase and aside talk about cons'
' You must remember (or write down) your You must remember the address'
' version number and the stream number along with your passphrase If you'
' choose a weak passphrase and someone on the Internet can brute-force it,'
' they can read your messages and send messages as you')
class Random(Screen):
"""Random Screen class for Ui"""
is_active = BooleanProperty(False)
checked = StringProperty("")
def generateaddress(self):
"""Method for Address Generator"""
# entered_label = str(self.ids.lab.text).strip()
entered_label = str(self.ids.add_random_bx.children[0].ids.lab.text).strip()
if not entered_label:
self.ids.add_random_bx.children[0].ids.lab.focus = True
streamNumberForAddress = 1
eighteenByteRipe = False
nonceTrialsPerByte = 1000
payloadLengthExtraBytes = 1000
lables = [BMConfigParser().get(obj, 'label')
for obj in BMConfigParser().addresses()]
if entered_label and entered_label not in lables:
toast('Address Creating...')
queues.addressGeneratorQueue.put((
'createRandomAddress', 4, streamNumberForAddress, entered_label, 1,
"", eighteenByteRipe, nonceTrialsPerByte,
payloadLengthExtraBytes))
self.parent.parent.ids.toolbar.opacity = 1
self.parent.parent.ids.toolbar.disabled = False
state.kivyapp.loadMyAddressScreen(True)
self.manager.current = 'myaddress'
Clock.schedule_once(self.address_created_callback, 6)
def address_created_callback(self, dt=0): # pylint: disable=unused-argument
"""New address created"""
state.kivyapp.loadMyAddressScreen(False)
state.kivyapp.root.ids.sc10.ids.ml.clear_widgets()
state.kivyapp.root.ids.sc10.is_add_created = True
state.kivyapp.root.ids.sc10.init_ui()
self.reset_address_spinner()
toast('New address created')
def reset_address_spinner(self):
"""reseting spinner address and UI"""
addresses = [addr for addr in BMConfigParser().addresses()
if BMConfigParser().get(str(addr), 'enabled') == 'true']
self.manager.parent.ids.content_drawer.ids.btn.values = []
self.manager.parent.ids.sc3.children[1].ids.btn.values = []
self.manager.parent.ids.content_drawer.ids.btn.values = addresses
self.manager.parent.ids.sc3.children[1].ids.btn.values = addresses
@staticmethod
def add_validation(instance):
"""Checking validation at address creation time"""
entered_label = str(instance.text.strip())
lables = [BMConfigParser().get(obj, 'label')
for obj in BMConfigParser().addresses()]
if entered_label in lables:
instance.error = True
instance.helper_text = 'it is already exist you'\
' can try this Ex. ( {0}_1, {0}_2 )'.format(
entered_label)
elif entered_label:
instance.error = False
else:
instance.error = False
instance.helper_text = 'This field is required'
def reset_address_label(self):
"""Resetting address labels"""
if not self.ids.add_random_bx.children:
self.ids.add_random_bx.add_widget(RandomBoxlayout())
class InfoLayout(BoxLayout, RectangularElevationBehavior):
"""InfoLayout class for kivy Ui"""
# pylint: disable=too-few-public-methods
class RandomBoxlayout(BoxLayout):
"""RandomBoxlayout class for BoxLayout behaviour"""
# pylint: disable=too-few-public-methods

View File

@ -0,0 +1,241 @@
from datetime import datetime
from bitmessagekivy.get_platform import platform
from helper_sql import sqlExecute, sqlQuery
from kivy.core.clipboard import Clipboard
from kivy.clock import Clock
from kivy.properties import (
StringProperty,
NumericProperty
)
from kivy.factory import Factory
from kivymd.uix.button import MDFlatButton, MDIconButton
from kivymd.uix.dialog import MDDialog
from kivymd.uix.list import (
OneLineListItem,
IRightBodyTouch
)
from kivy.uix.screenmanager import Screen
import state
from bitmessagekivy.baseclass.common import (
toast, avatarImageFirstLetter, ShowTimeHistoy
)
from bitmessagekivy.baseclass.popup import SenderDetailPopup
class OneLineListTitle(OneLineListItem):
"""OneLineListTitle class for kivy Ui"""
__events__ = ('on_long_press', )
long_press_time = NumericProperty(1)
def on_state(self, instance, value):
"""On state"""
if value == 'down':
lpt = self.long_press_time
self._clockev = Clock.schedule_once(self._do_long_press, lpt)
else:
self._clockev.cancel()
def _do_long_press(self, dt):
"""Do long press"""
self.dispatch('on_long_press')
def on_long_press(self, *largs):
"""On long press"""
self.copymessageTitle(self.text)
def copymessageTitle(self, title_text):
"""this method is for displaying dialog box"""
self.title_text = title_text
width = .8 if platform == 'android' else .55
self.dialog_box = MDDialog(
text=title_text,
size_hint=(width, .25),
buttons=[
MDFlatButton(
text="Copy", on_release=self.callback_for_copy_title
),
MDFlatButton(
text="Cancel", on_release=self.callback_for_copy_title,
),
],)
self.dialog_box.open()
def callback_for_copy_title(self, instance):
"""Callback of alert box"""
if instance.text == 'Copy':
Clipboard.copy(self.title_text)
self.dialog_box.dismiss()
toast(instance.text)
class IconRightSampleWidget(IRightBodyTouch, MDIconButton):
"""IconRightSampleWidget class for kivy Ui"""
class MailDetail(Screen): # pylint: disable=too-many-instance-attributes
"""MailDetail Screen class for kivy Ui"""
to_addr = StringProperty()
from_addr = StringProperty()
subject = StringProperty()
message = StringProperty()
status = StringProperty()
page_type = StringProperty()
time_tag = StringProperty()
avatarImg = StringProperty()
def __init__(self, *args, **kwargs):
"""Mail Details method"""
super(MailDetail, self).__init__(*args, **kwargs)
Clock.schedule_once(self.init_ui, 0)
def init_ui(self, dt=0):
"""Clock Schdule for method MailDetail mails"""
self.page_type = state.detailPageType if state.detailPageType else ''
try:
if state.detailPageType == 'sent' or state.detailPageType == 'draft':
data = sqlQuery(
"select toaddress, fromaddress, subject, message, status,"
" ackdata, senttime from sent where ackdata = ?;", state.mail_id)
state.status = self
state.ackdata = data[0][5]
self.assign_mail_details(data)
state.kivyapp.set_mail_detail_header()
elif state.detailPageType == 'inbox':
data = sqlQuery(
"select toaddress, fromaddress, subject, message, received from inbox"
" where msgid = ?;", state.mail_id)
self.assign_mail_details(data)
state.kivyapp.set_mail_detail_header()
except Exception as e:
print('Something wents wrong!!')
def assign_mail_details(self, data):
"""Assigning mail details"""
subject = data[0][2].decode() if isinstance(data[0][2], bytes) else data[0][2]
body = data[0][3].decode() if isinstance(data[0][2], bytes) else data[0][3]
self.to_addr = data[0][0] if len(data[0][0]) > 4 else ' '
self.from_addr = data[0][1]
self.subject = subject.capitalize(
) if subject.capitalize() else '(no subject)'
self.message = body
if len(data[0]) == 7:
self.status = data[0][4]
self.time_tag = ShowTimeHistoy(data[0][4]) if state.detailPageType == 'inbox' else ShowTimeHistoy(data[0][6])
self.avatarImg = state.imageDir + '/avatar.png' if state.detailPageType == 'draft' else (
state.imageDir + '/text_images/{0}.png'.format(avatarImageFirstLetter(self.subject.strip())))
self.timeinseconds = data[0][4] if state.detailPageType == 'inbox' else data[0][6]
def delete_mail(self):
"""Method for mail delete"""
msg_count_objs = state.kivyapp.root.ids.content_drawer.ids
state.searcing_text = ''
self.children[0].children[0].active = True
if state.detailPageType == 'sent':
state.kivyapp.root.ids.sc4.ids.sent_search.ids.search_field.text = ''
sqlExecute(
"UPDATE sent SET folder = 'trash' WHERE"
" ackdata = ?;", state.mail_id)
msg_count_objs.send_cnt.ids.badge_txt.text = str(int(state.sent_count) - 1)
state.sent_count = str(int(state.sent_count) - 1)
self.parent.screens[2].ids.ml.clear_widgets()
self.parent.screens[2].loadSent(state.association)
elif state.detailPageType == 'inbox':
state.kivyapp.root.ids.sc1.ids.inbox_search.ids.search_field.text = ''
sqlExecute(
"UPDATE inbox SET folder = 'trash' WHERE"
" msgid = ?;", state.mail_id)
msg_count_objs.inbox_cnt.ids.badge_txt.text = str(
int(state.inbox_count) - 1)
state.inbox_count = str(int(state.inbox_count) - 1)
self.parent.screens[0].ids.ml.clear_widgets()
self.parent.screens[0].loadMessagelist(state.association)
elif state.detailPageType == 'draft':
sqlExecute("DELETE FROM sent WHERE ackdata = ?;", state.mail_id)
msg_count_objs.draft_cnt.ids.badge_txt.text = str(
int(state.draft_count) - 1)
state.draft_count = str(int(state.draft_count) - 1)
self.parent.screens[13].clear_widgets()
self.parent.screens[13].add_widget(Factory.Draft())
if state.detailPageType != 'draft':
msg_count_objs.trash_cnt.ids.badge_txt.text = str(
int(state.trash_count) + 1)
msg_count_objs.allmail_cnt.ids.badge_txt.text = str(
int(state.all_count) - 1)
state.trash_count = str(int(state.trash_count) + 1)
state.all_count = str(int(state.all_count) - 1) if int(state.all_count) else '0'
self.parent.screens[3].clear_widgets()
self.parent.screens[3].add_widget(Factory.Trash())
self.parent.screens[14].clear_widgets()
self.parent.screens[14].add_widget(Factory.Allmails())
Clock.schedule_once(self.callback_for_delete, 4)
def callback_for_delete(self, dt=0):
"""Delete method from allmails"""
if state.detailPageType:
self.children[0].children[0].active = False
state.kivyapp.set_common_header()
self.parent.current = 'allmails' \
if state.is_allmail else state.detailPageType
state.detailPageType = ''
toast('Deleted')
def inbox_reply(self):
"""Reply inbox messages"""
state.in_composer = True
data = sqlQuery(
"select toaddress, fromaddress, subject, message, received from inbox where"
" msgid = ?;", state.mail_id)
composer_obj = self.parent.screens[1].children[1].ids
composer_obj.ti.text = data[0][0]
composer_obj.btn.text = data[0][0]
composer_obj.txt_input.text = data[0][1]
split_subject = data[0][2].split('Re:', 1)
composer_obj.subject.text = 'Re: ' + (split_subject[1] if len(split_subject) > 1 else split_subject[0])
time_obj = datetime.fromtimestamp(int(data[0][4]))
time_tag = time_obj.strftime("%d %b %Y, %I:%M %p")
# sender_name = BMConfigParser().get(data[0][1], 'label')
sender_name = data[0][1]
composer_obj.body.text = (
'\n\n --------------On ' + time_tag + ', ' + sender_name + ' wrote:--------------\n' + data[0][3])
composer_obj.body.focus = True
composer_obj.body.cursor = (0, 0)
state.kivyapp.root.ids.sc3.children[1].ids.rv.data = ''
self.parent.current = 'create'
state.kivyapp.set_navbar_for_composer()
def write_msg(self, navApp):
"""Write on draft mail"""
state.send_draft_mail = state.mail_id
data = sqlQuery(
"select toaddress, fromaddress, subject, message from sent where"
" ackdata = ?;", state.mail_id)
composer_ids = (
self.parent.parent.ids.sc3.children[1].ids)
composer_ids.ti.text = data[0][1]
composer_ids.btn.text = data[0][1]
composer_ids.txt_input.text = data[0][0]
composer_ids.subject.text = data[0][2] if data[0][2] != '(no subject)' else ''
composer_ids.body.text = data[0][3]
self.parent.current = 'create'
navApp.set_navbar_for_composer()
def detailedPopup(self):
"""Detailed popup"""
obj = SenderDetailPopup()
obj.open()
arg = (self.to_addr, self.from_addr, self.timeinseconds)
obj.assignDetail(*arg)
@staticmethod
def callback_for_menu_items(text_item, *arg):
"""Callback of alert box"""
toast(text_item)

View File

@ -0,0 +1,325 @@
import time
from bitmessagekivy.get_platform import platform
from bmconfigparser import BMConfigParser
from helper_sql import sqlExecute, sqlQuery
from kivy.clock import Clock
from kivy.core.window import Window
from kivy.factory import Factory
from kivy.properties import (
BooleanProperty,
ListProperty,
NumericProperty,
ObjectProperty,
)
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.boxlayout import BoxLayout
from kivymd.uix.button import MDFlatButton
from kivymd.uix.dialog import MDDialog
from kivy.uix.label import Label
from kivy.uix.recycleview import RecycleView
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.screenmanager import Screen
from kivy.uix.textinput import TextInput
from kivymd.uix.behaviors.hover_behavior import HoverBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.theming import ThemableBehavior
import state
import queues
from addresses import decodeAddress, addBMIfNotPresent
from bitmessagekivy.baseclass.common import (
toast, showLimitedCnt
)
from kivymd.uix.textfield import MDTextField
class Create(Screen):
"""Creates Screen class for kivy Ui"""
def __init__(self, **kwargs):
"""Getting Labels and address from addressbook"""
super(Create, self).__init__(**kwargs)
Window.softinput_mode = "below_target"
widget_1 = DropDownWidget()
widget_1.ids.txt_input.word_list = [
addr[1] for addr in sqlQuery(
"SELECT label, address from addressbook")]
widget_1.ids.txt_input.starting_no = 2
self.add_widget(widget_1)
self.children[0].ids.id_scroll.bind(scroll_y=self.check_scroll_y)
def check_scroll_y(self, instance, somethingelse): # pylint: disable=unused-argument
"""show data on scroll down"""
if self.children[1].ids.btn.is_open:
self.children[1].ids.btn.is_open = False
class RV(RecycleView):
"""Recycling View class for kivy Ui"""
def __init__(self, **kwargs):
"""Recycling Method"""
super(RV, self).__init__(**kwargs)
class SelectableRecycleBoxLayout(
FocusBehavior, LayoutSelectionBehavior, RecycleBoxLayout
):
"""Adds selection and focus behaviour to the view"""
# pylint: disable = duplicate-bases
class DropDownWidget(BoxLayout):
"""DropDownWidget class for kivy Ui"""
# pylint: disable=too-many-statements
txt_input = ObjectProperty()
rv = ObjectProperty()
def send(self, navApp):
"""Send message from one address to another"""
fromAddress = self.ids.ti.text.strip()
toAddress = self.ids.txt_input.text.strip()
subject = self.ids.subject.text.strip()
message = self.ids.body.text.strip()
print("message: ", self.ids.body.text)
if toAddress != "" and subject and message:
status, addressVersionNumber, streamNumber, ripe = decodeAddress(
toAddress
)
if status == "success":
navApp.root.ids.sc3.children[0].active = True
if state.detailPageType == "draft" and state.send_draft_mail:
sqlExecute(
"UPDATE sent SET toaddress = ?"
", fromaddress = ? , subject = ?"
", message = ?, folder = 'sent'"
", senttime = ?, lastactiontime = ?"
" WHERE ackdata = ?;",
toAddress,
fromAddress,
subject,
message,
int(time.time()),
int(time.time()),
state.send_draft_mail)
self.parent.parent.screens[13].clear_widgets()
self.parent.parent.screens[13].add_widget(Factory.Draft())
# state.detailPageType = ''
# state.send_draft_mail = None
else:
# toAddress = addBMIfNotPresent(toAddress)
if (addressVersionNumber > 4) or (
addressVersionNumber <= 1):
print(
"addressVersionNumber > 4"
" or addressVersionNumber <= 1")
if streamNumber > 1 or streamNumber == 0:
print("streamNumber > 1 or streamNumber == 0")
stealthLevel = BMConfigParser().safeGetInt(
'bitmessagesettings', 'ackstealthlevel')
from helper_ackPayload import genAckPayload
# ackdata = genAckPayload(streamNumber, stealthLevel)
# t = ()
sqlExecute(
'''INSERT INTO sent VALUES
(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''',
'',
addBMIfNotPresent(toAddress),
ripe,
fromAddress,
subject,
message,
genAckPayload(streamNumber, stealthLevel), #ackdata
int(time.time()),
int(time.time()),
0,
'msgqueued',
0,
'sent',
3, #encoding
BMConfigParser().safeGetInt(
'bitmessagesettings', 'ttl'))
state.check_sent_acc = fromAddress
# state.msg_counter_objs = self.parent.parent.parent.parent\
# .parent.parent.children[2].children[0].ids
if state.detailPageType == 'draft' \
and state.send_draft_mail:
state.draft_count = str(int(state.draft_count) - 1)
# state.msg_counter_objs.draft_cnt.badge_text = (
# state.draft_count)
state.detailPageType = ''
state.send_draft_mail = None
self.parent.parent.parent.ids.sc4.update_sent_messagelist()
allmailCnt_obj = state.kivyapp.root.ids.content_drawer.ids.allmail_cnt
allmailCnt_obj.ids.badge_txt.text = showLimitedCnt(int(state.all_count) + 1)
state.all_count = str(int(state.all_count) + 1)
Clock.schedule_once(self.callback_for_msgsend, 3)
queues.workerQueue.put(('sendmessage', addBMIfNotPresent(toAddress)))
print("sqlExecute successfully #######################")
state.in_composer = True
return
else:
msg = 'Enter a valid recipients address'
elif not toAddress:
msg = 'Please fill the form completely'
else:
msg = 'Please fill the form completely'
self.address_error_message(msg)
@staticmethod
def callback_for_msgsend(dt=0): # pylint: disable=unused-argument
"""Callback method for messagesend"""
state.kivyapp.root.ids.sc3.children[0].active = False
state.in_sent_method = True
state.kivyapp.back_press()
toast("sent")
@staticmethod
def address_error_message(msg):
"""Generates error message"""
width = .8 if platform == 'android' else .55
dialog_box = MDDialog(
text=msg,
size_hint=(width, .25),
buttons=[
MDFlatButton(
text="Ok", on_release=lambda x: callback_for_menu_items("Ok")
),
],)
dialog_box.open()
def callback_for_menu_items(text_item, *arg):
"""Callback of alert box"""
dialog_box.dismiss()
toast(text_item)
def reset_composer(self):
"""Method will reset composer"""
self.ids.ti.text = ""
self.ids.btn.text = "Select"
self.ids.txt_input.text = ""
self.ids.subject.text = ""
self.ids.body.text = ""
toast("Reset message")
def auto_fill_fromaddr(self):
"""Fill the text automatically From Address"""
self.ids.ti.text = self.ids.btn.text
self.ids.ti.focus = True
def is_camara_attached(self):
"""Checks the camera availability in device"""
self.parent.parent.parent.ids.sc23.check_camera()
is_available = self.parent.parent.parent.ids.sc23.camera_avaialbe
return is_available
@staticmethod
def camera_alert():
"""Show camera availability alert message"""
width = .8 if platform == 'android' else .55
altet_txt = 'Currently this feature is not avaialbe!'if platform == 'android' else 'Camera is not available!'
dialog_box = MDDialog(
text=altet_txt,
size_hint=(width, .25),
buttons=[
MDFlatButton(
text="Ok", on_release=lambda x: callback_for_menu_items("Ok")
),
],
)
dialog_box.open()
def callback_for_menu_items(text_item, *arg):
"""Callback of alert box"""
dialog_box.dismiss()
toast(text_item)
# class HoverItem(MDBoxLayout, ThemableBehavior, HoverBehavior):
# '''Custom item implementing hover behavior.'''
# def __init__(self, **kwargs):
# """Getting Text Input."""
# super(HoverItem, self).__init__(**kwargs)
# # import pdb; pdb.set_trace()
# def on_enter(self):
# # import pdb; pdb.set_trace()
# Window.set_system_cursor('hand')
# def on_leave(self):
# Window.set_system_cursor('arrow')
class MyTextInput(MDTextField):
"""MyTextInput class for kivy Ui"""
txt_input = ObjectProperty()
flt_list = ObjectProperty()
word_list = ListProperty()
starting_no = NumericProperty(3)
suggestion_text = ''
def __init__(self, **kwargs):
"""Getting Text Input."""
super(MyTextInput, self).__init__(**kwargs)
# import pdb; pdb.set_trace()
self.__lineBreak__ = 0
def on_text(self, instance, value): # pylint: disable=unused-argument
"""Find all the occurrence of the word"""
self.parent.parent.parent.parent.parent.ids.rv.data = []
matches = [self.word_list[i] for i in range(
len(self.word_list)) if self.word_list[
i][:self.starting_no] == value[:self.starting_no]]
display_data = []
for i in matches:
display_data.append({'text': i})
self.parent.parent.parent.parent.parent.ids.rv.data = display_data
if len(matches) <= 10:
self.parent.height = (250 + (len(matches) * 20))
else:
self.parent.height = 400
def keyboard_on_key_down(self, window, keycode, text, modifiers):
"""Keyboard on key Down"""
if self.suggestion_text and keycode[1] == 'tab':
self.insert_text(self.suggestion_text + ' ')
return True
return super(MyTextInput, self).keyboard_on_key_down(
window, keycode, text, modifiers)
class SelectableLabel(RecycleDataViewBehavior, Label):
"""Add selection support to the Label"""
index = None
selected = BooleanProperty(False)
selectable = BooleanProperty(True)
def refresh_view_attrs(self, rv, index, data):
"""Catch and handle the view changes"""
self.index = index
return super(SelectableLabel, self).refresh_view_attrs(rv, index, data)
def on_touch_down(self, touch): # pylint: disable=inconsistent-return-statements
"""Add selection on touch down"""
if super(SelectableLabel, self).on_touch_down(touch):
return True
if self.collide_point(*touch.pos) and self.selectable:
return self.parent.select_with_touch(self.index, touch)
def apply_selection(self, rv, index, is_selected):
"""Respond to the selection of items in the view"""
self.selected = is_selected
if is_selected:
print("selection changed to {0}".format(rv.data[index]))
rv.parent.txt_input.text = rv.parent.txt_input.text.replace(
rv.parent.txt_input.text, rv.data[index]["text"]
)

View File

@ -0,0 +1,251 @@
from bitmessagekivy.get_platform import platform
from functools import partial
from bmconfigparser import BMConfigParser
from kivy.clock import Clock
# from kivy.metrics import dp
from kivy.properties import (
ListProperty,
StringProperty
)
from kivymd.uix.button import MDFlatButton
from kivymd.uix.dialog import MDDialog
from kivymd.uix.label import MDLabel
from kivymd.uix.list import (
IRightBodyTouch,
TwoLineAvatarIconListItem,
)
from kivymd.uix.selectioncontrol import MDSwitch
from kivy.uix.screenmanager import Screen
import state
from bitmessagekivy.baseclass.common import (
avatarImageFirstLetter, AvatarSampleWidget, ThemeClsColor,
toast
)
from bitmessagekivy.baseclass.popup import MyaddDetailPopup
class ToggleBtn(IRightBodyTouch, MDSwitch):
"""ToggleBtn class for kivy Ui"""
class CustomTwoLineAvatarIconListItem(TwoLineAvatarIconListItem):
"""CustomTwoLineAvatarIconListItem class for kivy Ui"""
class BadgeText(IRightBodyTouch, MDLabel):
"""BadgeText class for kivy Ui"""
class MyAddress(Screen):
"""MyAddress screen class for kivy Ui"""
address_label = StringProperty()
text_address = StringProperty()
addresses_list = ListProperty()
has_refreshed = True
is_add_created = False
def __init__(self, *args, **kwargs):
"""Clock schdule for method Myaddress accounts"""
super(MyAddress, self).__init__(*args, **kwargs)
Clock.schedule_once(self.init_ui, 0)
def init_ui(self, dt=0):
"""Clock schdule for method Myaddress accounts"""
# pylint: disable=unnecessary-lambda, deprecated-lambda
# self.addresses_list = state.kivyapp.variable_1
self.addresses_list = BMConfigParser().addresses()
if state.searcing_text:
self.ids.refresh_layout.scroll_y = 1.0
filtered_list = [
x for x in BMConfigParser().addresses()
if self.filter_address(x)
]
self.addresses_list = filtered_list
self.addresses_list = [obj for obj in reversed(self.addresses_list)]
self.ids.tag_label.text = ''
if self.addresses_list:
self.ids.tag_label.text = 'My Addresses'
self.has_refreshed = True
self.set_mdList(0, 15)
self.ids.refresh_layout.bind(scroll_y=self.check_scroll_y)
else:
content = MDLabel(
font_style='Caption',
theme_text_color='Primary',
text="No address found!" if state.searcing_text
else "yet no address is created by user!!!!!!!!!!!!!",
halign='center',
size_hint_y=None,
valign='top')
self.ids.ml.add_widget(content)
if not state.searcing_text and not self.is_add_created:
try:
self.manager.current = 'login'
except Exception:
pass
def set_mdList(self, first_index, last_index):
"""Creating the mdlist"""
data = []
for address in self.addresses_list[first_index:last_index]:
data.append({
'text': BMConfigParser().get(address, 'label'),
'secondary_text': address})
for item in data:
is_enable = BMConfigParser().get(item['secondary_text'], 'enabled')
meny = CustomTwoLineAvatarIconListItem(
text=item['text'], secondary_text=item['secondary_text'],
theme_text_color='Custom' if is_enable == 'true' else 'Primary',
text_color=ThemeClsColor,)
# meny._txt_right_pad = dp(70)
try:
meny.canvas.children[3].rgba = [0, 0, 0, 0] if is_enable == 'true' else [0.5, 0.5, 0.5, 0.5]
except Exception:
pass
meny.add_widget(AvatarSampleWidget(
source=state.imageDir + '/text_images/{}.png'.format(
avatarImageFirstLetter(item['text'].strip()))))
meny.bind(on_press=partial(
self.myadd_detail, item['secondary_text'], item['text']))
if state.association == item['secondary_text'] and is_enable == 'true':
badge_obj = BadgeText(
size_hint=(None, None),
size=[90 if platform == 'android' else 50, 60],
text='Active', halign='center',
font_style='Body1', theme_text_color='Custom',
text_color=ThemeClsColor
)
badge_obj.font_size = '13sp'
meny.add_widget(badge_obj)
else:
meny.add_widget(ToggleBtn(active=True if is_enable == 'true' else False))
self.ids.ml.add_widget(meny)
def check_scroll_y(self, instance, somethingelse):
"""Load data on scroll down"""
if self.ids.refresh_layout.scroll_y <= -0.0 and self.has_refreshed:
self.ids.refresh_layout.scroll_y = 0.06
my_addresses = len(self.ids.ml.children)
if my_addresses != len(self.addresses_list):
self.update_addressBook_on_scroll(my_addresses)
self.has_refreshed = (
True if my_addresses != len(self.addresses_list) else False
)
def update_addressBook_on_scroll(self, my_addresses):
"""Loads more data on scroll down"""
self.set_mdList(my_addresses, my_addresses + 20)
# @staticmethod
def myadd_detail(self, fromaddress, label, *args):
"""Load myaddresses details"""
if BMConfigParser().get(fromaddress, 'enabled') == 'true':
obj = MyaddDetailPopup()
self.address_label = obj.address_label = label
self.text_address = obj.address = fromaddress
width = .9 if platform == 'android' else .6
self.myadddetail_popup = MDDialog(
type="custom",
size_hint=(width, .25),
content_cls=obj,
)
# self.myadddetail_popup.set_normal_height()
self.myadddetail_popup.auto_dismiss = False
self.myadddetail_popup.open()
# p.set_address(fromaddress, label)
else:
width = .8 if platform == 'android' else .55
dialog_box = MDDialog(
text='Address is not currently active. Please click on Toggle button to active it.',
size_hint=(width, .25),
buttons=[
MDFlatButton(
text="Ok", on_release=lambda x: callback_for_menu_items("Ok")
),
],
)
dialog_box.open()
def callback_for_menu_items(text_item, *arg):
"""Callback of alert box"""
dialog_box.dismiss()
toast(text_item)
# @staticmethod
# def callback_for_menu_items(text_item, *arg):
# """Callback of alert box"""
# toast(text_item)
def refresh_callback(self, *args):
"""Method updates the state of application,
While the spinner remains on the screen"""
def refresh_callback(interval):
"""Method used for loading the myaddress screen data"""
state.searcing_text = ''
# state.kivyapp.root.ids.sc10.children[2].active = False
self.ids.search_bar.ids.search_field.text = ''
self.has_refreshed = True
self.ids.ml.clear_widgets()
self.init_ui()
self.ids.refresh_layout.refresh_done()
# self.tick = 0
Clock.schedule_once(self.address_permision_callback, 0)
Clock.schedule_once(refresh_callback, 1)
@staticmethod
def filter_address(address):
"""Method will filter the my address list data"""
if [
x for x in [
BMConfigParser().get(address, 'label').lower(),
address.lower()
]
if (state.searcing_text).lower() in x
]:
return True
return False
def disableAddress(self, address, instance):
"""This method is use for disabling address"""
BMConfigParser().set(str(address), 'enabled', 'false')
BMConfigParser().save()
instance.parent.parent.theme_text_color = 'Primary'
instance.parent.parent.canvas.children[3].rgba = [0.5, 0.5, 0.5, 0.5]
# try:
# instance.parent.parent.canvas.children[6].rgba = [0.5, 0.5, 0.5, 0.5]
# except Exception:
# instance.parent.parent.canvas.children[9].rgba = [0.5, 0.5, 0.5, 0.5]
toast('Address disabled')
Clock.schedule_once(self.address_permision_callback, 0)
def enableAddress(self, address, instance):
"""This method is use for enabling address"""
BMConfigParser().set(address, 'enabled', 'true')
BMConfigParser().save()
instance.parent.parent.theme_text_color = 'Custom'
instance.parent.parent.canvas.children[3].rgba = [0, 0, 0, 0]
# try:
# instance.parent.parent.canvas.children[6].rgba = [0, 0, 0, 0]
# except Exception:
# instance.parent.parent.canvas.children[9].rgba = [0, 0, 0, 0]
toast('Address Enabled')
Clock.schedule_once(self.address_permision_callback, 0)
def address_permision_callback(self, dt=0):
"""callback for enable or disable addresses"""
addresses = [addr for addr in BMConfigParser().addresses()
if BMConfigParser().get(str(addr), 'enabled') == 'true']
self.parent.parent.ids.content_drawer.ids.btn.values = addresses
self.parent.parent.ids.sc3.children[1].ids.btn.values = addresses
state.kivyapp.variable_1 = addresses
def toggleAction(self, instance):
"""This method is used for enable or disable address"""
addr = instance.parent.parent.secondary_text
if instance.active:
self.enableAddress(addr, instance)
else:
self.disableAddress(addr, instance)

View File

@ -0,0 +1,40 @@
import state
from kivy.clock import Clock
from kivy.properties import StringProperty
from kivy.uix.screenmanager import Screen
from network import objectracker, stats
class NetworkStat(Screen):
"""NetworkStat class for kivy Ui"""
text_variable_1 = StringProperty(
'{0}::{1}'.format('Total Connections', '0'))
text_variable_2 = StringProperty(
'Processed {0} per-to-per messages'.format('0'))
text_variable_3 = StringProperty(
'Processed {0} brodcast messages'.format('0'))
text_variable_4 = StringProperty(
'Processed {0} public keys'.format('0'))
text_variable_5 = StringProperty(
'Processed {0} object to be synced'.format('0'))
def __init__(self, *args, **kwargs):
"""Init method for network stat"""
super(NetworkStat, self).__init__(*args, **kwargs)
Clock.schedule_interval(self.init_ui, 1)
def init_ui(self, dt=0):
"""Clock Schdule for method networkstat screen"""
self.text_variable_1 = '{0} :: {1}'.format(
'Total Connections', str(len(stats.connectedHostsList())))
self.text_variable_2 = 'Processed {0} per-to-per messages'.format(
str(state.numberOfMessagesProcessed))
self.text_variable_3 = 'Processed {0} brodcast messages'.format(
str(state.numberOfBroadcastsProcessed))
self.text_variable_4 = 'Processed {0} public keys'.format(
str(state.numberOfPubkeysProcessed))
self.text_variable_5 = '{0} object to be synced'.format(
len(objectracker.missingObjects))

View File

@ -0,0 +1,74 @@
'''
This is for pamyent related part
'''
from kivy.uix.boxlayout import BoxLayout
from kivymd.uix.behaviors.elevation import RectangularElevationBehavior
from kivy.uix.screenmanager import Screen
from kivymd.uix.label import MDLabel
from kivymd.uix.list import (
IRightBodyTouch,
OneLineAvatarIconListItem
)
# from bmconfigparser import BMConfigParser
from bitmessagekivy.baseclass.common import toast
# import queues
import state
class Payment(Screen):
"""Payment Screen class for kivy Ui"""
def get_free_credits(self, instance):
"""Get the available credits"""
# pylint: disable=no-self-use
state.availabe_credit = instance.parent.children[1].text
existing_credits = state.kivyapp.root.ids.sc18.ids.cred.text
if float(existing_credits.split()[1]) > 0:
toast(
'We already have added free coins'
' for the subscription to your account!')
else:
toast('Coins added to your account!')
state.kivyapp.root.ids.sc18.ids.cred.text = '{0}'.format(
state.availabe_credit)
@staticmethod
def create_hidden_payment_address():
"""This is basically used for creating hidden address used in payment for purchasing credits"""
# if BMConfigParser().paymentaddress():
# toast('hidden payment address already exist for buying subscription...')
# else:
# streamNumberForAddress = 1
# eighteenByteRipe = False
# nonceTrialsPerByte = 1000
# payloadLengthExtraBytes = 1000
# queues.addressGeneratorQueue.put((
# 'createPaymentAddress', 4, streamNumberForAddress, '', 1,
# "", eighteenByteRipe, nonceTrialsPerByte,
# payloadLengthExtraBytes))
# toast('hidden payment address Creating for buying subscription....')
class Category(BoxLayout, RectangularElevationBehavior):
"""Category class for kivy Ui"""
elevation_normal = .01
class ProductLayout(BoxLayout, RectangularElevationBehavior):
"""ProductLayout class for kivy Ui"""
elevation_normal = .01
class PaymentMethodLayout(BoxLayout):
"""PaymentMethodLayout class for kivy Ui"""
class ListItemWithLabel(OneLineAvatarIconListItem):
"""ListItemWithLabel class for kivy Ui"""
class RightLabel(IRightBodyTouch, MDLabel):
"""RightLabel class for kivy Ui"""

View File

@ -0,0 +1,230 @@
from bitmessagekivy.get_platform import platform
from bitmessagekivy import kivy_helper_search
from kivy.clock import Clock
from kivy.metrics import dp
from kivy.properties import StringProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.popup import Popup
import state
from addresses import decodeAddress
from datetime import datetime
from bitmessagekivy.baseclass.common import toast
class LoadingPopup(Popup):
"""LoadingPopup class for kivy Ui"""
def __init__(self, **kwargs):
super(LoadingPopup, self).__init__(**kwargs)
# call dismiss_popup in 2 seconds
Clock.schedule_once(self.dismiss_popup, 0.5)
def dismiss_popup(self, dt):
"""Dismiss popups"""
self.dismiss()
class GrashofPopup(BoxLayout):
"""GrashofPopup class for kivy Ui"""
valid = False
def __init__(self, **kwargs):
"""Grash of pop screen settings"""
super(GrashofPopup, self).__init__(**kwargs)
def checkAddress_valid(self, instance):
"""Checking address is valid or not"""
my_addresses = (
state.kivyapp.root.ids.content_drawer.ids.btn.values)
add_book = [addr[1] for addr in kivy_helper_search.search_sql(
folder="addressbook")]
entered_text = str(instance.text).strip()
if entered_text in add_book:
text = 'Address is already in the addressbook.'
elif entered_text in my_addresses:
text = 'You can not save your own address.'
elif entered_text:
text = self.addressChanged(entered_text)
if entered_text in my_addresses or entered_text in add_book:
self.ids.address.error = True
self.ids.address.helper_text = text
elif entered_text and self.valid:
self.ids.address.error = False
elif entered_text:
self.ids.address.error = True
self.ids.address.helper_text = text
else:
self.ids.address.error = False
self.ids.address.helper_text = 'This field is required'
def checkLabel_valid(self, instance):
"""Checking address label is unique or not"""
entered_label = instance.text.strip()
addr_labels = [labels[0] for labels in kivy_helper_search.search_sql(
folder="addressbook")]
if entered_label in addr_labels:
self.ids.label.error = True
self.ids.label.helper_text = 'label name already exists.'
elif entered_label:
self.ids.label.error = False
else:
self.ids.label.error = False
self.ids.label.helper_text = 'This field is required'
def _onSuccess(self, addressVersion, streamNumber, ripe):
pass
def addressChanged(self, addr):
"""Address validation callback, performs validation and gives feedback"""
status, addressVersion, streamNumber, ripe = decodeAddress(
str(addr))
self.valid = status == 'success'
if self.valid:
text = "Address is valid."
self._onSuccess(addressVersion, streamNumber, ripe)
elif status == 'missingbm':
text = "The address should start with ''BM-''"
elif status == 'checksumfailed':
text = (
"The address is not typed or copied correctly"
# " (the checksum failed)."
)
elif status == 'versiontoohigh':
text = (
"The version number of this address is higher than this"
" software can support. Please upgrade Bitmessage.")
elif status == 'invalidcharacters':
text = "The address contains invalid characters."
elif status == 'ripetooshort':
text = "Some data encoded in the address is too short."
elif status == 'ripetoolong':
text = "Some data encoded in the address is too long."
elif status == 'varintmalformed':
text = "Some data encoded in the address is malformed."
return text
class AddbookDetailPopup(BoxLayout):
"""AddbookDetailPopup class for kivy Ui"""
address_label = StringProperty()
address = StringProperty()
def __init__(self, **kwargs):
"""Set screen of address detail page"""
super(AddbookDetailPopup, self).__init__(**kwargs)
def checkLabel_valid(self, instance):
"""Checking address label is unique of not"""
entered_label = str(instance.text.strip())
address_list = kivy_helper_search.search_sql(folder="addressbook")
addr_labels = [labels[0] for labels in address_list]
add_dict = dict(address_list)
if self.address and entered_label in addr_labels \
and self.address != add_dict[entered_label]:
self.ids.add_label.error = True
self.ids.add_label.helper_text = 'label name already exists.'
elif entered_label:
self.ids.add_label.error = False
else:
self.ids.add_label.error = False
self.ids.add_label.helper_text = 'This field is required'
class MyaddDetailPopup(BoxLayout):
"""MyaddDetailPopup class for kivy Ui"""
address_label = StringProperty()
address = StringProperty()
def __init__(self, **kwargs):
"""My Address Details screen setting"""
super(MyaddDetailPopup, self).__init__(**kwargs)
def send_message_from(self):
"""Method used to fill from address of composer autofield"""
state.kivyapp.set_navbar_for_composer()
window_obj = state.kivyapp.root.ids
window_obj.sc3.children[1].ids.ti.text = self.address
window_obj.sc3.children[1].ids.btn.text = self.address
window_obj.sc3.children[1].ids.txt_input.text = ''
window_obj.sc3.children[1].ids.subject.text = ''
window_obj.sc3.children[1].ids.body.text = ''
window_obj.scr_mngr.current = 'create'
self.parent.parent.parent.dismiss()
# @staticmethod
def close_pop(self):
"""Pop is Canceled"""
self.parent.parent.parent.dismiss()
toast('Canceled')
class AppClosingPopup(Popup):
"""AppClosingPopup class for kivy Ui"""
def __init__(self, **kwargs):
super(AppClosingPopup, self).__init__(**kwargs)
def closingAction(self, text):
"""Action on closing window"""
if text == 'Yes':
print("*******************EXITING FROM APPLICATION*******************")
import shutdown
shutdown.doCleanShutdown()
else:
self.dismiss()
toast(text)
class SenderDetailPopup(Popup):
"""SenderDetailPopup class for kivy Ui"""
to_addr = StringProperty()
from_addr = StringProperty()
time_tag = StringProperty()
def __init__(self, **kwargs):
"""this metthod initialized the send message detial popup"""
super(SenderDetailPopup, self).__init__(**kwargs)
def assignDetail(self, to_addr, from_addr, timeinseconds):
"""Detailes assigned"""
self.to_addr = to_addr
self.from_addr = from_addr
time_obj = datetime.fromtimestamp(int(timeinseconds))
self.time_tag = time_obj.strftime("%d %b %Y, %I:%M %p")
device_type = 2 if platform == 'android' else 1.5
pop_height = 1.2 * device_type * (self.ids.sd_label.height + self.ids.dismiss_btn.height)
if len(to_addr) > 3:
self.height = pop_height
self.ids.to_addId.size_hint_y = None
self.ids.to_addId.height = 50
self.ids.to_addtitle.add_widget(ToAddressTitle())
frmaddbox = ToAddrBoxlayout()
frmaddbox.set_toAddress(to_addr)
self.ids.to_addId.add_widget(frmaddbox)
else:
self.ids.space_1.height = dp(0)
self.ids.space_2.height = dp(0)
self.ids.myadd_popup_box.spacing = dp(8 if platform == 'android' else 3)
self.height = pop_height / 1.2
class ToAddrBoxlayout(BoxLayout):
"""ToAddrBoxlayout class for kivy Ui"""
to_addr = StringProperty()
def set_toAddress(self, to_addr):
"""This method is use to set to address"""
self.to_addr = to_addr
class ToAddressTitle(BoxLayout):
"""ToAddressTitle class for BoxLayout behaviour"""

View File

@ -0,0 +1,20 @@
import state
from bitmessagekivy.baseclass.common import toast
from kivy.uix.screenmanager import Screen
from kivy.properties import StringProperty
from kivy_garden.qrcode import QRCodeWidget
class ShowQRCode(Screen):
"""ShowQRCode Screen class for kivy Ui"""
address = StringProperty()
def qrdisplay(self, instasnce, address):
"""Method used for showing QR Code"""
self.ids.qr.clear_widgets()
state.kivyapp.set_toolbar_for_QrCode()
self.address = address
self.ids.qr.add_widget(QRCodeWidget(data=address))
self.ids.qr.children[0].show_border = False
instasnce.parent.parent.parent.dismiss()
toast('Show QR code')

View File

@ -0,0 +1,120 @@
from bitmessagekivy.get_platform import platform
import os
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.properties import (
BooleanProperty,
ObjectProperty,
StringProperty
)
from kivy.uix.screenmanager import Screen
# if platform != "android":
# from kivy.config import Config
# from kivy_garden.zbarcam import ZBarCam
# from pyzbar.pyzbar import ZBarSymbol
# Config.set("input", "mouse", "mouse, multitouch_on_demand")
# elif platform == "android":
# from jnius import autoclass, cast
# from android.runnable import run_on_ui_thread
# from android import python_act as PythonActivity
# Toast = autoclass("android.widget.Toast")
# String = autoclass("java.lang.String")
# CharSequence = autoclass("java.lang.CharSequence")
# context = PythonActivity.mActivity
# @run_on_ui_thread
# def show_toast(text, length):
# """Its showing toast on screen"""
# t = Toast.makeText(context, text, length)
# t.show()
class ScanScreen(Screen):
"""ScanScreen is for scaning Qr code"""
# pylint: disable=unused-argument
# pylint: disable=W0212
camera_avaialbe = BooleanProperty(False)
previous_open_screen = StringProperty()
pop_up_instance = ObjectProperty()
def __init__(self, *args, **kwargs):
"""Getting AddressBook Details"""
super(ScanScreen, self).__init__(*args, **kwargs)
self.check_camera()
def check_camera(self):
"""This method is used for checking camera avaibility"""
if platform != "android":
import cv2
cap = cv2.VideoCapture(0)
while(cap.isOpened()):
print('Camera is available!')
self.camera_avaialbe = True
break
else:
print("Camera is not available!")
self.camera_avaialbe = False
else:
self.camera_avaialbe = True
def get_screen(self, screen_name, instance=None):
"""This method is used for getting previous screen name"""
self.previous_open_screen = screen_name
if screen_name != 'composer':
self.pop_up_instance = instance
def on_pre_enter(self):
"""
on_pre_enter works little better on android
It affects screen transition on linux
"""
if not self.children:
tmp = Builder.load_file(
os.path.join(
os.path.dirname(os.path.dirname(__file__)), "kv/{}.kv").format("scanner")
)
self.add_widget(tmp)
if platform == "android":
Clock.schedule_once(self.start_camera, 0)
def on_enter(self):
"""
on_enter works better on linux
It creates a black screen on android until camera gets loaded
"""
# print(self.children)
if platform != "android":
# pass
Clock.schedule_once(self.start_camera, 0)
def on_leave(self):
"""this methos will call on leave"""
# pass
Clock.schedule_once(self.stop_camera, 0)
def start_camera(self, *args):
"""Its used for starting camera for scanning qrcode"""
self.xcam = self.children[0].ids.zbarcam.ids.xcamera
if platform == "android":
self.xcam.play = True
else:
Clock.schedule_once(self.open_cam, 0)
def stop_camera(self, *args):
"""Its used for stop the camera"""
self.xcam.play = False
if platform != "android":
self.xcam._camera._device.release()
def open_cam(self, *args):
"""It will open up the camera"""
if not self.xcam._camera._device.isOpened():
self.xcam._camera._device.open(self.xcam._camera._index)
self.xcam.play = True

View File

@ -0,0 +1,234 @@
# from bitmessagekivy import identiconGeneration
from bitmessagekivy import kivy_helper_search
from bmconfigparser import BMConfigParser
from functools import partial
from helper_sql import sqlExecute
from kivy.clock import Clock
from kivy.factory import Factory
from kivy.properties import StringProperty, ListProperty
from kivy.uix.screenmanager import Screen
from kivymd.uix.label import MDLabel
import state
from bitmessagekivy.baseclass.common import (
showLimitedCnt, ThemeClsColor, avatarImageFirstLetter,
toast, SwipeToDeleteItem, ShowTimeHistoy
)
from bitmessagekivy.baseclass.maildetail import MailDetail
class Sent(Screen):
"""Sent Screen class for kivy Ui"""
queryreturn = ListProperty()
has_refreshed = True
account = StringProperty()
def __init__(self, *args, **kwargs):
"""Association with the screen"""
super(Sent, self).__init__(*args, **kwargs)
if state.association == '':
if state.kivyapp.variable_1:
state.association = state.kivyapp.variable_1[0]
Clock.schedule_once(self.init_ui, 0)
def init_ui(self, dt=0):
"""Clock Schdule for method sent accounts"""
self.loadSent()
print(dt)
def loadSent(self, where="", what=""):
"""Load Sent list for Sent messages"""
self.account = state.association
if state.searcing_text:
self.ids.scroll_y.scroll_y = 1.0
where = ['subject', 'message']
what = state.searcing_text
xAddress = 'fromaddress'
data = []
self.ids.tag_label.text = ''
self.sentDataQuery(xAddress, where, what)
if self.queryreturn:
self.ids.tag_label.text = 'Sent'
self.set_sentCount(state.sent_count)
for mail in self.queryreturn:
data.append({
'text': mail[1].strip(),
'secondary_text': (mail[2][:50] + '........' if len(
mail[2]) >= 50 else (mail[2] + ',' + mail[3])[0:50] + '........').replace(
'\t', '').replace(' ', ''),
'ackdata': mail[5], 'senttime': mail[6]},)
self.set_mdlist(data, 0)
self.has_refreshed = True
self.ids.scroll_y.bind(scroll_y=self.check_scroll_y)
else:
self.set_sentCount('0')
content = MDLabel(
font_style='Caption',
theme_text_color='Primary',
text="No message found!" if state.searcing_text
else "yet no message for this account!!!!!!!!!!!!!",
halign='center',
size_hint_y=None,
valign='top')
self.ids.ml.add_widget(content)
def sentDataQuery(self, xAddress, where, what, start_indx=0, end_indx=20):
"""This method is used to retrieving data from sent table"""
self.queryreturn = kivy_helper_search.search_sql(
xAddress,
self.account,
'sent',
where,
what,
False,
start_indx,
end_indx)
def set_mdlist(self, data, set_index=0):
"""This method is used to create the mdList"""
total_sent_msg = len(self.ids.ml.children)
for item in data:
message_row = SwipeToDeleteItem(
text=item["text"],
)
listItem = message_row.ids.content
listItem.secondary_text = item["secondary_text"]
listItem.theme_text_color = "Custom"
listItem.text_color = ThemeClsColor
image = state.imageDir + '/text_images/{}.png'.format(
avatarImageFirstLetter(item['secondary_text'].strip()))
message_row.ids.avater_img.source = image
listItem.bind(on_release=partial(self.sent_detail, item['ackdata'], message_row))
message_row.ids.time_tag.text = str(ShowTimeHistoy(item['senttime']))
message_row.ids.delete_msg.bind(on_press=partial(self.delete, item["ackdata"]))
self.ids.ml.add_widget(message_row, index=set_index)
updated_msgs = len(self.ids.ml.children)
self.has_refreshed = True if total_sent_msg != updated_msgs else False
def update_sent_messagelist(self):
"""This method is used to update screen when new mail is sent"""
self.account = state.association
if len(self.ids.ml.children) < 3:
self.ids.ml.clear_widgets()
self.loadSent()
if state.association == state.check_sent_acc:
total_sent = int(state.sent_count) + 1
state.sent_count = str(int(state.sent_count) + 1)
self.set_sentCount(total_sent)
else:
total_sent = int(state.sent_count)
else:
data = []
self.sentDataQuery('fromaddress', '', '', 0, 1)
if state.association == state.check_sent_acc:
total_sent = int(state.sent_count) + 1
state.sent_count = str(int(state.sent_count) + 1)
self.set_sentCount(total_sent)
else:
total_sent = int(state.sent_count)
for mail in self.queryreturn:
data.append({
'text': mail[1].strip(),
'secondary_text': (mail[2][:50] + '........' if len(
mail[2]) >= 50 else (mail[2] + ',' + mail[3])[0:50] + '........').replace(
'\t', '').replace(' ', ''),
'ackdata': mail[5], 'senttime': mail[6]})
self.set_mdlist(data, total_sent - 1)
if state.msg_counter_objs and state.association == (
state.check_sent_acc):
state.all_count = str(int(state.all_count) + 1)
state.msg_counter_objs.allmail_cnt.badge_text = state.all_count
state.check_sent_acc = None
def check_scroll_y(self, instance, somethingelse):
"""Load data on scroll down"""
if self.ids.scroll_y.scroll_y <= -0.0 and self.has_refreshed:
self.ids.scroll_y.scroll_y = 0.06
total_sent_msg = len(self.ids.ml.children)
self.update_sent_screen_on_scroll(total_sent_msg)
def update_sent_screen_on_scroll(self, total_sent_msg, where="", what=""):
"""This method is used to load more data on scroll down"""
if state.searcing_text:
where = ['subject', 'message']
what = state.searcing_text
self.sentDataQuery('fromaddress', where, what, total_sent_msg, 5)
data = []
for mail in self.queryreturn:
data.append({
'text': mail[1].strip(),
'secondary_text': mail[2][:50] + '........' if len(
mail[2]) >= 50 else (mail[2] + ',' + mail[3].replace(
'\n', ''))[0:50] + '........',
'ackdata': mail[5], 'senttime': mail[6]})
self.set_mdlist(data, 0)
@staticmethod
def set_sentCount(total_sent):
"""Set the total no. of sent message count"""
src_mng_obj = state.kivyapp.root.ids.content_drawer.ids.send_cnt
state.kivyapp.root.ids.content_drawer.ids.send_cnt.ids.badge_txt.text
if state.association:
src_mng_obj.ids.badge_txt.text = showLimitedCnt(int(total_sent))
else:
src_mng_obj.ids.badge_txt.text = '0'
def sent_detail(self, ackdata, instance, *args):
"""Load sent mail details"""
if instance.state == 'closed':
instance.ids.delete_msg.disabled = True
if instance.open_progress == 0.0:
state.detailPageType = 'sent'
state.mail_id = ackdata
if self.manager:
src_mng_obj = self.manager
else:
src_mng_obj = self.parent.parent
src_mng_obj.screens[11].clear_widgets()
src_mng_obj.screens[11].add_widget(MailDetail())
src_mng_obj.current = 'mailDetail'
else:
instance.ids.delete_msg.disabled = False
def delete(self, data_index, instance, *args):
"""Delete sent mail from sent mail listing"""
msg_count_objs = self.parent.parent.ids.content_drawer.ids
if int(state.sent_count) > 0:
msg_count_objs.send_cnt.ids.badge_txt.text = showLimitedCnt(int(state.sent_count) - 1)
msg_count_objs.trash_cnt.ids.badge_txt.text = showLimitedCnt(int(state.trash_count) + 1)
msg_count_objs.allmail_cnt.ids.badge_txt.text = showLimitedCnt(int(state.all_count) - 1)
state.sent_count = str(int(state.sent_count) - 1)
state.trash_count = str(int(state.trash_count) + 1)
state.all_count = str(int(state.all_count) - 1)
if int(state.sent_count) <= 0:
self.ids.tag_label.text = ''
sqlExecute(
"UPDATE sent SET folder = 'trash'"
" WHERE ackdata = ?;", data_index)
self.ids.ml.remove_widget(instance.parent.parent)
toast('Deleted')
def archive(self, data_index, instance, *args):
"""Archive sent mail from sent mail listing"""
sqlExecute(
"UPDATE sent SET folder = 'trash'"
" WHERE ackdata = ?;", data_index)
self.ids.ml.remove_widget(instance.parent.parent)
self.update_trash()
def update_trash(self):
"""Update trash screen mails which is deleted from inbox"""
try:
self.parent.screens[3].clear_widgets()
self.parent.screens[3].add_widget(Factory.Trash())
# self.parent.screens[14].clear_widgets()
# self.parent.screens[14].add_widget(Factory.Allmails())
except Exception:
self.parent.parent.screens[3].clear_widgets()
self.parent.parent.screens[3].add_widget(Factory.Trash())
# self.parent.parent.screens[14].clear_widgets()
# self.parent.parent.screens[14].add_widget(Factory.Allmails())

View File

@ -0,0 +1,91 @@
from kivy.uix.screenmanager import Screen
from kivy.uix.checkbox import CheckBox
class Setting(Screen):
"""Setting Screen for kivy Ui"""
# exp_text = "By default, if you send a message to someone and he is offline for more than two days, Bitmessage will\
# send the message again after an additional two days. This will be continued with exponential backoff\
# forever; messages will be resent after 5, 10, 20 days ect. until the receiver acknowledges them.\
# Here you may change that behavior by having Bitmessage give up after a certain number of days \
# or months."
# languages = {
# 'ar': 'Arabic',
# 'cs': 'Czech',
# 'da': 'Danish',
# 'de': 'German',
# 'en': 'English',
# 'eo': 'Esperanto',
# 'fr': 'French',
# 'it': 'Italian',
# 'ja': 'Japanese',
# 'nl': 'Dutch',
# 'no': 'Norwegian',
# 'pl': 'Polish',
# 'pt': 'Portuguese',
# 'ru': 'Russian',
# 'sk': 'Slovak',
# 'zh': 'Chinese',
# }
# newlocale = None
# def __init__(self, *args, **kwargs):
# """Trash method, delete sent message and add in Trash"""
# super(Setting, self).__init__(*args, **kwargs)
# if self.newlocale is None:
# self.newlocale = l10n.getTranslationLanguage()
# lang = locale.normalize(l10n.getTranslationLanguage())
# langs = [
# lang.split(".")[0] + "." + l10n.encoding,
# lang.split(".")[0] + "." + 'UTF-8',
# lang
# ]
# if 'win32' in platform or 'win64' in platform:
# langs = [l10n.getWindowsLocale(lang)]
# for lang in langs:
# try:
# l10n.setlocale(locale.LC_ALL, lang)
# if 'win32' not in platform and 'win64' not in platform:
# l10n.encoding = locale.nl_langinfo(locale.CODESET)
# else:
# l10n.encoding = locale.getlocale()[1]
# logger.info("Successfully set locale to %s", lang)
# break
# except:
# logger.error("Failed to set locale to %s", lang, exc_info=True)
# Clock.schedule_once(self.init_ui, 0)
# def init_ui(self, dt=0):
# """Initialization for Ui"""
# if self.newlocale is None:
# self.newlocale = l10n.getTranslationLanguage()
# # state.kivyapp.tr = Lang(self.newlocale)
# state.kivyapp.tr = Lang(self.newlocale)
# menu_items = [{"text": f"{i}"} for i in self.languages.values()]
# self.menu = MDDropdownMenu(
# caller=self.ids.dropdown_item,
# items=menu_items,
# position="auto",
# width_mult=3.5,
# )
# self.menu.bind(on_release=self.set_item)
# def set_item(self, instance_menu, instance_menu_item):
# self.ids.dropdown_item.set_item(instance_menu_item.text)
# instance_menu.dismiss()
# def change_language(self):
# lang = self.ids.dropdown_item.current_item
# for k, v in self.languages.items():
# if v == lang:
# BMConfigParser().set('bitmessagesettings', 'userlocale', k)
# BMConfigParser().save()
# state.kivyapp.tr = Lang(k)
# self.children[0].active = True
# Clock.schedule_once(partial(self.language_callback, k), 1)
# def language_callback(self, lang, dt=0):
# self.children[0].active = False
# state.kivyapp.tr = Lang(lang)
# toast('Language changed')

View File

@ -0,0 +1,183 @@
from bitmessagekivy.get_platform import platform
from bmconfigparser import BMConfigParser
from helper_sql import sqlExecute, sqlQuery
from functools import partial
from kivy.clock import Clock
from kivy.properties import (
ListProperty,
StringProperty
)
from kivymd.uix.button import MDFlatButton
from kivymd.uix.dialog import MDDialog
from kivymd.uix.label import MDLabel
from kivy.uix.screenmanager import Screen
import state
from bitmessagekivy.baseclass.common import (
toast, showLimitedCnt, ThemeClsColor,
CutsomSwipeToDeleteItem, ShowTimeHistoy,
avatarImageFirstLetter
)
class Trash(Screen):
"""Trash Screen class for kivy Ui"""
trash_messages = ListProperty()
has_refreshed = True
delete_index = None
table_name = StringProperty()
def __init__(self, *args, **kwargs):
"""Trash method, delete sent message and add in Trash"""
super(Trash, self).__init__(*args, **kwargs)
Clock.schedule_once(self.init_ui, 0)
def init_ui(self, dt=0):
"""Clock Schdule for method trash screen"""
if state.association == '':
if state.kivyapp.variable_1:
state.association = state.kivyapp.variable_1[0]
self.ids.tag_label.text = ''
self.trashDataQuery(0, 20)
if len(self.trash_messages):
self.ids.ml.clear_widgets()
self.ids.tag_label.text = 'Trash'
# src_mng_obj = state.kivyapp.root.children[2].children[0].ids
# src_mng_obj.trash_cnt.badge_text = state.trash_count
self.set_TrashCnt(state.trash_count)
self.set_mdList()
self.ids.scroll_y.bind(scroll_y=self.check_scroll_y)
else:
self.set_TrashCnt('0')
content = MDLabel(
font_style='Caption',
theme_text_color='Primary',
text="yet no trashed message for this account!!!!!!!!!!!!!",
halign='center',
size_hint_y=None,
valign='top')
self.ids.ml.add_widget(content)
def trashDataQuery(self, start_indx, end_indx):
"""Trash message query"""
self.trash_messages = sqlQuery(
"SELECT toaddress, fromaddress, subject, message,"
" folder ||',' || 'sent' as folder, ackdata As"
" id, DATE(senttime) As actionTime, senttime as msgtime FROM sent"
" WHERE folder = 'trash' and fromaddress = '{0}' UNION"
" SELECT toaddress, fromaddress, subject, message,"
" folder ||',' || 'inbox' as folder, msgid As id,"
" DATE(received) As actionTime, received as msgtime FROM inbox"
" WHERE folder = 'trash' and toaddress = '{0}'"
" ORDER BY actionTime DESC limit {1}, {2}".format(
state.association, start_indx, end_indx))
def set_TrashCnt(self, Count): # pylint: disable=no-self-use
"""This method is used to set trash message count"""
trashCnt_obj = state.kivyapp.root.ids.content_drawer.ids.trash_cnt
trashCnt_obj.ids.badge_txt.text = showLimitedCnt(int(Count))
def set_mdList(self):
"""This method is used to create the mdlist"""
total_trash_msg = len(self.ids.ml.children)
for item in self.trash_messages:
subject = item[2].decode() if isinstance(item[2], bytes) else item[2]
body = item[3].decode() if isinstance(item[3], bytes) else item[3]
message_row = CutsomSwipeToDeleteItem(
text=item[1],
)
message_row.bind(on_swipe_complete=partial(self.on_swipe_complete, message_row))
listItem = message_row.ids.content
listItem.secondary_text = (item[2][:50] + '........' if len(
subject) >= 50 else (subject + ',' + body)[0:50] + '........').replace('\t', '').replace(' ', '')
listItem.theme_text_color = "Custom"
listItem.text_color = ThemeClsColor
img_latter = state.imageDir + '/text_images/{}.png'.format(
avatarImageFirstLetter(subject[0].strip()))
message_row.ids.avater_img.source = img_latter
message_row.ids.time_tag.text = str(ShowTimeHistoy(item[7]))
message_row.ids.chip_tag.text = 'inbox 'if 'inbox' in item[4] else 'sent'
message_row.ids.delete_msg.bind(on_press=partial(
self.delete_permanently, item[5], item[4]))
self.ids.ml.add_widget(message_row)
self.has_refreshed = True if total_trash_msg != len(
self.ids.ml.children) else False
def on_swipe_complete(self, instance, *args):
"""call on swipe left"""
instance.ids.delete_msg.disabled = bool(instance.state == 'closed')
def check_scroll_y(self, instance, somethingelse):
"""Load data on scroll"""
if self.ids.scroll_y.scroll_y <= -0.0 and self.has_refreshed:
self.ids.scroll_y.scroll_y = 0.06
total_trash_msg = len(self.ids.ml.children)
self.update_trash_screen_on_scroll(total_trash_msg)
def update_trash_screen_on_scroll(self, total_trash_msg):
"""Load more data on scroll down"""
self.trashDataQuery(total_trash_msg, 5)
self.set_mdList()
def delete_permanently(self, data_index, folder, instance, *args):
"""Deleting trash mail permanently"""
self.table_name = folder.split(',')[1]
self.delete_index = data_index
self.delete_confirmation()
def callback_for_screen_load(self, dt=0):
"""This methos is for loading screen"""
self.ids.ml.clear_widgets()
self.init_ui(0)
self.children[1].active = False
toast('Message is permanently deleted')
def delete_confirmation(self):
"""Show confirmation delete popup"""
width = .8 if platform == 'android' else .55
dialog_box = MDDialog(
text='Are you sure you want to delete this'
' message permanently from trash?',
size_hint=(width, .25),
buttons=[
MDFlatButton(
text="Yes", on_release=lambda x: callback_for_delete_msg("Yes")
),
MDFlatButton(
text="No", on_release=lambda x: callback_for_delete_msg("No"),
),
],)
dialog_box.open()
def callback_for_delete_msg(text_item, *arg):
"""Getting the callback of alert box"""
if text_item == 'Yes':
self.delete_message_from_trash()
else:
toast(text_item)
dialog_box.dismiss()
# def callback_for_delete_msg(self, text_item, *arg):
# """Getting the callback of alert box"""
# if text_item == 'Yes':
# self.delete_message_from_trash()
# else:
# toast(text_item)
def delete_message_from_trash(self):
"""Deleting message from trash"""
self.children[1].active = True
if self.table_name == 'inbox':
sqlExecute(
"DELETE FROM inbox WHERE msgid = ?;", self.delete_index)
elif self.table_name == 'sent':
sqlExecute(
"DELETE FROM sent WHERE ackdata = ?;", self.delete_index)
if int(state.trash_count) > 0:
# msg_count_objs.trash_cnt.badge_text = str(
# int(state.trash_count) - 1)
self.set_TrashCnt(int(state.trash_count) - 1)
state.trash_count = str(int(state.trash_count) - 1)
Clock.schedule_once(self.callback_for_screen_load, 1)

View File

@ -0,0 +1,273 @@
"""
BMConfigParser class definition and default configuration settings
"""
import os
import shutil
import sys # FIXME: bad style! write more generally
from datetime import datetime
from six import string_types
from six.moves import configparser
try:
import state
from singleton import Singleton
except ImportError:
from pybitmessage import state
from pybitmessage.singleton import Singleton
SafeConfigParser = configparser.SafeConfigParser
BMConfigDefaults = {
"bitmessagesettings": {
"maxaddrperstreamsend": 500,
"maxbootstrapconnections": 20,
"maxdownloadrate": 0,
"maxoutboundconnections": 8,
"maxtotalconnections": 200,
"maxuploadrate": 0,
"apiinterface": "127.0.0.1",
"apiport": 8442,
"udp": "True"
},
"threads": {
"receive": 3,
},
"network": {
"bind": "",
"dandelion": 90,
},
"inventory": {
"storage": "sqlite",
"acceptmismatch": "False",
},
"knownnodes": {
"maxnodes": 20000,
},
"zlib": {
"maxsize": 1048576
}
}
@Singleton
class BMConfigParser(SafeConfigParser):
"""
Singleton class inherited from :class:`ConfigParser.SafeConfigParser`
with additional methods specific to bitmessage config.
"""
# pylint: disable=too-many-ancestors
_temp = {}
def set(self, section, option, value=None):
if self._optcre is self.OPTCRE or value:
if not isinstance(value, string_types):
raise TypeError("option values must be strings")
if not self.validate(section, option, value):
raise ValueError("Invalid value %s" % value)
return SafeConfigParser.set(self, section, option, value)
# pylint: disable=redefined-builtinm, too-many-return-statements
def get(self, section, option, raw=False, vars=None):
if sys.version_info[0] == 3:
# pylint: disable=arguments-differ
try:
if section == "bitmessagesettings" and option == "timeformat":
return SafeConfigParser.get(
self, section, option, raw=True, vars=vars)
try:
return self._temp[section][option]
except KeyError:
pass
return SafeConfigParser.get(
self, section, option, raw=True, vars=vars)
except configparser.InterpolationError:
return SafeConfigParser.get(
self, section, option, raw=True, vars=vars)
except (configparser.NoSectionError, configparser.NoOptionError) as e:
try:
return BMConfigDefaults[section][option]
except (KeyError, ValueError, AttributeError):
raise e
else:
# pylint: disable=arguments-differ
try:
if section == "bitmessagesettings" and option == "timeformat":
return SafeConfigParser.get(
self, section, option, raw, vars)
try:
return self._temp[section][option]
except KeyError:
pass
return SafeConfigParser.get(
self, section, option, True, vars)
except configparser.InterpolationError:
return SafeConfigParser.get(
self, section, option, True, vars)
except (configparser.NoSectionError, configparser.NoOptionError) as e:
try:
return BMConfigDefaults[section][option]
except (KeyError, ValueError, AttributeError):
raise e
def setTemp(self, section, option, value=None):
"""Temporary set option to value, not saving."""
try:
self._temp[section][option] = value
except KeyError:
self._temp[section] = {option: value}
def safeGetBoolean(self, section, field):
"""Return value as boolean, False on exceptions"""
try:
# Used in the python2.7
# return self.getboolean(section, field)
# Used in the python3.5.2
# print(config, section, field)
return self.getboolean(section, field)
except (configparser.NoSectionError, configparser.NoOptionError,
ValueError, AttributeError):
return False
def safeGetInt(self, section, field, default=0):
"""Return value as integer, default on exceptions,
0 if default missing"""
try:
# Used in the python2.7
# return self.getint(section, field)
# Used in the python3.7.0
return int(self.get(section, field))
except (configparser.NoSectionError, configparser.NoOptionError,
ValueError, AttributeError):
return default
def safeGetFloat(self, section, field, default=0.0):
"""Return value as float, default on exceptions,
0.0 if default missing"""
try:
return self.getfloat(section, field)
except (configparser.NoSectionError, configparser.NoOptionError,
ValueError, AttributeError):
return default
def safeGet(self, section, option, default=None):
"""Return value as is, default on exceptions, None if default missing"""
try:
return self.get(section, option)
except (configparser.NoSectionError, configparser.NoOptionError,
ValueError, AttributeError):
return default
def items(self, section, raw=False, variables=None):
# pylint: disable=signature-differs
"""Return section variables as parent,
but override the "raw" argument to always True"""
return SafeConfigParser.items(self, section, True, variables)
def _reset(self):
"""Reset current config. There doesn't appear to be a built in
method for this"""
sections = self.sections()
for x in sections:
self.remove_section(x)
if sys.version_info[0] == 3:
@staticmethod
def addresses(hidden=False):
"""Return a list of local bitmessage addresses (from section labels)"""
return [x for x in BMConfigParser().sections() if x.startswith('BM-') and (
hidden or not BMConfigParser().safeGetBoolean(x, 'hidden'))]
def read(self, filenames):
self._reset()
SafeConfigParser.read(self, filenames)
for section in self.sections():
for option in self.options(section):
try:
# pylint: disable=unsubscriptable-object
if not self.validate(
section, option,
self[section][option]
):
try:
newVal = BMConfigDefaults[section][option]
except configparser.NoSectionError:
continue
except KeyError:
continue
SafeConfigParser.set(
self, section, option, newVal)
except configparser.InterpolationError:
continue
def readfp(self, fp, filename=None):
# pylint: disable=no-member
SafeConfigParser.read_file(self, fp)
else:
@staticmethod
def addresses():
"""Return a list of local bitmessage addresses (from section labels)"""
return [
x for x in BMConfigParser().sections() if x.startswith('BM-')]
def read(self, filenames):
"""Read config and populate defaults"""
self._reset()
SafeConfigParser.read(self, filenames)
for section in self.sections():
for option in self.options(section):
try:
if not self.validate(
section, option,
SafeConfigParser.get(self, section, option)
):
try:
newVal = BMConfigDefaults[section][option]
except KeyError:
continue
SafeConfigParser.set(
self, section, option, newVal)
except configparser.InterpolationError:
continue
def save(self):
"""Save the runtime config onto the filesystem"""
fileName = os.path.join(state.appdata, 'keys.dat')
fileNameBak = '.'.join([
fileName, datetime.now().strftime("%Y%j%H%M%S%f"), 'bak'])
# create a backup copy to prevent the accidental loss due to
# the disk write failure
try:
shutil.copyfile(fileName, fileNameBak)
# The backup succeeded.
fileNameExisted = True
except (IOError, Exception):
# The backup failed. This can happen if the file
# didn't exist before.
fileNameExisted = False
with open(fileName, 'w') as configfile:
self.write(configfile)
# delete the backup
if fileNameExisted:
os.remove(fileNameBak)
def validate(self, section, option, value):
"""Input validator interface (using factory pattern)"""
try:
return getattr(self, 'validate_%s_%s' % (section, option))(value)
except AttributeError:
return True
@staticmethod
def validate_bitmessagesettings_maxoutboundconnections(value):
"""Reject maxoutboundconnections that are too high or too low"""
try:
value = int(value)
except ValueError:
return False
if value < 0 or value > 8:
return False
return True

View File

@ -0,0 +1,75 @@
"""
A thread for creating addresses
"""
from six.moves import queue
from pybitmessage import state
from pybitmessage import queues
from pybitmessage.bmconfigparser import BMConfigParser
from pybitmessage.network.threads import StoppableThread
fake_addresses = {
'BM-2cUgQGcTLWAkC6dNsv2Bc8XB3Y1GEesVLV': {
'privsigningkey': '5KWXwYq1oJMzghUSJaJoWPn8VdeBbhDN8zFot1cBd6ezKKReqBd',
'privencryptionkey': '5JaeFJs8iPcQT3N8676r3gHKvJ5mTWXy1VLhGCEDqRs4vpvpxV8'
},
'BM-2cUd2dm8MVMokruMTcGhhteTpyRZCAMhnA': {
'privsigningkey': '5JnJ79nkcwjo4Aj7iG8sFMkzYoQqWfpUjTcitTuFJZ1YKHZz98J',
'privencryptionkey': '5JXgNzTRouFLqSRFJvuHMDHCYPBvTeMPBiHt4Jeb6smNjhUNTYq'
},
'BM-2cWyvL54WytfALrJHZqbsDHca5QkrtByAW': {
'privsigningkey': '5KVE4gLmcfYVicLdgyD4GmnbBTFSnY7Yj2UCuytQqgBBsfwDhpi',
'privencryptionkey': '5JTw48CGm5CP8fyJUJQMq8HQANQMHDHp2ETUe1dgm6EFpT1egD7'
},
'BM-2cTE65PK9Y4AQEkCZbazV86pcQACocnRXd': {
'privsigningkey': '5KCuyReHx9MB4m5hhEyCWcLEXqc8rxhD1T2VWk8CicPFc8B6LaZ',
'privencryptionkey': '5KBRpwXdX3n2tP7f583SbFgfzgs6Jemx7qfYqhdH7B1Vhe2jqY6'
},
'BM-2cX5z1EgmJ87f2oKAwXdv4VQtEVwr2V3BG': {
'privsigningkey': '5K5UK7qED7F1uWCVsehudQrszLyMZxFVnP6vN2VDQAjtn5qnyRK',
'privencryptionkey': '5J5coocoJBX6hy5DFTWKtyEgPmADpSwfQTazMpU7QPeART6oMAu'
}
}
class addressGenerator(StoppableThread):
"""A thread for creating fake addresses"""
name = "addressGenerator"
address_list = list(fake_addresses.keys())
def stopThread(self):
""""To stop address generator thread"""
try:
queues.addressGeneratorQueue.put(("stopThread", "data"))
except queue.Full:
self.logger.warning('addressGeneratorQueue is Full')
super(addressGenerator, self).stopThread()
def run(self):
"""
Process the requests for addresses generation
from `.queues.addressGeneratorQueue`
"""
while state.shutdown == 0:
queueValue = queues.addressGeneratorQueue.get()
try:
address = self.address_list.pop(0)
label = queueValue[3]
BMConfigParser().add_section(address)
BMConfigParser().set(address, 'label', label)
BMConfigParser().set(address, 'enabled', 'true')
BMConfigParser().set(
address, 'privsigningkey', fake_addresses[address]['privsigningkey'])
BMConfigParser().set(
address, 'privencryptionkey', fake_addresses[address]['privencryptionkey'])
BMConfigParser().save()
queues.addressGeneratorQueue.task_done()
except IndexError:
self.logger.error(
'Program error: you can only create 5 fake addresses')

View File

@ -0,0 +1,52 @@
"""
The objectProcessor thread, of which there is only one,
processes the network objects
"""
import logging
import random
import threading
# import queues
from pybitmessage import state
# from helper_sql import sql_ready, sqlExecute, sqlQuery
# from network import bmproto
logger = logging.getLogger('default')
class objectProcessor(threading.Thread):
"""
The objectProcessor thread, of which there is only one, receives network
objects (msg, broadcast, pubkey, getpubkey) from the receiveDataThreads.
"""
def __init__(self):
threading.Thread.__init__(self, name="objectProcessor")
random.seed()
# It may be the case that the last time Bitmessage was running,
# the user closed it before it finished processing everything in the
# objectProcessorQueue. Assuming that Bitmessage wasn't closed
# forcefully, it should have saved the data in the queue into the
# objectprocessorqueue table. Let's pull it out.
# sql_ready.wait()
# queryreturn = sqlQuery(
# 'SELECT objecttype, data FROM objectprocessorqueue')
# for objectType, data in queryreturn:
# queues.objectProcessorQueue.put((objectType, data))
# sqlExecute('DELETE FROM objectprocessorqueue')
# logger.debug(
# 'Loaded %s objects from disk into the objectProcessorQueue.',
# len(queryreturn))
# self._ack_obj = bmproto.BMStringParser()
self.successfullyDecryptMessageTimings = []
def run(self):
"""Process the objects from `.queues.objectProcessorQueue`"""
while True:
# pylint: disable=unused-variable
# objectType, data = queues.objectProcessorQueue.get()
if state.shutdown:
state.shutdown = 2
break

View File

@ -0,0 +1,217 @@
"""
The `singleCleaner` class is a timer-driven thread that cleans data structures
to free memory, resends messages when a remote node doesn't respond, and
sends pong messages to keep connections alive if the network isn't busy.
It cleans these data structures in memory:
- inventory (moves data to the on-disk sql database)
- inventorySets (clears then reloads data out of sql database)
It cleans these tables on the disk:
- inventory (clears expired objects)
- pubkeys (clears pubkeys older than 4 weeks old which we have not used
personally)
- knownNodes (clears addresses which have not been online for over 3 days)
It resends messages when there has been no response:
- resends getpubkey messages in 5 days (then 10 days, then 20 days, etc...)
- resends msg messages in 5 days (then 10 days, then 20 days, etc...)
"""
# pylint: disable=relative-import, protected-access
import gc
import os
from datetime import datetime, timedelta
import time
# import knownnodes
from pybitmessage import queues
from pybitmessage import state
# import tr
from pybitmessage.bmconfigparser import BMConfigParser
from pybitmessage.helper_sql import sqlExecute, sqlQuery
from pybitmessage.inventory import Inventory
# from network.connectionpool import BMConnectionPool
from pybitmessage.network.threads import StoppableThread
#: Equals 4 weeks. You could make this longer if you want
#: but making it shorter would not be advisable because
#: there is a very small possibility that it could keep you
#: from obtaining a needed pubkey for a period of time.
lengthOfTimeToHoldOnToAllPubkeys = 2419200
class singleCleaner(StoppableThread):
"""The singleCleaner thread class"""
name = "singleCleaner"
cycleLength = 300
expireDiscoveredPeers = 300
def run(self): # pylint: disable=too-many-branches
gc.disable()
timeWeLastClearedInventoryAndPubkeysTables = 0
try:
state.maximumLengthOfTimeToBotherResendingMessages = (
float(BMConfigParser().get(
'bitmessagesettings', 'stopresendingafterxdays'))
* 24 * 60 * 60
) + (
float(BMConfigParser().get(
'bitmessagesettings', 'stopresendingafterxmonths'))
* (60 * 60 * 24 * 365) / 12)
except:
# Either the user hasn't set stopresendingafterxdays and
# stopresendingafterxmonths yet or the options are missing
# from the config file.
state.maximumLengthOfTimeToBotherResendingMessages = float('inf')
# initial wait
if state.shutdown == 0:
self.stop.wait(singleCleaner.cycleLength)
while state.shutdown == 0:
queues.UISignalQueue.put((
'updateStatusBar',
'Doing housekeeping (Flushing inventory in memory to disk...)'
))
Inventory().flush()
queues.UISignalQueue.put(('updateStatusBar', ''))
# If we are running as a daemon then we are going to fill up the UI
# queue which will never be handled by a UI. We should clear it to
# save memory.
# FIXME redundant?
if state.thisapp.daemon or not state.enableGUI:
queues.UISignalQueue.queue.clear()
if timeWeLastClearedInventoryAndPubkeysTables < \
int(time.time()) - 7380:
timeWeLastClearedInventoryAndPubkeysTables = int(time.time())
Inventory().clean()
queues.workerQueue.put(('sendOnionPeerObj', ''))
# pubkeys
sqlExecute(
"DELETE FROM pubkeys WHERE time<? AND usedpersonally='no'",
int(time.time()) - lengthOfTimeToHoldOnToAllPubkeys)
# Let us resend getpubkey objects if we have not yet heard
# a pubkey, and also msg objects if we have not yet heard
# an acknowledgement
queryreturn = sqlQuery(
"SELECT toaddress, ackdata, status FROM sent"
" WHERE ((status='awaitingpubkey' OR status='msgsent')"
" AND folder='sent' AND sleeptill<? AND senttime>?)",
int(time.time()), int(time.time())
- state.maximumLengthOfTimeToBotherResendingMessages
)
for row in queryreturn:
if len(row) < 2:
self.logger.error(
'Something went wrong in the singleCleaner thread:'
' a query did not return the requested fields. %r',
row
)
self.stop.wait(3)
break
toAddress, ackData, status = row
if status == 'awaitingpubkey':
self.resendPubkeyRequest(toAddress)
elif status == 'msgsent':
self.resendMsg(ackData)
deleteTrashMsgPermonantly()
# try:
# # Cleanup knownnodes and handle possible severe exception
# # while writing it to disk
# knownnodes.cleanupKnownNodes()
# except Exception as err:
# # pylint: disable=protected-access
# if "Errno 28" in str(err):
# self.logger.fatal(
# '(while writing knownnodes to disk)'
# ' Alert: Your disk or data storage volume is full.'
# )
# queues.UISignalQueue.put((
# 'alert',
# (tr._translate("MainWindow", "Disk full"),
# tr._translate(
# "MainWindow",
# 'Alert: Your disk or data storage volume'
# ' is full. Bitmessage will now exit.'),
# True)
# ))
# # FIXME redundant?
# if state.thisapp.daemon or not state.enableGUI:
# os._exit(1)
# inv/object tracking
# for connection in BMConnectionPool().connections():
# connection.clean()
# discovery tracking
exp = time.time() - singleCleaner.expireDiscoveredPeers
reaper = (k for k, v in state.discoveredPeers.items() if v < exp)
for k in reaper:
try:
del state.discoveredPeers[k]
except KeyError:
pass
# ..todo:: cleanup pending upload / download
gc.collect()
if state.shutdown == 0:
self.stop.wait(singleCleaner.cycleLength)
def resendPubkeyRequest(self, address):
"""Resend pubkey request for address"""
self.logger.debug(
'It has been a long time and we haven\'t heard a response to our'
' getpubkey request. Sending again.'
)
try:
# We need to take this entry out of the neededPubkeys structure
# because the queues.workerQueue checks to see whether the entry
# is already present and will not do the POW and send the message
# because it assumes that it has already done it recently.
del state.neededPubkeys[address]
except:
pass
queues.UISignalQueue.put((
'updateStatusBar',
'Doing work necessary to again attempt to request a public key...'
))
sqlExecute(
'''UPDATE sent SET status='msgqueued' WHERE toaddress=?''',
address)
queues.workerQueue.put(('sendmessage', ''))
def resendMsg(self, ackdata):
"""Resend message by ackdata"""
self.logger.debug(
'It has been a long time and we haven\'t heard an acknowledgement'
' to our msg. Sending again.'
)
sqlExecute(
'''UPDATE sent SET status='msgqueued' WHERE ackdata=?''',
ackdata)
queues.workerQueue.put(('sendmessage', ''))
queues.UISignalQueue.put((
'updateStatusBar',
'Doing work necessary to again attempt to deliver a message...'
))
def deleteTrashMsgPermonantly():
"""This method is used to delete old messages"""
ndays_before_time = datetime.now() - timedelta(days=30)
old_messages = time.mktime(ndays_before_time.timetuple())
sqlExecute(
"delete from sent where folder = 'trash' and lastactiontime <= ?;",
int(old_messages))
sqlExecute(
"delete from inbox where folder = 'trash' and received <= ?;",
int(old_messages))
return

View File

@ -0,0 +1,44 @@
"""
Thread for performing PoW
"""
from __future__ import division
from six.moves import queue
from pybitmessage import state
from pybitmessage import queues
from pybitmessage.network.threads import StoppableThread
class singleWorker(StoppableThread):
"""Thread for performing PoW"""
def __init__(self):
super(singleWorker, self).__init__(name="singleWorker")
self.busy = None
def stopThread(self):
"""Signal through the queue that the thread should be stopped"""
try:
queues.workerQueue.put(("stopThread", "data"))
except queue.Full:
self.logger.error('workerQueue is Full')
super(singleWorker, self).stopThread()
def run(self):
"""To run single worker thread"""
if state.shutdown > 0:
return
while state.shutdown == 0:
self.busy = 0
command, _ = queues.workerQueue.get()
self.busy = 1
if command == 'stopThread':
self.busy = 0
return
queues.workerQueue.task_done()
self.logger.info("Quitting...")

View File

@ -0,0 +1,144 @@
from datetime import datetime
from kivy.core.window import Window
from kivy.lang import Builder
from kivy.metrics import dp
from kivymd.uix.list import (
ILeftBody,
IRightBodyTouch,
)
from kivy.uix.image import Image
from kivymd.uix.label import MDLabel
from bitmessagekivy.get_platform import platform
from kivymd.toast import kivytoast
from kivymd.uix.card import MDCardSwipe
from kivymd.uix.chip import MDChip
from kivy.properties import (
NumericProperty,
StringProperty
)
ThemeClsColor = [0.12, 0.58, 0.95, 1]
data_screens = {
"MailDetail": {
"kv_string": "maildetail",
"Factory": "MailDetail()",
"name_screen": "mailDetail",
"object": 0,
"Import": "from bitmessagekivy.baseclass.maildetail import MailDetail",
},
}
def chipTag(text):
"""This method is used for showing chip tag"""
obj = MDChip()
# obj.size_hint = (None, None)
obj.size_hint = (0.16 if platform == "android" else 0.08, None)
obj.text = text
obj.icon = ""
obj.pos_hint = {
"center_x": 0.91 if platform == "android" else 0.94,
"center_y": 0.3
}
obj.height = dp(18)
obj.text_color = (1, 1, 1, 1)
obj.radius = [8]
return obj
# def initailize_detail_page(manager):
# if not manager.has_screen(
# data_screens['MailDetail']["name_screen"]
# ):
# Builder.load_file(
# os.path.join(
# # os.environ["KITCHEN_SINK_ROOT"],
# os.path.dirname(os.path.dirname(__file__)),
# "kv",
# "maildetail.kv",
# )
# )
# if "Import" in data_screens['MailDetail']:
# exec(data_screens['MailDetail']["Import"])
# screen_object = eval(data_screens['MailDetail']["Factory"])
# data_screens['MailDetail']["object"] = screen_object
# manager.add_widget(screen_object)
# manager.current = data_screens['MailDetail']["name_screen"]
def toast(text):
"""Method will display the toast message"""
kivytoast.toast(text)
def showLimitedCnt(total_msg):
"""This method set the total count limit in badge_text"""
return "99+" if total_msg > 99 else str(total_msg)
def avatarImageFirstLetter(letter_string):
"""This function is used to the first letter for the avatar image"""
try:
if letter_string[0].upper() >= 'A' and letter_string[0].upper() <= 'Z':
img_latter = letter_string[0].upper()
elif int(letter_string[0]) >= 0 and int(letter_string[0]) <= 9:
img_latter = letter_string[0]
else:
img_latter = '!'
except ValueError:
img_latter = '!'
return img_latter if img_latter else '!'
def AddTimeWidget(time): # pylint: disable=redefined-outer-name, W0201
"""This method is used to create TimeWidget"""
action_time = TimeTagRightSampleWidget(
text=str(ShowTimeHistoy(time)),
font_style="Caption",
size=[120, 140] if platform == "android" else [64, 80],
)
action_time.font_size = "11sp"
return action_time
def ShowTimeHistoy(act_time):
"""This method is used to return the message sent or receive time"""
action_time = datetime.fromtimestamp(int(act_time))
crnt_date = datetime.now()
duration = crnt_date - action_time
display_data = (
action_time.strftime("%d/%m/%Y")
if duration.days >= 365
else action_time.strftime("%I:%M %p").lstrip("0")
if duration.days == 0 and crnt_date.strftime("%d/%m/%Y") == action_time.strftime("%d/%m/%Y")
else action_time.strftime("%d %b")
)
return display_data
# pylint: disable=too-few-public-methods
class AvatarSampleWidget(ILeftBody, Image):
"""AvatarSampleWidget class for kivy Ui"""
class TimeTagRightSampleWidget(IRightBodyTouch, MDLabel):
"""TimeTagRightSampleWidget class for Ui"""
class SwipeToDeleteItem(MDCardSwipe):
"""Swipe delete class for App UI"""
text = StringProperty()
cla = Window.size[0] / 2
# cla = 800
swipe_distance = NumericProperty(cla)
opening_time = NumericProperty(0.5)
class CutsomSwipeToDeleteItem(MDCardSwipe):
"""Custom swipe delete class for App UI"""
text = StringProperty()
cla = Window.size[0] / 2
swipe_distance = NumericProperty(cla)
opening_time = NumericProperty(0.5)

View File

@ -0,0 +1,49 @@
from sys import platform as _sys_platform
from os import environ
"""
We need to check platform and set environ for KIVY_CAMERA, if requires, before importing kivy.
We cannot use sys.platform directly because it returns 'linux' on android devices as well.
We cannot use kivy.util.platform beacuse it imports kivy beforehand and thus setting environ
after that doesn't make any sense.
So we needed to copy the `_get_platform` function from kivy.utils
"""
def _get_platform():
# On Android sys.platform returns 'linux2', so prefer to check the
# existence of environ variables set during Python initialization
kivy_build = environ.get("KIVY_BUILD", "")
if kivy_build in {"android", "ios"}:
return kivy_build
elif "P4A_BOOTSTRAP" in environ:
return "android"
elif "ANDROID_ARGUMENT" in environ:
# We used to use this method to detect android platform,
# leaving it here to be backwards compatible with `pydroid3`
# and similar tools outside kivy's ecosystem
return "android"
elif _sys_platform in ("win32", "cygwin"):
return "win"
elif _sys_platform == "darwin":
return "macosx"
elif _sys_platform.startswith("linux"):
return "linux"
elif _sys_platform.startswith("freebsd"):
return "linux"
return "unknown"
platform = _get_platform()
if platform not in ("android", "unknown"):
"""
After tweaking a little bit with opencv camera, it's possible to make camera
go on and off as required while the app is still running.
Other camera provider such as `gi` has some issue upon closing the camera.
by setting KIVY_CAMERA environment variable before importing kivy, we are forcing it to use opencv camera provider.
"""
environ["KIVY_CAMERA"] = "opencv"

View File

@ -0,0 +1,72 @@
"""Convenience functions for random operations. Not suitable for security / cryptography operations."""
import os
import random
# from pyelliptic.openssl import OpenSSL
NoneType = type(None)
def seed():
"""Initialize random number generator"""
random.seed()
def randomBytes(n):
"""Method randomBytes."""
try:
return os.urandom(n)
except NotImplementedError:
# return OpenSSL.rand(n)
pass
def randomshuffle(population):
"""Method randomShuffle.
shuffle the sequence x in place.
shuffles the elements in list in place,
so they are in a random order.
As Shuffle will alter data in-place,
so its input must be a mutable sequence.
In contrast, sample produces a new list
and its input can be much more varied
(tuple, string, xrange, bytearray, set, etc)
"""
random.shuffle(population)
def randomsample(population, k):
"""Method randomSample.
return a k length list of unique elements
chosen from the population sequence.
Used for random sampling
without replacement, its called
partial shuffle.
"""
return random.sample(population, k)
def randomrandrange(x, y=None):
"""Method randomRandrange.
return a randomly selected element from
range(start, stop). This is equivalent to
choice(range(start, stop)),
but doesnt actually build a range object.
"""
if isinstance(y, NoneType):
return random.randrange(x) # nosec
return random.randrange(x, y) # nosec
def randomchoice(population):
"""Method randomchoice.
Return a random element from the non-empty
sequence seq. If seq is empty, raises
IndexError.
"""
return random.choice(population) # nosec

View File

@ -0,0 +1,124 @@
"""
SQL-related functions defined here are really pass the queries (or other SQL
commands) to :class:`.threads.sqlThread` through `sqlSubmitQueue` queue and check
or return the result got from `sqlReturnQueue`.
This is done that way because :mod:`sqlite3` is so thread-unsafe that they
won't even let you call it from different threads using your own locks.
SQLite objects can only be used from one thread.
.. note:: This actually only applies for certain deployments, and/or
really old version of sqlite. I haven't actually seen it anywhere.
Current versions do have support for threading and multiprocessing.
I don't see an urgent reason to refactor this, but it should be noted
in the comment that the problem is mostly not valid. Sadly, last time
I checked, there is no reliable way to check whether the library is
or isn't thread-safe.
"""
import threading
import queue as Queue
sqlSubmitQueue = Queue.Queue()
"""the queue for SQL"""
sqlReturnQueue = Queue.Queue()
"""the queue for results"""
sqlLock = threading.Lock()
def sqlQuery(sqlStatement, *args):
"""
Query sqlite and return results
:param str sqlStatement: SQL statement string
:param list args: SQL query parameters
:rtype: list
"""
sqlLock.acquire()
sqlSubmitQueue.put(sqlStatement)
if args == ():
sqlSubmitQueue.put('')
elif isinstance(args[0], (list, tuple)):
sqlSubmitQueue.put(args[0])
else:
sqlSubmitQueue.put(args)
queryreturn, _ = sqlReturnQueue.get()
sqlLock.release()
return queryreturn
def sqlExecuteChunked(sqlStatement, idCount, *args):
"""Execute chunked SQL statement to avoid argument limit"""
# SQLITE_MAX_VARIABLE_NUMBER,
# unfortunately getting/setting isn't exposed to python
sqlExecuteChunked.chunkSize = 999
if idCount == 0 or idCount > len(args):
return 0
totalRowCount = 0
with sqlLock:
for i in range(
len(args) - idCount, len(args),
sqlExecuteChunked.chunkSize - (len(args) - idCount)
):
chunk_slice = args[
i:i + sqlExecuteChunked.chunkSize - (len(args) - idCount)
]
sqlSubmitQueue.put(
sqlStatement.format(','.join('?' * len(chunk_slice)))
)
# first static args, and then iterative chunk
sqlSubmitQueue.put(
args[0:len(args) - idCount] + chunk_slice
)
retVal = sqlReturnQueue.get()
totalRowCount += retVal[1]
sqlSubmitQueue.put('commit')
return totalRowCount
def sqlExecute(sqlStatement, *args):
"""Execute SQL statement (optionally with arguments)"""
sqlLock.acquire()
sqlSubmitQueue.put(sqlStatement)
if args == ():
sqlSubmitQueue.put('')
else:
sqlSubmitQueue.put(args)
_, rowcount = sqlReturnQueue.get()
sqlSubmitQueue.put('commit')
sqlLock.release()
return rowcount
def sqlStoredProcedure(procName):
"""Schedule procName to be run"""
sqlLock.acquire()
sqlSubmitQueue.put(procName)
sqlLock.release()
class SqlBulkExecute(object): # pylint: disable=no-init
"""This is used when you have to execute the same statement in a cycle."""
def __enter__(self):
sqlLock.acquire()
return self
def __exit__(self, exc_type, value, traceback):
sqlSubmitQueue.put('commit')
sqlLock.release()
@staticmethod
def execute(sqlStatement, *args):
"""Used for statements that do not return results."""
sqlSubmitQueue.put(sqlStatement)
if args == ():
sqlSubmitQueue.put('')
else:
sqlSubmitQueue.put(args)
sqlReturnQueue.get()

View File

@ -0,0 +1,81 @@
"""
Core classes for loading images and converting them to a Texture.
The raw image data can be keep in memory for further access
"""
import hashlib
from io import BytesIO
from PIL import Image
from kivy.core.image import Image as CoreImage
from kivy.uix.image import Image as kiImage
# pylint: disable=import-error
# constants
RESOLUTION = 300, 300
V_RESOLUTION = 7, 7
BACKGROUND_COLOR = 255, 255, 255, 255
MODE = "RGB"
def generate(Generate_string=None):
"""Generating string"""
hash_string = generate_hash(Generate_string)
color = random_color(hash_string)
image = Image.new(MODE, V_RESOLUTION, BACKGROUND_COLOR)
image = generate_image(image, color, hash_string)
image = image.resize(RESOLUTION, 0)
data = BytesIO()
image.save(data, format='png')
data.seek(0)
# yes you actually need this
im = CoreImage(BytesIO(data.read()), ext='png')
beeld = kiImage()
# only use this line in first code instance
beeld.texture = im.texture
return beeld
# image.show()
def generate_hash(string):
"""Generating hash"""
try:
# make input case insensitive
string = str.lower(string)
hash_object = hashlib.md5(str.encode(string))
print(hash_object.hexdigest())
# returned object is a hex string
return hash_object.hexdigest()
except IndexError:
print("Error: Please enter a string as an argument.")
def random_color(hash_string):
"""Getting random color"""
# remove first three digits from hex string
split = 6
rgb = hash_string[:split]
split = 2
r = rgb[:split]
g = rgb[split:2 * split]
b = rgb[2 * split:3 * split]
color = (int(r, 16), int(g, 16), int(b, 16), 0xFF)
return color
def generate_image(image, color, hash_string):
"""Generating images"""
hash_string = hash_string[6:]
lower_x = 1
lower_y = 1
upper_x = int(V_RESOLUTION[0] / 2) + 1
upper_y = V_RESOLUTION[1] - 1
limit_x = V_RESOLUTION[0] - 1
index = 0
for x in range(lower_x, upper_x):
for y in range(lower_y, upper_y):
if int(hash_string[index], 16) % 2 == 0:
image.putpixel((x, y), color)
image.putpixel((limit_x - x, y), color)
index = index + 1
return image

View File

@ -0,0 +1,15 @@
"""The Inventory singleton"""
# TODO make this dynamic, and watch out for frozen, like with messagetypes
from pybitmessage.singleton import Singleton
# pylint: disable=old-style-class,too-few-public-methods
@Singleton
class Inventory():
"""
Inventory singleton class which uses storage backends
to manage the inventory.
"""
def __init__(self):
self.numberOfInventoryLookupsPerformed = 0

View File

@ -0,0 +1,26 @@
<AddressBook>:
name: 'addressbook'
BoxLayout:
orientation: 'vertical'
spacing: dp(5)
SearchBar:
id: address_search
GridLayout:
id: identi_tag
padding: [20, 0, 0, 5]
cols: 1
size_hint_y: None
height: self.minimum_height
MDLabel:
id: tag_label
text: ''
font_style: 'Subtitle2'
BoxLayout:
orientation:'vertical'
ScrollView:
id: scroll_y
do_scroll_x: False
MDList:
id: ml
Loader:
ComposerButton:

View File

@ -0,0 +1,25 @@
<Allmails>:
name: 'allmails'
BoxLayout:
orientation: 'vertical'
spacing: dp(5)
GridLayout:
id: identi_tag
padding: [20, 20, 0, 5]
spacing: dp(5)
cols: 1
size_hint_y: None
height: self.minimum_height
MDLabel:
id: tag_label
text: ''
font_style: 'Subtitle2'
BoxLayout:
orientation:'vertical'
ScrollView:
id: scroll_y
do_scroll_x: False
MDList:
id: ml
Loader:
ComposerButton:

View File

@ -0,0 +1,58 @@
<ChatList>:
name: 'chlist'
canvas.before:
Color:
rgba: 1,1,1,1
Rectangle:
pos: self.pos
size: self.size
MDTabs:
id: chat_panel
tab_display_mode:'text'
Tab:
text: app.tr._("Chats")
BoxLayout:
id: chat_box
orientation: 'vertical'
ScrollView:
id: scroll_y
do_scroll_x: False
MDList:
id: ml
MDLabel:
font_style: 'Caption'
theme_text_color: 'Primary'
text: app.tr._('No Chat')
halign: 'center'
size_hint_y: None
bold: True
valign: 'top'
# OneLineAvatarListItem:
# text: "Single-line item with avatar"
# divider: None
# _no_ripple_effect: True
# ImageLeftWidget:
# source: './images/text_images/A.png'
# OneLineAvatarListItem:
# text: "Single-line item with avatar"
# divider: None
# _no_ripple_effect: True
# ImageLeftWidget:
# source: './images/text_images/B.png'
# OneLineAvatarListItem:
# text: "Single-line item with avatar"
# divider: None
# _no_ripple_effect: True
# ImageLeftWidget:
# source: './images/text_images/A.png'
Tab:
text: app.tr._("Contacts")
BoxLayout:
id: contact_box
orientation: 'vertical'
ScrollView:
id: scroll_y
do_scroll_x: False
MDList:
id: ml

View File

@ -0,0 +1,45 @@
#:import C kivy.utils.get_color_from_hex
<ChatRoom>:
name: 'chroom'
BoxLayout:
orientation: 'vertical'
canvas.before:
Color:
rgba: 1,1,1,1
Rectangle:
pos: self.pos
size: self.size
ScrollView:
Label:
id: chat_logs
text: ''
color: C('#101010')
text_size: (self.width, None)
halign: 'left'
valign: 'top'
padding: (0, 0) # fixed in Kivy 1.8.1
size_hint: (1, None)
height: self.texture_size[1]
markup: True
font_size: sp(20)
BoxLayout:
height: 50
orientation: 'horizontal'
padding: 0
size_hint: (1, None)
TextInput:
id: message
size_hint: (1, 1)
multiline: False
font_size: sp(20)
on_text_validate: root.send_msg()
MDRaisedButton:
text: app.tr._("Send")
elevation_normal: 2
opposite_colors: True
size_hint: (0.3, 1)
pos_hint: {"center_x": .5}
on_press: root.send_msg()

View File

@ -0,0 +1,62 @@
<ArrowImg@Image>:
source: app.image_path +('/down-arrow.png' if self.parent.is_open == True else '/right-arrow.png')
size: 15, 15
x: self.parent.x + self.parent.width - self.width - 5
y: self.parent.y + self.parent.height/2 - self.height + 5
<SearchBar@BoxLayout>:
# id: search_bar
size_hint_y: None
height: self.minimum_height
MDIconButton:
icon: 'magnify'
MDTextField:
id: search_field
hint_text: 'Search'
on_text: app.searchQuery(self)
canvas.before:
Color:
rgba: (0,0,0,1)
<Loader@MDSpinner>:
id: spinner
size_hint: None, None
size: dp(46), dp(46)
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
active: False
<ComposerButton@BoxLayout>:
size_hint_y: None
height: dp(56)
spacing: '10dp'
pos_hint: {'center_x':0.45, 'center_y': .1}
Widget:
MDFloatingActionButton:
icon: 'plus'
opposite_colors: True
elevation_normal: 8
md_bg_color: [0.941, 0, 0,1]
on_press: app.root.ids.scr_mngr.current = 'create'
on_press: app.clear_composer()
<ToggleBtn>:
size_hint: None, None
size: dp(36), dp(48)
pos_hint: {'center_x': .95, 'center_y': .4}
on_active: app.root.ids.sc10.toggleAction(self)
<CustomTwoLineAvatarIconListItem>:
canvas:
Color:
id: set_clr
# rgba: 0.5, 0.5, 0.5, 0.5
rgba: 0,0,0,0
Rectangle: #woohoo!!!
size: self.size
pos: self.pos

View File

@ -0,0 +1,28 @@
<Credits>:
name: 'credits'
ScrollView:
do_scroll_x: False
BoxLayout:
size_hint_y: None
orientation: 'vertical'
OneLineListTitle:
id: cred
text: app.tr._("Available Credits")
divider: None
theme_text_color: 'Primary'
_no_ripple_effect: True
long_press_time: 1
OneLineListTitle:
id: cred
text: app.tr._(root.available_credits)
divider: None
font_style: 'H5'
theme_text_color: 'Primary'
_no_ripple_effect: True
long_press_time: 1
AnchorLayout:
MDRaisedButton:
height: dp(38)
text: app.tr._("+Add more credits")
on_press: app.root.ids.scr_mngr.current = 'payment'

View File

@ -0,0 +1,23 @@
<Draft>:
name: 'draft'
BoxLayout:
orientation: 'vertical'
spacing: dp(5)
GridLayout:
id: identi_tag
padding: [20, 20, 0, 5]
cols: 1
size_hint_y: None
height: self.minimum_height
MDLabel:
id: tag_label
text: ''
font_style: 'Subtitle2'
BoxLayout:
orientation:'vertical'
ScrollView:
id: scroll_y
do_scroll_x: False
MDList:
id: ml
ComposerButton:

View File

@ -0,0 +1,39 @@
<Inbox>:
name: 'inbox'
#transition: NoTransition()
BoxLayout:
orientation: 'vertical'
spacing: dp(5)
SearchBar:
id:inbox_search
GridLayout:
id: identi_tag
padding: [20, 0, 0, 5]
cols: 1
size_hint_y: None
height: self.minimum_height
MDLabel:
id: tag_label
text: ''
font_style: 'Subtitle2'
#FloatLayout:
# MDScrollViewRefreshLayout:
# id: refresh_layout
# refresh_callback: root.refresh_callback
# root_layout: root.set_root_layout()
# MDList:
# id: ml
BoxLayout:
orientation:'vertical'
ScrollView:
id: scroll_y
do_scroll_x: False
MDList:
id: ml
Loader:
ComposerButton:
<TimeTagRightSampleWidget>:
size_hint:(None, None)
font_style: 'Caption'
halign: 'center'

View File

@ -0,0 +1,264 @@
#:import SlideTransition kivy.uix.screenmanager.SlideTransition
<Login>:
name:"login"
BoxLayout:
orientation: "vertical"
#buttons-area-outer
BoxLayout:
size_hint_y: .53
canvas:
Color:
rgba: 1,1,1,1
Rectangle:
pos: self.pos
size: self.size
ScreenManager:
id: check_screenmgr
Screen:
name: "check_screen"
BoxLayout:
orientation: "vertical"
padding: 0, dp(5), 0, dp(5)
spacing: dp(5)
#label area
AnchorLayout:
size_hint_y: None
height: dp(50)
MDLabel:
text: app.tr._("Select method to make an address:")
bold: True
halign: "center"
theme_text_color: "Custom"
text_color: .4,.4,.4,1
#upper-checkbor-area
AnchorLayout:
size_hint_y: None
height: dp(40)
BoxLayout:
size_hint_x: None
width: self.minimum_width
#check-container
AnchorLayout:
size_hint_x: None
width: dp(40)
Check:
active: True
#text-container
AnchorLayout:
size_hint_x: None
width: dp(200)
MDLabel:
text: app.tr._("Random Number Generator")
AnchorLayout:
size_hint_y: None
height: dp(40)
BoxLayout:
size_hint_x: None
width: self.minimum_width
#check-container
AnchorLayout:
size_hint_x: None
width: dp(40)
Check:
#text-container
AnchorLayout:
size_hint_x: None
width: dp(200)
MDLabel:
text: app.tr._("Pseudo Number Generator")
AnchorLayout:
MDFillRoundFlatIconButton:
icon: "chevron-double-right"
text: app.tr._("Proceed Next")
on_release:
app.root.ids.scr_mngr.current = 'random'
on_press:
app.root.ids.sc7.reset_address_label()
#info-area-outer
BoxLayout:
size_hint_y: .47
padding: dp(7)
InfoLayout:
orientation:"vertical"
padding: 0, dp(5), 0, dp(5)
canvas:
Color:
rgba:1,1,1,1
Rectangle:
pos: self.pos
size: self.size
Color:
rgba: app.theme_cls.primary_color
Line:
rounded_rectangle: (self.pos[0]+4, self.pos[1]+4, self.width-8,self.height-8, 10, 10, 10, 10, 50)
width: dp(1)
ScreenManager:
id: info_screenmgr
Screen:
name: "info1"
ScrollView:
bar_width:0
do_scroll_x: False
BoxLayout:
orientation: "vertical"
size_hint_y: None
height: self.minimum_height
#note area
ContentHead:
section_name: "NOTE:"
ContentBody:
section_text: ("You may generate addresses by using either random numbers or by using a pass-phrase.If you use a pass-phrase, the address is called a deterministic address. The Random Number option is selected by default but deterministic addresses may have several pros and cons.")
#pros area
ContentHead:
section_name: "PROS:"
ContentBody:
section_text: ("You can re-create your addresses on any computer from memory you need-not-to worry about backing up your keys.dat file as long as you can remember your pass-phrase.")
#cons area
ContentHead:
section_name: "CONS:"
ContentBody:
section_text: ("You must remember (or write down) your address version number and the stream number along with your pass-phrase.If you choose a weak pass-phrase and someone on the internet can brute-force it, they can read your messages and send messages as you.")
<Random>:
name:"random"
ScrollView:
id:add_random_bx
<RandomBoxlayout>:
orientation: "vertical"
#buttons-area-outer
BoxLayout:
orientation: "vertical"
# padding: 0, dp(5), 0, dp(5)
# spacing: dp(5)
size_hint_y: .53
canvas:
Color:
rgba: 1,1,1,1
Rectangle:
pos: self.pos
size: self.size
#label area
AnchorLayout:
size_hint_y: None
height: dp(50)
MDLabel:
text: app.tr._("Enter a label to generate address for:")
bold: True
halign: "center"
theme_text_color: "Custom"
text_color: .4,.4,.4,1
AnchorLayout:
size_hint_y: None
height: dp(40)
MDTextField:
id:lab
hint_text: "Label"
required: True
size_hint_x: None
width: dp(190)
helper_text_mode: "on_error"
# helper_text: "Please enter your label name"
on_text: app.root.ids.sc7.add_validation(self)
canvas.before:
Color:
rgba: (0,0,0,1)
AnchorLayout:
MDFillRoundFlatIconButton:
icon: "chevron-double-right"
text: app.tr._("Proceed Next")
on_release: app.root.ids.sc7.generateaddress()
Widget:
#info-area-outer
BoxLayout:
size_hint_y: .47
padding: dp(7)
InfoLayout:
orientation:"vertical"
padding: 0, dp(5), 0, dp(5)
canvas:
Color:
rgba:1,1,1,1
Rectangle:
pos: self.pos
size: self.size
Color:
rgba: app.theme_cls.primary_color
Line:
rounded_rectangle: (self.pos[0]+4, self.pos[1]+4, self.width-8,self.height-8, 10, 10, 10, 10, 50)
width: dp(1)
ScreenManager:
id: info_screenmgr
Screen:
name: "info2"
ScrollView:
bar_width:0
do_scroll_x: False
BoxLayout:
orientation: "vertical"
size_hint_y: None
height: self.minimum_height
#note area
ContentHead:
section_name: "NOTE:"
ContentBody:
section_text: ("Here you may generate as many addresses as you like..Indeed creating and abandoning addresses is not encouraged.")
<Check@MDCheckbox>:
group: 'group'
size_hint: None, None
size: dp(48), dp(48)
<ContentHead@BoxLayout>:
section_name: ""
orientation: "vertical"
size_hint_y: None
height: dp(50)
padding: dp(20), 0, 0, 0
Widget:
size_hint_y: None
height: dp(25)
MDLabel:
theme_text_color: "Custom"
text_color: .1,.1,.1,.9
text: app.tr._(root.section_name)
bold: True
font_style: "Button"
<ContentBody@BoxLayout>:
section_text: ""
size_hint_y: None
height: self.minimum_height
padding: dp(50), 0, dp(10), 0
MDLabel:
size_hint_y: None
height: self.texture_size[1]+dp(10)
theme_text_color: "Custom"
text_color: 0.3,0.3,0.3,1
font_style: "Body1"
text: app.tr._(root.section_text)

View File

@ -0,0 +1,87 @@
<MailDetail>:
name: 'mailDetail'
ScrollView:
do_scroll_x: False
BoxLayout:
size_hint_y: None
orientation: 'vertical'
# height: dp(bod.height) + self.minimum_height
height: self.minimum_height
padding: dp(10)
# MDLabel:
# size_hint_y: None
# id: subj
# text: root.subject
# theme_text_color: 'Primary'
# halign: 'left'
# font_style: 'H5'
# height: dp(40)
# on_touch_down: root.allclick(self)
OneLineListTitle:
id: subj
text: app.tr._(root.subject)
divider: None
font_style: 'H5'
theme_text_color: 'Primary'
_no_ripple_effect: True
long_press_time: 1
TwoLineAvatarIconListItem:
id: subaft
text: app.tr._(root.from_addr)
secondary_text: app.tr._('to ' + root.to_addr)
divider: None
on_press: root.detailedPopup()
BadgeText:
size_hint:(None, None)
size:[120, 140] if app.app_platform == 'android' else [64, 80]
text: app.tr._(root.time_tag)
halign:'center'
font_style:'Caption'
pos_hint: {'center_y': .8}
_txt_right_pad: dp(70)
font_size: '11sp'
MDChip:
size_hint: (.16 if app.app_platform == 'android' else .08 , None)
text: app.tr._(root.page_type)
icon: ''
text_color: (1,1,1,1)
pos_hint: {'center_x': .91 if app.app_platform == 'android' else .95, 'center_y': .3}
radius: [8]
height: self.parent.height/4
AvatarSampleWidget:
source: root.avatarImg
MDLabel:
text: root.status
disabled: True
font_style: 'Body2'
halign:'left'
padding_x: 20
# MDLabel:
# id: bod
# font_style: 'Subtitle2'
# theme_text_color: 'Primary'
# text: root.message
# halign: 'left'
# height: self.texture_size[1]
MyMDTextField:
id: bod
size_hint_y: None
font_style: 'Subtitle2'
text: root.message
multiline: True
readonly: True
line_color_normal: [0,0,0,0]
_current_line_color: [0,0,0,0]
line_color_focus: [0,0,0,0]
markup: True
font_size: '15sp'
canvas.before:
Color:
rgba: (0,0,0,1)
Loader:
<MyMDTextField@MDTextField>:
canvas.before:
Color:
rgba: (0,0,0,1)

View File

@ -0,0 +1,178 @@
<Create>:
name: 'create'
Loader:
<DropDownWidget>:
ScrollView:
id: id_scroll
BoxLayout:
orientation: 'vertical'
size_hint_y: None
height: self.minimum_height + 3 * self.parent.height/5
padding: dp(20)
spacing: 15
BoxLayout:
orientation: 'vertical'
MDTextField:
id: ti
size_hint_y: None
hint_text: 'Type or Select sender address'
icon_right: 'account'
icon_right_color: app.theme_cls.primary_light
font_size: '15sp'
multiline: False
required: True
# height: self.parent.height/2
height: 100
current_hint_text_color: 0,0,0,0.5
helper_text_mode: "on_error"
canvas.before:
Color:
rgba: (0,0,0,1)
BoxLayout:
size_hint_y: None
height: dp(40)
CustomSpinner:
id: btn
background_color: app.theme_cls.primary_dark
values: app.variable_1
on_text: root.auto_fill_fromaddr() if self.text != 'Select' else ''
option_cls: Factory.get("ComposerSpinnerOption")
#background_color: color_button if self.state == 'normal' else color_button_pressed
#background_down: 'atlas://data/images/defaulttheme/spinner'
background_normal: ''
background_color: app.theme_cls.primary_color
color: color_font
font_size: '13.5sp'
ArrowImg:
RelativeLayout:
orientation: 'horizontal'
BoxLayout:
orientation: 'vertical'
txt_input: txt_input
rv: rv
size : (890, 60)
MyTextInput:
id: txt_input
size_hint_y: None
font_size: '15sp'
color: color_font
# height: self.parent.height/2
current_hint_text_color: 0,0,0,0.5
height: 100
hint_text: app.tr._('Type or Scan QR code for recipients address')
canvas.before:
Color:
rgba: (0,0,0,1)
RV:
id: rv
MDIconButton:
icon: 'qrcode-scan'
pos_hint: {'center_x': 0.95, 'y': 0.6}
on_release:
if root.is_camara_attached(): app.root.ids.scr_mngr.current = 'scanscreen'
else: root.camera_alert()
on_press:
app.root.ids.sc23.get_screen('composer')
MyMDTextField:
id: subject
hint_text: 'Subject'
height: 100
font_size: '15sp'
icon_right: 'notebook-outline'
icon_right_color: app.theme_cls.primary_light
current_hint_text_color: 0,0,0,0.5
font_color_normal: 0, 0, 0, 1
size_hint_y: None
required: True
multiline: False
helper_text_mode: "on_focus"
canvas.before:
Color:
rgba: (0,0,0,1)
# MyMDTextField:
# id: body
# multiline: True
# hint_text: 'body'
# size_hint_y: None
# font_size: '15sp'
# required: True
# helper_text_mode: "on_error"
# canvas.before:
# Color:
# rgba: (0,0,0,1)
ScrollView:
id: scrlv
MDTextField:
id: body
hint_text: 'Body'
mode: "fill"
fill_color: 1/255, 144/255, 254/255, 0.1
multiline: True
font_color_normal: 0, 0, 0, .4
icon_right: 'grease-pencil'
icon_right_color: app.theme_cls.primary_light
size_hint: 1, 1
height: app.window_size[1]/4
canvas.before:
Color:
rgba: 125/255, 125/255, 125/255, 1
BoxLayout:
spacing:50
<MyTextInput>:
readonly: False
multiline: False
<SelectableLabel>:
# Draw a background to indicate selection
color: 0,0,0,1
canvas.before:
Color:
rgba: app.theme_cls.primary_dark if self.selected else (1, 1, 1, 0)
Rectangle:
pos: self.pos
size: self.size
<RV>:
canvas:
Color:
rgba: 0,0,0,.2
Line:
rectangle: self.x +1 , self.y, self.width - 2, self.height -2
bar_width: 10
scroll_type:['bars']
viewclass: 'SelectableLabel'
SelectableRecycleBoxLayout:
default_size: None, dp(20)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
multiselect: False
<MyMDTextField@MDTextField>:
canvas.before:
Color:
rgba: (0,0,0,1)
<ComposerSpinnerOption@SpinnerOption>:
font_size: '13.5sp'
#background_color: color_button if self.state == 'down' else color_button_pressed
#background_down: 'atlas://data/images/defaulttheme/button'
background_normal: 'atlas://data/images/defaulttheme/textinput_active'
background_color: app.theme_cls.primary_color
color: color_font

View File

@ -0,0 +1,33 @@
<MyAddress>:
name: 'myaddress'
BoxLayout:
id: main_box
orientation: 'vertical'
spacing: dp(5)
SearchBar:
id: search_bar
GridLayout:
id: identi_tag
padding: [20, 0, 0, 5]
cols: 1
size_hint_y: None
height: self.minimum_height
MDLabel:
id: tag_label
text: app.tr._('My Addresses')
font_style: 'Subtitle2'
FloatLayout:
MDScrollViewRefreshLayout:
id: refresh_layout
refresh_callback: root.refresh_callback
root_layout: root
MDList:
id: ml
Loader:
<ToggleBtn>:
size_hint: None, None
size: dp(36), dp(48)
pos_hint: {'center_x': .95, 'center_y': .4}
on_active: app.root.ids.sc10.toggleAction(self)

View File

@ -0,0 +1,131 @@
<NetworkStat>:
name: 'networkstat'
MDTabs:
id: tab_panel
tab_display_mode:'text'
Tab:
text: app.tr._("Total connections")
ScrollView:
do_scroll_x: False
MDList:
id: ml
size_hint_y: None
height: dp(200)
OneLineListItem:
text: app.tr._("Total Connections")
_no_ripple_effect: True
BoxLayout:
orientation: 'vertical'
size_hint_y: None
height: dp(58)
MDRaisedButton:
_no_ripple_effect: True
# size_hint: .6, 0
# height: dp(40)
text: app.tr._(root.text_variable_1)
elevation_normal: 2
opposite_colors: True
pos_hint: {'center_x': .5}
# MDLabel:
# font_style: 'H6'
# text: app.tr._(root.text_variable_1)
# font_size: '13sp'
# color: (1,1,1,1)
# halign: 'center'
Tab:
text: app.tr._('Processes')
ScrollView:
do_scroll_x: False
MDList:
id: ml
size_hint_y: None
height: dp(500)
OneLineListItem:
text: app.tr._("person-to-person")
_no_ripple_effect: True
BoxLayout:
orientation: 'vertical'
size_hint_y: None
height: dp(58)
MDRaisedButton:
_no_ripple_effect: True
# size_hint: .6, 0
# height: dp(40)
text: app.tr._(root.text_variable_2)
elevation_normal: 2
opposite_colors: True
pos_hint: {'center_x': .5}
# MDLabel:
# font_style: 'H6'
# text: app.tr._(root.text_variable_2)
# font_size: '13sp'
# color: (1,1,1,1)
# halign: 'center'
OneLineListItem:
text: app.tr._("Brodcast")
_no_ripple_effect: True
BoxLayout:
orientation: 'vertical'
size_hint_y: None
height: dp(58)
MDRaisedButton:
_no_ripple_effect: True
# size_hint: .6, 0
# height: dp(40)
text: app.tr._(root.text_variable_3)
elevation_normal: 2
opposite_colors: True
pos_hint: {'center_x': .5}
# MDLabel:
# font_style: 'H6'
# text: app.tr._(root.text_variable_3)
# font_size: '13sp'
# color: (1,1,1,1)
# halign: 'center'
OneLineListItem:
text: app.tr._("publickeys")
_no_ripple_effect: True
BoxLayout:
orientation: 'vertical'
size_hint_y: None
height: dp(58)
MDRaisedButton:
_no_ripple_effect: True
# size_hint: .6, 0
# height: dp(40)
text: app.tr._(root.text_variable_4)
elevation_normal: 2
opposite_colors: True
pos_hint: {'center_x': .5}
# MDLabel:
# font_style: 'H6'
# text: app.tr._(root.text_variable_4)
# font_size: '13sp'
# color: (1,1,1,1)
# halign: 'center'
OneLineListItem:
text: app.tr._("objects")
_no_ripple_effect: True
BoxLayout:
orientation: 'vertical'
size_hint_y: None
height: dp(58)
MDRaisedButton:
_no_ripple_effect: True
# size_hint: .6, 0
#height: dp(40)
text: app.tr._(root.text_variable_5)
elevation_normal: 2
opposite_colors: True
pos_hint: {'center_x': .5}
# MDLabel:
# font_style: 'H6'
# text: app.tr._(root.text_variable_5)
# font_size: '13sp'
# color: (1,1,1,1)
# halign: 'center'

View File

@ -0,0 +1,253 @@
#:import get_color_from_hex kivy.utils.get_color_from_hex
<Payment>:
name: "payment"
BoxLayout:
ScrollView:
bar_width:0
do_scroll_x: False
#scroll_y:0
BoxLayout:
spacing: dp(8)
padding: dp(5)
size_hint_y: None
height: self.minimum_height
orientation: "vertical"
ProductCategoryLayout:
category_text: "Monthly-Subscriptions"
ProductLayout:
heading_text: "Gas (Play Billing Codelab)"
price_text: "$0.99"
source: app.image_path + "/payment/buynew1.png"
description_text: "Buy gasoline to ride!"
product_id: "SKUGASBILLING"
ProductLayout:
heading_text: "Upgrade your car (Play Billing Codelab)"
price_text: "$1.49"
source: app.image_path + "/payment/buynew1.png"
description_text: "Buy a premium outfit for your car!"
product_id: "SKUUPGRADECAR"
ProductLayout:
heading_text: "Month in gold status (Play Billing Codelab)"
price_text: "$0.99"
source: app.image_path + "/payment/buynew1.png"
description_text: "Enjoy a gold status for a month!"
product_id: "SKUMONTHLYGOLD"
ProductCategoryLayout:
category_text: "One-time payment"
ProductLayout:
heading_text: "Gas (Play Billing Codelab)"
price_text: "$0.99"
source: app.image_path + "/payment/buynew1.png"
description_text: "Buy gasoline to ride!"
product_id: "SKUONETIMEGAS"
ProductCategoryLayout:
category_text: "Annual-Subscriptions"
ProductLayout:
heading_text: "Gas (Play Billing Codelab)"
price_text: "$0.99"
source: app.image_path + "/payment/buynew1.png"
description_text: "Buy gasoline to ride!"
product_id: "SKUANNUALGAS"
ProductLayout:
heading_text: "Year in gold status (Play Billing Codelab)"
price_text: "$10.99"
source: app.image_path + "/payment/buynew1.png"
description_text: "Enjoy a gold status for a year!"
product_id: "SKUANNUALGOLD"
<ProductCategoryLayout@BoxLayout>:
size_hint_y: None
height: self.minimum_height
category_text:""
orientation: "vertical"
spacing: 2
#category area
Category:
text_: root.category_text
<Category>:
canvas:
Color:
rgba: 1,1,1,1
Rectangle:
pos: self.pos
size: self.size
text_: ""
size_hint_y: None
height: dp(30)
Widget:
size_hint_x: None
width: dp(20)
MDLabel:
text: root.text_
font_size: sp(15)
<ProductLayout>:
heading_text: ""
price_text: ""
source: ""
description_text: ""
product_id: ""
canvas:
Color:
rgba: 1,1,1,1
Rectangle:
pos: self.pos
size: self.size
size_hint_y: None
height: dp(200)
orientation: "vertical"
#heading area
BoxLayout:
size_hint_y: 0.3
#text heading
BoxLayout:
Widget:
size_hint_x: None
width: dp(20)
MDLabel:
text: root.heading_text
bold: True
#price text
BoxLayout:
size_hint_x:.3
MDLabel:
text: root.price_text
bold: True
halign: "right"
theme_text_color: "Custom"
text_color: 0,0,1,1
Widget:
size_hint_x: None
width: dp(20)
#details area
BoxLayout:
size_hint_y: 0.3
Widget:
size_hint_x: None
width: dp(20)
#image area
AnchorLayout:
size_hint_x: None
width: self.height
BoxLayout:
canvas:
Color:
rgba: 1,1,1,1
Ellipse:
size: self.size
pos: self.pos
source: root.source
Widget:
size_hint_x: None
width: dp(10)
#description text
BoxLayout:
#size_hint_x: 1
MDLabel:
text: root.description_text
font_size: sp(15)
#Button Area
BoxLayout:
size_hint_y: 0.4
Widget:
AnchorLayout:
anchor_x: "right"
MDRaisedButton:
elevation_normal: 5
text: "BUY"
on_release:
#print(app)
app.open_payment_layout(root.product_id)
Widget:
size_hint_x: None
width: dp(20)
<ListItemWithLabel>:
on_release: app.initiate_purchase(self.method_name)
recent: False
source: ""
method_name: ""
right_label_text: "Recent" if self.recent else ""
ImageLeftWidget:
source: root.source
RightLabel:
text: root.right_label_text
theme_text_color: "Custom"
text_color: 0,0,0,.4
font_size: sp(12)
<PaymentMethodLayout>:
orientation: "vertical"
size_hint_y: None
height: "200dp"
BoxLayout:
size_hint_y: None
height: dp(40)
Widget:
size_hint_x: None
width: dp(20)
MDLabel:
text: "Select Payment Method"
font_size: sp(14)
bold: True
theme_text_color: "Custom"
text_color: 0,0,0,.5
ScrollView:
GridLayout:
cols: 1
size_hint_y:None
height:self.minimum_height
ListItemWithLabel:
source: app.image_path + "/payment/gplay.png"
text: "Google Play"
method_name: "gplay"
recent: True
ListItemWithLabel:
source: app.image_path + "/payment/btc.png"
text: "BTC"
method_name: "btc"
ListItemWithLabel:
source: app.image_path + "/payment/paypal.png"
text: "Paypal"
method_name: "som"
ListItemWithLabel:
source: app.image_path + "/payment/buy.png"
text: "One more method"
method_name: "omm"

View File

@ -0,0 +1,333 @@
<LoadingPopup>:
separator_color: 1, 1, 1, 1
background: "White.png"
Button:
id: btn
disabled: True
background_disabled_normal: "White.png"
Image:
source: app.image_path + '/loader.zip'
anim_delay: 0
#mipmap: True
size: root.size
<GrashofPopup>:
id: popup_box
orientation: 'vertical'
# spacing:dp(20)
# spacing: "12dp"
size_hint_y: None
# height: "120dp"
height: label.height+address.height
BoxLayout:
orientation: 'vertical'
MDTextField:
id: label
multiline: False
hint_text: app.tr._("Label")
required: True
icon_right: 'label'
helper_text_mode: "on_error"
on_text: root.checkLabel_valid(self)
canvas.before:
Color:
rgba: (0,0,0,1)
MDTextField:
id: address
hint_text: app.tr._("Address")
required: True
icon_right: 'book-plus'
helper_text_mode: "on_error"
multiline: False
on_text: root.checkAddress_valid(self)
canvas.before:
Color:
rgba: (0,0,0,1)
<AddbookDetailPopup>:
id: addbook_popup_box
size_hint_y: None
height: 2.5*(add_label.height)
orientation: 'vertical'
spacing:dp(5)
MDLabel
font_style: 'Subtitle2'
theme_text_color: 'Primary'
text: app.tr._("Label")
font_size: '17sp'
halign: 'left'
MDTextField:
id: add_label
font_style: 'Body1'
font_size: '15sp'
halign: 'left'
text: app.tr._(root.address_label)
theme_text_color: 'Primary'
required: True
helper_text_mode: "on_error"
on_text: root.checkLabel_valid(self)
canvas.before:
Color:
rgba: (0,0,0,1)
MDLabel:
font_style: 'Subtitle2'
theme_text_color: 'Primary'
text: app.tr._("Address")
font_size: '17sp'
halign: 'left'
Widget:
size_hint_y: None
height: dp(1)
BoxLayout:
orientation: 'horizontal'
MDLabel:
id: address
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._(root.address)
font_size: '15sp'
halign: 'left'
IconRightSampleWidget:
pos_hint: {'center_x': 0, 'center_y': 1}
icon: 'content-copy'
on_press: app.copy_composer_text(root.address)
<MyaddDetailPopup>:
id: myadd_popup
size_hint_y: None
height: "130dp"
spacing:dp(25)
#height: dp(1.5*(myaddr_label.height))
orientation: 'vertical'
MDLabel:
id: myaddr_label
font_style: 'Subtitle2'
theme_text_color: 'Primary'
text: app.tr._("Label")
font_size: '17sp'
halign: 'left'
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: root.address_label
font_size: '15sp'
halign: 'left'
MDLabel:
font_style: 'Subtitle2'
theme_text_color: 'Primary'
text: app.tr._("Address")
font_size: '17sp'
halign: 'left'
BoxLayout:
orientation: 'horizontal'
MDLabel:
id: label_address
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._(root.address)
font_size: '15sp'
halign: 'left'
IconRightSampleWidget:
pos_hint: {'center_x': 0, 'center_y': 1}
icon: 'content-copy'
on_press: app.copy_composer_text(root.address)
BoxLayout:
id: my_add_btn
spacing:5
orientation: 'horizontal'
size_hint_y: None
height: self.minimum_height
MDRaisedButton:
size_hint: 2, None
height: dp(40)
on_press: root.send_message_from()
MDLabel:
font_style: 'H6'
text: app.tr._('Send message from')
font_size: '13sp'
color: (1,1,1,1)
halign: 'center'
MDRaisedButton:
size_hint: 1.5, None
height: dp(40)
on_press: app.root.ids.scr_mngr.current = 'showqrcode'
on_press: app.root.ids.sc15.qrdisplay(root, root.address)
MDLabel:
font_style: 'H6'
text: app.tr._('Show QR code')
font_size: '13sp'
color: (1,1,1,1)
halign: 'center'
MDRaisedButton:
size_hint: 1.5, None
height: dp(40)
on_press: root.close_pop()
MDLabel:
font_style: 'H6'
text: app.tr._('Cancel')
font_size: '13sp'
color: (1,1,1,1)
halign: 'center'
<AppClosingPopup>:
id: closing_popup
size_hint : (None,None)
height: 1.4*(popup_label.height+ my_add_btn.children[0].height)
width :app.window_size[0] - (app.window_size[0]/10 if app.app_platform == 'android' else app.window_size[0]/4)
background: app.image_path + '/popup.jpeg'
auto_dismiss: False
separator_height: 0
BoxLayout:
id: myadd_popup_box
size_hint_y: None
spacing:dp(70)
orientation: 'vertical'
BoxLayout:
size_hint_y: None
orientation: 'vertical'
spacing:dp(25)
MDLabel:
id: popup_label
font_style: 'Subtitle2'
theme_text_color: 'Primary'
text: app.tr._("Bitmessage isn't connected to the network.\n If you quit now, it may cause delivery delays.\n Wait until connected and the synchronisation finishes?")
font_size: '17sp'
halign: 'center'
BoxLayout:
id: my_add_btn
spacing:5
orientation: 'horizontal'
MDRaisedButton:
size_hint: 1.5, None
height: dp(40)
on_press: root.closingAction(self.children[0].text)
on_press: app.stop()
MDLabel:
font_style: 'H6'
text: app.tr._('Yes')
font_size: '13sp'
color: (1,1,1,1)
halign: 'center'
MDRaisedButton:
size_hint: 1.5, None
height: dp(40)
on_press: root.closingAction(self.children[0].text)
MDLabel:
font_style: 'H6'
text: app.tr._('No')
font_size: '13sp'
color: (1,1,1,1)
halign: 'center'
MDRaisedButton:
size_hint: 1.5, None
height: dp(40)
#on_press: root.dismiss()
on_press: root.closingAction(self.children[0].text)
MDLabel:
font_style: 'H6'
text: app.tr._('Cancel')
font_size: '13sp'
color: (1,1,1,1)
halign: 'center'
<SenderDetailPopup>:
id: myadd_popup
size_hint : (None,None)
# height: 2*(sd_label.height+ sd_btn.children[0].height)
width :app.window_size[0] - (app.window_size[0]/10 if app.app_platform == 'android' else app.window_size[0]/4)
background: app.image_path + '/popup.jpeg'
auto_dismiss: False
separator_height: 0
BoxLayout:
id: myadd_popup_box
size_hint_y: None
orientation: 'vertical'
spacing:dp(8 if app.app_platform == 'android' else 3)
BoxLayout:
orientation: 'vertical'
MDLabel:
id: from_add_label
font_style: 'Subtitle2'
theme_text_color: 'Primary'
text: app.tr._("From :")
font_size: '15sp'
halign: 'left'
Widget:
size_hint_y: None
height: dp(1 if app.app_platform == 'android' else 0)
BoxLayout:
size_hint_y: None
height: 50
orientation: 'horizontal'
MDLabel:
id: sd_label
font_style: 'Body2'
theme_text_color: 'Primary'
text: app.tr._("[b]" + root.from_addr + "[/b]")
font_size: '15sp'
halign: 'left'
markup: True
IconRightSampleWidget:
icon: 'content-copy'
on_press: app.copy_composer_text(root.from_addr)
Widget:
id: space_1
size_hint_y: None
height: dp(2 if app.app_platform == 'android' else 0)
BoxLayout:
id: to_addtitle
Widget:
id:space_2
size_hint_y: None
height: dp(1 if app.app_platform == 'android' else 0)
BoxLayout:
id: to_addId
BoxLayout:
size_hint_y: None
orientation: 'vertical'
height: 50
MDLabel:
font_style: 'Body2'
theme_text_color: 'Primary'
text: app.tr._("Date : " + root.time_tag)
font_size: '15sp'
halign: 'left'
BoxLayout:
id: sd_btn
orientation: 'vertical'
MDRaisedButton:
id: dismiss_btn
on_press: root.dismiss()
size_hint: .2, 0
pos_hint: {'x': 0.8, 'y': 0}
MDLabel:
font_style: 'H6'
text: app.tr._('Cancel')
font_size: '13sp'
color: (1,1,1,1)
halign: 'center'
<ToAddrBoxlayout>:
orientation: 'horizontal'
MDLabel:
font_style: 'Body2'
theme_text_color: 'Primary'
text: app.tr._(root.to_addr)
font_size: '15sp'
halign: 'left'
IconRightSampleWidget:
icon: 'content-copy'
on_press: app.copy_composer_text(root.to_addr)
<ToAddressTitle>:
orientation: 'vertical'
MDLabel:
id: to_add_label
font_style: 'Subtitle2'
theme_text_color: 'Primary'
text: "To :"
font_size: '15sp'
halign: 'left'

View File

@ -0,0 +1,33 @@
<ShowQRCode>:
name: 'showqrcode'
BoxLayout:
orientation: 'vertical'
size_hint: (None, None)
pos_hint:{'center_x': .5, 'top': 0.9}
size: (app.window_size[0]/1.8, app.window_size[0]/1.8)
id: qr
BoxLayout:
orientation: 'vertical'
MyMDTextField:
size_hint_y: None
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._(root.address)
multiline: True
readonly: True
line_color_normal: [0,0,0,0]
_current_line_color: [0,0,0,0]
line_color_focus: [0,0,0,0]
halign: 'center'
font_size: dp(15)
bold: True
canvas.before:
Color:
rgba: (0,0,0,1)
# MDLabel:
# size_hint_y: None
# font_style: 'Body1'
# theme_text_color: 'Primary'
# text: "[b]BM-2cV7Y8imvAevK6z6YmhYRcj2t7rghBtDSZ[/b]"
# markup: True
# pos_hint: {'x': .28, 'y': 0.6}

View File

@ -0,0 +1,2 @@
<ScanScreen>:
name:'scanscreen'

View File

@ -0,0 +1,37 @@
#:import ZBarSymbol pyzbar.pyzbar.ZBarSymbol
BoxLayout:
orientation: 'vertical'
ZBarCam:
id: zbarcam
# optional, by default checks all types
code_types: ZBarSymbol.QRCODE, ZBarSymbol.EAN13
scan_callback: app._after_scan
scanner_line_y_initial: self.size[1]/2 +self.qrwidth/2
scanner_line_y_final: self.size[1]/2-self.qrwidth/2
canvas:
Color:
rgba: 0,0,0,.25
#left rect
Rectangle:
pos: self.pos[0], self.pos[1]
size: self.size[0]/2-self.qrwidth/2, self.size[1]
#right rect
Rectangle:
pos: self.size[0]/2+self.qrwidth/2, 0
size: self.size[0]/2-self.qrwidth/2, self.size[1]
#top rect
Rectangle:
pos: self.size[0]/2-self.qrwidth/2, self.size[1]/2+self.qrwidth/2
size: self.qrwidth, self.size[1]/2-self.qrwidth/2
#bottom rect
Rectangle:
pos: self.size[0]/2-self.qrwidth/2, 0
size: self.qrwidth, self.size[1]/2-self.qrwidth/2

View File

@ -0,0 +1,26 @@
<Sent>:
name: 'sent'
BoxLayout:
orientation: 'vertical'
spacing: dp(5)
SearchBar:
id: sent_search
GridLayout:
id: identi_tag
padding: [20, 0, 0, 5]
cols: 1
size_hint_y: None
height: self.minimum_height
MDLabel:
id: tag_label
text: ''
font_style: 'Subtitle2'
BoxLayout:
orientation:'vertical'
ScrollView:
id: scroll_y
do_scroll_x: False
MDList:
id: ml
Loader:
ComposerButton:

View File

@ -0,0 +1,964 @@
<Setting>:
name: 'set'
MDTabs:
id: tab_panel
tab_display_mode:'text'
Tab:
text: app.tr._("User Interface")
ScrollView:
do_scroll_x: False
BoxLayout:
size_hint_y: None
orientation: 'vertical'
height: dp(250) + self.minimum_height
padding: 10
BoxLayout:
size_hint_y: None
orientation: 'horizontal'
height: self.minimum_height
MDCheckbox:
id: chkbox
size_hint: None, None
size: dp(48), dp(50)
# active: True
halign: 'center'
disabled: True
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("Start-on-login not yet supported on your OS")
halign: 'left'
pos_hint: {'center_x': 0, 'center_y': 0.6}
disabled: True
BoxLayout:
size_hint_y: None
orientation: 'vertical'
padding: [20, 0, 0, 0]
spacing: dp(10)
height: dp(100) + self.minimum_height
# pos_hint: {'center_x': 0, 'center_y': 0.6}
BoxLayout:
id: box_height
orientation: 'vertical'
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("Tray")
halign: 'left'
bold: True
BoxLayout:
orientation: 'horizontal'
MDCheckbox:
id: chkbox
size_hint: None, None
size: dp(48), dp(50)
# active: True
halign: 'center'
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("Start Bitmessage in the tray(don't show main window)")
halign: 'left'
pos_hint: {'x': 0, 'y': .5}
BoxLayout:
orientation: 'horizontal'
MDCheckbox:
id: chkbox
size_hint: None, None
size: dp(48), dp(50)
# active: True
halign: 'center'
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("Minimize to tray")
halign: 'left'
pos_hint: {'x': 0, 'y': .5}
BoxLayout:
orientation: 'horizontal'
MDCheckbox:
id: chkbox
size_hint: None, None
size: dp(48), dp(50)
# active: True
halign: 'center'
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("Close to tray")
halign: 'left'
pos_hint: {'x': 0, 'y': .5}
BoxLayout:
size_hint_y: None
orientation: 'vertical'
height: dp(100) + self.minimum_height
BoxLayout:
orientation: 'horizontal'
MDCheckbox:
id: chkbox
size_hint: None, None
size: dp(48), dp(50)
# active: True
halign: 'center'
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("Hide connection notifications")
halign: 'left'
pos_hint: {'x': 0, 'y': 0.2}
BoxLayout:
orientation: 'horizontal'
MDCheckbox:
id: chkbox
size_hint: None, None
size: dp(48), dp(50)
active: True
halign: 'center'
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("Show notification when message received")
halign: 'left'
pos_hint: {'x': 0, 'y': 0.2}
BoxLayout:
orientation: 'horizontal'
MDCheckbox:
id: chkbox
size_hint: None, None
size: dp(48), dp(50)
# active: True
halign: 'center'
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("Run in Portable Mode")
halign: 'left'
pos_hint: {'x': 0, 'y': 0.2}
BoxLayout:
orientation: 'vertical'
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._('In portable Mode, messages and config files are stored in the same directory as the program rather then the normal application-data folder. This makes it convenient to run Bitmessage from a USB thumb drive.')
# text: 'huiiiii'
halign: 'left'
BoxLayout:
size_hint_y: None
orientation: 'vertical'
height: dp(100) + self.minimum_height
BoxLayout:
orientation: 'horizontal'
MDCheckbox:
id: chkbox
size_hint: None, None
size: dp(48), dp(50)
halign: 'center'
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("Willingly include unencrypted destination address when sending to a mobile device")
halign: 'left'
pos_hint: {'x': 0, 'y': 0.2}
BoxLayout:
orientation: 'horizontal'
MDCheckbox:
id: chkbox
size_hint: None, None
size: dp(48), dp(50)
active: True
halign: 'center'
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("Use identicons")
halign: 'left'
pos_hint: {'x': 0, 'y': 0.2}
BoxLayout:
orientation: 'horizontal'
MDCheckbox:
id: chkbox
size_hint: None, None
size: dp(48), dp(50)
halign: 'center'
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("Reply below Quote")
halign: 'left'
pos_hint: {'x': 0, 'y': 0.2}
Widget:
size_hint_y: None
height: 10
BoxLayout:
size_hint_y: None
orientation: 'vertical'
# padding: [0, 10, 0, 0]
spacing: 10
padding: [20, 0, 0, 0]
height: dp(20) + self.minimum_height
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("Interface Language")
# halign: 'right'
bold: True
MDDropDownItem:
id: dropdown_item
text: "System Setting"
# pos_hint: {"center_x": .5, "center_y": .6}
# current_item: "Item 0"
# on_release: root.menu.open()
BoxLayout:
spacing:5
orientation: 'horizontal'
# pos_hint: {'x':.76}
BoxLayout:
orientation: 'horizontal'
spacing: 10
MDRaisedButton:
text: app.tr._('Apply')
# on_press: root.change_language()
Tab:
text: 'Network Settings'
ScrollView:
do_scroll_x: False
BoxLayout:
size_hint_y: None
orientation: 'vertical'
height: dp(500) + self.minimum_height
padding: 10
BoxLayout:
id: box_height
orientation: 'vertical'
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("Listening port")
halign: 'left'
bold: True
BoxLayout:
orientation: 'horizontal'
padding: [10, 0, 0, 0]
BoxLayout:
orientation: 'horizontal'
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("Listen for connections on port:")
halign: 'left'
BoxLayout:
orientation: 'horizontal'
MDTextFieldRect:
size_hint: None, None
size: dp(100), dp(30)
text: app.tr._('8444')
pos_hint: {'center_y': .5, 'center_x': .5}
input_filter: "int"
BoxLayout:
orientation: 'horizontal'
padding_left: 10
MDCheckbox:
id: chkbox
size_hint: None, None
size: dp(48), dp(50)
# active: True
halign: 'center'
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("UPnP")
halign: 'left'
pos_hint: {'x': 0, 'y': 0}
BoxLayout:
orientation: 'vertical'
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("Proxy server / Tor")
halign: 'left'
bold: True
GridLayout:
cols: 2
padding: [10, 0, 0, 0]
MDLabel:
size_hint_x: None
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("Type:")
halign: 'left'
MDDropDownItem:
id: dropdown_item2
dropdown_bg: [1, 1, 1, 1]
text: 'none'
pos_hint: {'x': 0.9, 'y': 0}
items: [f"{i}" for i in ['System Setting','U.S. English']]
BoxLayout:
size_hint_y: None
orientation: 'vertical'
padding: [30, 0, 0, 0]
spacing: 10
height: dp(100) + self.minimum_height
BoxLayout:
orientation: 'horizontal'
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("Server hostname:")
halign: 'left'
MDTextFieldRect:
size_hint: None, None
size: dp(app.window_size[0]/4), dp(30)
hint_text: app.tr._('localhost')
pos_hint: {'center_y': .5, 'center_x': .5}
BoxLayout:
orientation: 'horizontal'
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("Port:")
halign: 'left'
# TextInput:
# size_hint: None, None
# hint_text: '9050'
# size: dp(app.window_size[0]/4), dp(30)
# input_filter: "int"
# readonly: False
# multiline: False
# font_size: '15sp'
MDTextFieldRect:
size_hint: None, None
size: dp(app.window_size[0]/4), dp(30)
hint_text: app.tr._('9050')
pos_hint: {'center_y': .5, 'center_x': .5}
input_filter: "int"
BoxLayout:
orientation: 'horizontal'
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("Username:")
halign: 'left'
MDTextFieldRect:
size_hint: None, None
size: dp(app.window_size[0]/4), dp(30)
pos_hint: {'center_y': .5, 'center_x': .5}
BoxLayout:
orientation: 'horizontal'
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("Pass:")
halign: 'left'
MDTextFieldRect:
size_hint: None, None
size: dp(app.window_size[0]/4), dp(30)
pos_hint: {'center_y': .5, 'center_x': .5}
BoxLayout:
orientation: 'horizontal'
padding: [30, 0, 0, 0]
MDCheckbox:
id: chkbox
size_hint: None, None
size: dp(48), dp(50)
# active: True
halign: 'center'
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("Authentication")
halign: 'left'
pos_hint: {'x': 0, 'y': 0}
BoxLayout:
orientation: 'horizontal'
padding: [30, 0, 0, 0]
MDCheckbox:
id: chkbox
size_hint: None, None
size: dp(48), dp(50)
# active: True
halign: 'center'
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("Listen for incoming connections when using proxy")
halign: 'left'
pos_hint: {'x': 0, 'y': 0}
BoxLayout:
orientation: 'horizontal'
padding: [30, 0, 0, 0]
MDCheckbox:
id: chkbox
size_hint: None, None
size: dp(48), dp(50)
# active: True
halign: 'center'
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("Only connect to onion services(*.onion)")
halign: 'left'
pos_hint: {'x': 0, 'y': 0}
BoxLayout:
orientation: 'vertical'
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("Bandwidth limit")
halign: 'left'
bold: True
BoxLayout:
size_hint_y: None
orientation: 'horizontal'
padding: [30, 0, 0, 0]
height: dp(30) + self.minimum_height
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("Maximum download rate (kB/s):[0:unlimited]")
halign: 'left'
MDTextFieldRect:
size_hint: None, None
size: app.window_size[0]/2, dp(30)
hint_text: app.tr._('0')
pos_hint: {'center_y': .5, 'center_x': .5}
input_filter: "int"
BoxLayout:
size_hint_y: None
orientation: 'horizontal'
padding: [30, 0, 0, 0]
height: dp(30) + self.minimum_height
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("Maximum upload rate (kB/s):[0:unlimited]")
halign: 'left'
MDTextFieldRect:
size_hint: None, None
size: app.window_size[0]/2, dp(30)
hint_text: '0'
pos_hint: {'center_y': .5, 'center_x': .5}
input_filter: "int"
BoxLayout:
size_hint_y: None
orientation: 'horizontal'
padding: [30, 0, 0, 0]
height: dp(30) + self.minimum_height
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("Maximum outbound connections:[0:none]")
halign: 'left'
MDTextFieldRect:
size_hint: None, None
size: app.window_size[0]/2, dp(30)
hint_text: '8'
pos_hint: {'center_y': .5, 'center_x': .5}
input_filter: "int"
BoxLayout:
spacing:5
orientation: 'horizontal'
# pos_hint: {'x':.76}
MDRaisedButton:
text: app.tr._('Apply')
Tab:
text: 'Demanded Difficulty'
ScrollView:
do_scroll_x: False
BoxLayout:
size_hint_y: None
orientation: 'vertical'
height: dp(300) + self.minimum_height
padding: 10
BoxLayout:
id: box_height
orientation: 'vertical'
# MDLabel:
# font_style: 'Body1'
# theme_text_color: 'Primary'
# text: app.tr._("Listening port")
# halign: 'left'
# bold: True
# BoxLayout:
# size_hint_y: None
# orientation: 'vertical'
# height: dp(210 if app.app_platform == 'android' else 100)+ self.minimum_height
# padding: 20
# # spacing: 10
# BoxLayout:
# # size_hint_y: None
# id: box1_height
# # orientation: 'vertical'
# # height: dp(100) + self.minimum_height
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
# text: app.tr._(root.exp_text)
text: "\n\n\nWhen someone sends you a message, their computer must first complete some work. The difficulty of this work, by default, is 1. You may raise this default for new addresses you create by changing the values here. Any new addresses you create will require senders to meet the higher difficulty. There is one exception: if you add a friend or acquaintance to your address book, Bitmessage will automatically notify them when you next send a message that they need only complete the minimum amount of work: difficulty 1.\n\n"
halign: 'left'
BoxLayout:
orientation: 'horizontal'
padding: 5
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("Total difficulty:")
halign: 'left'
MDTextFieldRect:
size_hint: None, None
size: dp(app.window_size[0]/4), dp(30)
hint_text: app.tr._('00000.0')
pos_hint: {'center_y': .5, 'center_x': .5}
input_filter: "int"
BoxLayout:
# size_hint_y: None
id: box1_height
orientation: 'vertical'
padding: 5
# height: dp(100) + self.minimum_height
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
# text: app.tr._(root.exp_text)
text: "The 'Total difficulty' affects the absolute amount of work the sender must complete. Doubling this value doubles the amount of work."
halign: 'left'
BoxLayout:
orientation: 'horizontal'
spacing: 0
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("Small message difficulty:")
halign: 'left'
MDTextFieldRect:
size_hint: None, None
size: dp(app.window_size[0]/4), dp(30)
hint_text: app.tr._('00000.0')
pos_hint: {'center_y': .5, 'center_x': .5}
input_filter: "int"
BoxLayout:
size_hint_y: None
padding: 0
id: box1_height
orientation: 'vertical'
# height: dp(100) + self.minimum_height
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
# text: app.tr._(root.exp_text)
text: "The 'Small message difficulty' mostly only affects the difficulty of sending small messages. Doubling this value makes it almost twice as difficult to send a small message but doesn't really affect large messages."
halign: 'left'
# BoxLayout:
# id: box2_height
# size_hint_y: None
# orientation: 'vertical'
# height: dp(30) + self.minimum_height
# MDLabel:
# font_style: 'Body1'
# theme_text_color: 'Primary'
# text: app.tr._("Leave these input fields blank for the default behavior.")
# halign: 'left'
# BoxLayout:
# size_hint_y: None
# orientation: 'vertical'
# padding: [10, 0, 0, 0]
# height: dp(50) + self.minimum_height
# BoxLayout:
# orientation: 'horizontal'
# MDLabel:
# font_style: 'Body1'
# theme_text_color: 'Primary'
# text: app.tr._("Give up after")
# halign: 'left'
# MDTextFieldRect:
# size_hint: None, None
# size: dp(70), dp(30)
# text: app.tr._('0')
# # pos_hint: {'center_y': .5, 'center_x': .5}
# input_filter: "int"
# MDLabel:
# font_style: 'Body1'
# theme_text_color: 'Primary'
# text: app.tr._("days and")
# halign: 'left'
# MDTextFieldRect:
# size_hint: None, None
# size: dp(70), dp(30)
# text: '0'
# # pos_hint: {'center_y': .5, 'center_x': .5}
# input_filter: "int"
# MDLabel:
# font_style: 'Body1'
# theme_text_color: 'Primary'
# text: "months"
# halign: 'left'
BoxLayout:
size_hint_y: None
spacing:10
orientation: 'horizontal'
# pos_hint: {'left': 0}
# pos_hint: {'x':.75}
height: dp(10) + self.minimum_height
MDRaisedButton:
text: app.tr._('Cancel')
MDRaisedButton:
text: app.tr._('Apply')
Tab:
text: 'Max acceptable Difficulty'
ScrollView:
do_scroll_x: False
BoxLayout:
size_hint_y: None
orientation: 'vertical'
height: dp(210 if app.app_platform == 'android' else 100)+ self.minimum_height
padding: 20
# spacing: 10
BoxLayout:
# size_hint_y: None
id: box1_height
orientation: 'vertical'
spacing: 10
# pos_hint: {'x': 0, 'y': 0.2}
# height: dp(100) + self.minimum_height
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
# text: app.tr._(root.exp_text)
text: "\n\n\nHere you may set the maximum amount of work you are willing to do to send a message to another person. Setting these values to 0 means that any value is acceptable."
halign: 'left'
# BoxLayout:
# id: box2_height
# size_hint_y: None
# orientation: 'vertical'
# height: dp(40) + self.minimum_height
# BoxLayout:
# size_hint_y: None
# orientation: 'vertical'
# padding: [10, 0, 0, 0]
# height: dp(50) + self.minimum_height
GridLayout:
cols: 2
padding: [10, 0, 0, 0]
BoxLayout:
size_hint_y: None
orientation: 'vertical'
padding: [10, 0, 0, 0]
spacing: 10
height: dp(50) + self.minimum_height
BoxLayout:
orientation: 'horizontal'
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("Maximum acceptable total difficulty:")
halign: 'left'
MDTextFieldRect:
size_hint: None, None
size: dp(app.window_size[0]/4), dp(30)
hint_text: app.tr._('00000.0')
pos_hint: {'center_y': .5, 'center_x': .5}
input_filter: "int"
BoxLayout:
orientation: 'horizontal'
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("Hardware GPU acceleration (OpenCL):")
halign: 'left'
MDDropDownItem:
id: dropdown_item
text: "None"
pos_hint: {"center_x": 0, "center_y": 0}
# current_item: "Item 0"
# on_release: root.menu.open()
# BoxLayout:
# size_hint_y: None
# spacing:5
# orientation: 'horizontal'
# pos_hint: {'center_y': .4, 'center_x': 1.15}
# halign: 'right'
BoxLayout:
size_hint_y: None
spacing:5
orientation: 'horizontal'
pos_hint: {'center_y': 1, 'center_x': 1.15}
halign: 'right'
# pos_hint: {'left': 0}
# pos_hint: {'x':.75}
height: dp(50) + self.minimum_height
MDRaisedButton:
text: app.tr._('Cancel')
MDRaisedButton:
text: app.tr._('OK')
Tab:
text: 'Resends Expire'
ScrollView:
do_scroll_x: False
BoxLayout:
size_hint_y: None
orientation: 'vertical'
height: dp(210 if app.app_platform == 'android' else 100)+ self.minimum_height
padding: 20
# spacing: 10
BoxLayout:
# size_hint_y: None
id: box1_height
orientation: 'vertical'
# height: dp(100) + self.minimum_height
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
# text: app.tr._(root.exp_text)
text: "By default, if you send a message to someone and he is offline for more than two days, Bitmessage will send the message again after an additional two days. This will be continued with exponential backoff forever; messages will be resent after 5, 10, 20 days ect. until the receiver acknowledges them. Here you may change that behavior by having Bitmessage give up after a certain number of days or months."
halign: 'left'
BoxLayout:
id: box2_height
size_hint_y: None
orientation: 'vertical'
height: dp(30) + self.minimum_height
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("Leave these input fields blank for the default behavior.")
halign: 'left'
BoxLayout:
size_hint_y: None
orientation: 'vertical'
padding: [10, 0, 0, 0]
height: dp(50) + self.minimum_height
BoxLayout:
orientation: 'horizontal'
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("Give up after")
halign: 'left'
MDTextFieldRect:
size_hint: None, None
size: dp(70), dp(30)
text: app.tr._('0')
pos_hint: {'center_y': .5, 'center_x': .5}
input_filter: "int"
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("days and")
halign: 'left'
MDTextFieldRect:
size_hint: None, None
size: dp(70), dp(30)
text: '0'
pos_hint: {'center_y': .5, 'center_x': .5}
input_filter: "int"
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: "months"
halign: 'left'
BoxLayout:
size_hint_y: None
spacing:5
orientation: 'horizontal'
# pos_hint: {'left': 0}
# pos_hint: {'x':.75}
height: dp(50) + self.minimum_height
# MDRaisedButton:
# text: app.tr._('Cancel')
MDRaisedButton:
text: app.tr._('Apply')
Tab:
text: 'Namecoin Integration'
ScrollView:
do_scroll_x: False
BoxLayout:
size_hint_y: None
orientation: 'vertical'
height: dp(210 if app.app_platform == 'android' else 100)+ self.minimum_height
padding: 20
# spacing: 10
BoxLayout:
# size_hint_y: None
id: box1_height
orientation: 'vertical'
spacing: 10
# pos_hint: {'x': 0, 'y': 0.2}
# height: dp(100) + self.minimum_height
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
# text: app.tr._(root.exp_text)
text: "\n\n\n\n\n\nBitmessage can utilize a different Bitcoin-based program called Namecoin to make addresses human-friendly. For example, instead of having to tell your friend your long Bitmessage address, you can simply tell him to send a message to test.\n\n(Getting your own Bitmessage address into Namecoin is still rather difficult).\n\nBitmessage can use either namecoind directly or a running nmcontrol instance\n\n"
halign: 'left'
BoxLayout:
id: box2_height
size_hint_y: None
orientation: 'vertical'
height: dp(40) + self.minimum_height
BoxLayout:
size_hint_y: None
orientation: 'vertical'
padding: [10, 0, 0, 0]
height: dp(50) + self.minimum_height
BoxLayout:
orientation: 'horizontal'
padding: [10, 0, 0, 0]
BoxLayout:
orientation: 'horizontal'
# padding_left: 10
# MDCheckbox:
# id: chkbox
# size_hint: None, None
# size: dp(48), dp(50)
# # active: True
# halign: 'center'
# MDLabel:
# font_style: 'Body1'
# theme_text_color: 'Primary'
# text: app.tr._("UPnP")
# halign: 'left'
# pos_hint: {'x': 0, 'y': 0}
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("Connect to:")
halign: 'left'
# MDCheckbox:
# id: chkbox
# size_hint: None, None
# size: dp(48), dp(50)
# # active: True
# halign: 'center'
Check:
active: True
pos_hint: {'x': 0, 'y': -0.2}
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("Namecoind")
halign: 'left'
pos_hint: {'x': 0, 'y': 0}
Check:
active: False
pos_hint: {'x': 0, 'y': -0.2}
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("NMControl")
halign: 'left'
pos_hint: {'x': 0, 'y': 0}
GridLayout:
cols: 2
padding: [10, 0, 0, 0]
BoxLayout:
size_hint_y: None
orientation: 'vertical'
padding: [30, 0, 0, 0]
spacing: 10
height: dp(100) + self.minimum_height
BoxLayout:
orientation: 'horizontal'
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("hostname:")
halign: 'left'
MDTextFieldRect:
size_hint: None, None
size: dp(app.window_size[0]/4), dp(30)
hint_text: app.tr._('localhost')
pos_hint: {'center_y': .5, 'center_x': .5}
BoxLayout:
orientation: 'horizontal'
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("Port:")
halign: 'left'
# TextInput:
# size_hint: None, None
# hint_text: '9050'
# size: dp(app.window_size[0]/4), dp(30)
# input_filter: "int"
# readonly: False
# multiline: False
# font_size: '15sp'
MDTextFieldRect:
size_hint: None, None
size: dp(app.window_size[0]/4), dp(30)
hint_text: app.tr._('9050')
pos_hint: {'center_y': .5, 'center_x': .5}
input_filter: "int"
BoxLayout:
orientation: 'horizontal'
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("Username:")
halign: 'left'
MDTextFieldRect:
size_hint: None, None
size: dp(app.window_size[0]/4), dp(30)
pos_hint: {'center_y': .5, 'center_x': .5}
BoxLayout:
orientation: 'horizontal'
MDLabel:
font_style: 'Body1'
theme_text_color: 'Primary'
text: app.tr._("Password:")
halign: 'left'
MDTextFieldRect:
size_hint: None, None
size: dp(app.window_size[0]/4), dp(30)
pos_hint: {'center_y': .5, 'center_x': .5}
password: True
BoxLayout:
size_hint_y: None
spacing:5
orientation: 'horizontal'
pos_hint: {'center_y': .4, 'center_x': 1.15}
halign: 'right'
# pos_hint: {'left': 0}
# pos_hint: {'x':.75}
height: dp(50) + self.minimum_height
MDRaisedButton:
text: app.tr._('Cancel')
MDRaisedButton:
text: app.tr._('Apply')
MDRaisedButton:
text: app.tr._('OK')
Loader:

View File

@ -0,0 +1,25 @@
<Trash>:
name: 'trash'
BoxLayout:
orientation: 'vertical'
spacing: dp(5)
GridLayout:
id: identi_tag
padding: [20, 20, 0, 5]
spacing: dp(5)
cols: 1
size_hint_y: None
height: self.minimum_height
MDLabel:
id: tag_label
text: ''
font_style: 'Subtitle2'
BoxLayout:
orientation:'vertical'
ScrollView:
id: scroll_y
do_scroll_x: False
MDList:
id: ml
Loader:
ComposerButton:

View File

@ -0,0 +1,422 @@
#:import IconLeftWidget kivymd.uix.list.IconLeftWidget
#:import images_path kivymd.images_path
#:import Spinner kivy.uix.spinner.Spinner
#:import Factory kivy.factory.Factory
#:import MDCheckbox kivymd.uix.selectioncontrol.MDCheckbox
#:import MDList kivymd.uix.list.MDList
#:import OneLineListItem kivymd.uix.list.OneLineListItem
#:import MDTextField kivymd.uix.textfield.MDTextField
#:import get_color_from_hex kivy.utils.get_color_from_hex
#:import MDCard kivymd.uix.card.MDCard
#:import colors kivymd.color_definitions.colors
#:import MDTabs kivymd.uix.tab.MDTabs
#:import MDFloatingActionButton kivymd.uix.button.MDFloatingActionButton
#:import Factory kivy.factory.Factory
#:import MDScrollViewRefreshLayout kivymd.uix.refreshlayout.MDScrollViewRefreshLayout
#:import MDSpinner kivymd.uix.spinner.MDSpinner
#:import MDTabsBase kivymd.uix.tab.MDTabsBase
##:import ZBarSymbol pyzbar.pyzbar.ZBarSymbol
<Tab@BoxLayout+MDTabsBase>
#:set color_button (0.784, 0.443, 0.216, 1) # brown
#:set color_button_pressed (0.659, 0.522, 0.431, 1) # darker brown
#:set color_font (0.957, 0.890, 0.843, 1) # off white
<MySpinnerOption@SpinnerOption>:
font_size: '12.5sp'
#background_color: color_button if self.state == 'down' else color_button_pressed
#background_down: 'atlas://data/images/defaulttheme/button'
background_normal: 'atlas://data/images/defaulttheme/textinput_active'
background_color: app.theme_cls.primary_color
# text_autoupdate: True
color: color_font
<NavigationItem>
#on_press: root.active = not root.active
on_press: root.currentlyActive()
active_color: root.theme_cls.primary_color if root.active else root.theme_cls.text_color
IconLeftWidget:
icon: root.icon
theme_text_color: "Custom"
text_color: root.active_color
BadgeText:
id: badge_txt
text: f"{root.badge_text}"
theme_text_color: "Custom"
#text_color: root.active_color
halign: 'right'
<NavigationDrawerDivider>:
canvas:
Color:
rgba: self.theme_cls.divider_color
Line:
points: root.x, root.y + dp(8), root.x + self.width, root.y + dp(8)
<ContentNavigationDrawer>
BoxLayout:
orientation: 'vertical'
FloatLayout:
size_hint_y: None
height: "200dp"
MDIconButton:
id: reset_image
icon: "refresh"
x: root.parent.x + dp(10)
pos_hint: {"top": 1, 'left': 1}
color: [1,0,0,1]
on_release: app.rest_default_avatar_img()
theme_text_color: "Custom"
text_color: app.theme_cls.primary_color
# opacity: 1 if app.current_address_label() else 0
# disabled: False if app.current_address_label() else True
opacity: 0
disabled: True
MDIconButton:
id: file_manager
icon: "file-image"
x: root.parent.x + dp(10)
pos_hint: {"top": 1, 'right': 1}
color: [1,0,0,1]
on_release: app.file_manager_open()
# md_bg_color: app.theme_cls.primary_color
theme_text_color: "Custom"
text_color: app.theme_cls.primary_color
opacity: 1 if app.current_address_label() else 0
disabled: False if app.current_address_label() else True
BoxLayout:
id: top_box
size_hint_y: None
height: "200dp"
#padding: "10dp"
x: root.parent.x
pos_hint: {"top": 1}
Image:
#source: './images/drawer_logo1.png'
source: app.get_default_logo(self)
ScrollView:
id: scroll_y
pos_hint: {"top": 1}
GridLayout:
id: box_item
cols: 1
size_hint_y: None
height: self.minimum_height
NavigationDrawerDivider:
NavigationDrawerSubheader:
text: app.tr._('Accounts')
#text: app.tr._('Hello World')
height:"35dp"
NavigationItem:
# size: 50,50
height: dp(48)
CustomSpinner:
id: btn
pos_hint:{"x":0,"y":0}
option_cls: Factory.get("MySpinnerOption")
font_size: '12.5sp'
text: app.getDefaultAccData(self)
color: color_font
background_normal: ''
background_color: app.theme_cls.primary_color
on_text:app.getCurrentAccountData(self.text)
ArrowImg:
NavigationItem:
id: inbox_cnt
text: app.tr._('Inbox')
#text: app.tr._('Hello World')
icon: 'email-open'
divider: None
on_release: app.root.ids.scr_mngr.current = 'inbox'
on_release: root.parent.set_state()
on_press: app.load_screen(self)
NavigationItem:
id: send_cnt
text: app.tr._('Sent')
icon: 'send'
divider: None
on_release: app.root.ids.scr_mngr.current = 'sent'
on_release: root.parent.set_state()
NavigationItem:
id: draft_cnt
text: app.tr._('Draft')
icon: 'message-draw'
divider: None
on_release: app.root.ids.scr_mngr.current = 'draft'
on_release: root.parent.set_state()
NavigationItem:
id: trash_cnt
text: app.tr._('Trash')
icon: 'delete'
divider: None
on_release: app.root.ids.scr_mngr.current = 'trash'
on_press: root.parent.set_state()
on_press: app.load_screen(self)
NavigationItem:
id: allmail_cnt
text: app.tr._('All Mails')
icon: 'mailbox'
divider: None
on_release: app.root.ids.scr_mngr.current = 'allmails'
on_release: root.parent.set_state()
on_press: app.load_screen(self)
# NavigationItem:
# id: chat_rm
# text: app.tr._('Chat Room')
# icon: 'wechat'
# divider: None
# on_release: app.root.ids.scr_mngr.current = 'chlist'
# on_release: root.parent.set_state()
NavigationDrawerDivider:
NavigationDrawerSubheader:
text: app.tr._("All labels")
NavigationItem:
text: app.tr._('Address Book')
icon: 'book-multiple'
divider: None
on_release: app.root.ids.scr_mngr.current = 'addressbook'
on_release: root.parent.set_state()
NavigationItem:
text: app.tr._('Settings')
icon: 'application-settings'
divider: None
on_release: app.root.ids.scr_mngr.current = 'set'
on_release: root.parent.set_state()
NavigationItem:
text: app.tr._('Purchase')
icon: 'shopping'
divider: None
on_release: app.root.ids.scr_mngr.current = 'payment'
on_release: root.parent.set_state()
# NavigationItem:
# text: app.tr._('Credits')
# icon: 'wallet'
# divider: None
# on_release: app.root.ids.scr_mngr.current = 'credits'
# on_release: root.parent.set_state()
NavigationItem:
text: app.tr._('New address')
icon: 'account-plus'
divider: None
on_release: app.root.ids.scr_mngr.current = 'login'
on_release: root.parent.set_state()
on_press: app.reset_login_screen()
NavigationItem:
text: app.tr._('Network status')
icon: 'server-network'
divider: None
on_release: app.root.ids.scr_mngr.current = 'networkstat'
on_release: root.parent.set_state()
NavigationItem:
text: app.tr._('My addresses')
icon: 'account-multiple'
divider: None
on_release: app.root.ids.scr_mngr.current = 'myaddress'
on_release: root.parent.set_state()
MDNavigationLayout:
id: nav_layout
MDToolbar:
id: toolbar
title: app.current_address_label()
opacity: 1 if app.addressexist() else 0
disabled: False if app.addressexist() else True
pos_hint: {"top": 1}
md_bg_color: app.theme_cls.primary_color
elevation: 10
left_action_items: [['menu', lambda x: nav_drawer.set_state("toggle")]]
right_action_items: [['account-plus', lambda x: app.addingtoaddressbook()]]
ScreenManager:
id: scr_mngr
size_hint_y: None
height: root.height - toolbar.height
Inbox:
id:sc1
# Page:
# id:sc2
Create:
id:sc3
Sent:
id:sc4
Trash:
id:sc5
Login:
id:sc6
Random:
id:sc7
# Spam:
# id:sc8
Setting:
id:sc9
MyAddress:
id:sc10
AddressBook:
id:sc11
Payment:
id:sc12
NetworkStat:
id:sc13
MailDetail:
id:sc14
ShowQRCode:
id:sc15
Draft:
id:sc16
Allmails:
id:sc17
# Credits:
# id:sc18
# Starred:
# id:sc19
# Archieve:
# id:sc20
# ChatRoom:
# id:sc21
# ChatList:
# id:sc22
ScanScreen:
id:sc23
MDNavigationDrawer:
id: nav_drawer
ContentNavigationDrawer:
id: content_drawer
<ArrowImg@Image>:
source: app.image_path +('/down-arrow.png' if self.parent.is_open == True else '/right-arrow.png')
size: 15, 15
x: self.parent.x + self.parent.width - self.width - 5
y: self.parent.y + self.parent.height/2 - self.height + 5
<SearchBar@BoxLayout>:
# id: search_bar
size_hint_y: None
height: self.minimum_height
MDIconButton:
icon: 'magnify'
MDTextField:
id: search_field
hint_text: 'Search'
on_text: app.searchQuery(self)
canvas.before:
Color:
rgba: (0,0,0,1)
<Loader@MDSpinner>:
id: spinner
size_hint: None, None
size: dp(46), dp(46)
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
active: False
<ComposerButton@BoxLayout>:
size_hint_y: None
height: dp(56)
spacing: '10dp'
pos_hint: {'center_x':0.45, 'center_y': .1}
Widget:
MDFloatingActionButton:
icon: 'plus'
opposite_colors: True
elevation_normal: 8
md_bg_color: [0.941, 0, 0,1]
on_press: app.root.ids.scr_mngr.current = 'create'
on_press: app.clear_composer()
<SwipeToDeleteItem>:
size_hint_y: None
height: content.height
MDCardSwipeLayerBox:
padding: "8dp"
MDIconButton:
id: delete_msg
icon: "trash-can"
pos_hint: {"center_y": .5}
md_bg_color: (1, 0, 0, 1)
disabled: True
MDCardSwipeFrontBox:
TwoLineAvatarIconListItem:
id: content
text: root.text
_no_ripple_effect: True
AvatarSampleWidget:
id: avater_img
# source: './images/kivy/avatar.png'
source: None
TimeTagRightSampleWidget:
id: time_tag
text: ''
font_size: "11sp"
font_style: "Caption"
size: [120, 140] if app.app_platform == "android" else [64, 80]
<CutsomSwipeToDeleteItem>:
size_hint_y: None
height: content.height
MDCardSwipeLayerBox:
padding: "8dp"
MDIconButton:
id: delete_msg
icon: "trash-can"
pos_hint: {"center_y": .5}
md_bg_color: (1, 0, 0, 1)
disabled: True
MDCardSwipeFrontBox:
TwoLineAvatarIconListItem:
id: content
text: root.text
_no_ripple_effect: True
AvatarSampleWidget:
id: avater_img
# source: './images/kivy/avatar.png'
source: None
TimeTagRightSampleWidget:
id: time_tag
text: 'time'
font_size: "11sp"
font_style: "Caption"
size: [120, 140] if app.app_platform == "android" else [64, 80]
MDChip:
id: chip_tag
size_hint: (0.16 if app.app_platform == "android" else 0.08, None)
text: 'test'
icon: ""
pos_hint: {"center_x": 0.91 if app.app_platform == "android" else 0.94, "center_y": 0.3}
# height: dp(18)
height: '18dp'
text_color: (1,1,1,1)
radius: [8]

View File

@ -0,0 +1,28 @@
"""
Dummy implementation for kivy Desktop and android(mobile) interface
"""
# pylint: disable=too-few-public-methods
from kivy.app import App
from kivy.uix.label import Label
class NavigateApp(App):
"""Navigation Layout of class"""
def build(self):
"""Method builds the widget"""
# pylint: disable=no-self-use
return Label(text="Hello World !")
def clickNavDrawer(self):
"""method for clicking navigation drawer"""
pass
def addingtoaddressbook(self):
"""method for clicking address book popup"""
pass
if __name__ == '__main__':
NavigateApp().run()

View File

@ -0,0 +1,1005 @@
# pylint: disable=too-many-lines,import-error,no-name-in-module,unused-argument
# pylint: disable=too-many-ancestors,too-many-locals,useless-super-delegation
# pylint: disable=protected-access
# pylint: disable=import-outside-toplevel,ungrouped-imports,wrong-import-order,unused-import,arguments-differ
# pylint: disable=invalid-name,unnecessary-comprehension,broad-except,simplifiable-if-expression,no-member
# pylint: disable=too-many-return-statements
"""
Bitmessage android(mobile) interface
"""
import platform
import os
# import identiconGeneration
# from bitmessagekivy import kivy_helper_search
from pybitmessage.uikivysignaler import UIkivySignaler
from pybitmessage.bmconfigparser import BMConfigParser
# from debug import logger
from functools import partial
from pybitmessage.helper_sql import sqlExecute, sqlQuery
from kivymd.app import MDApp
from kivy.clock import Clock
from kivy.core.clipboard import Clipboard
from kivy.core.window import Window
from kivy.lang import Builder
from kivy.metrics import dp
from kivy.properties import (
BooleanProperty,
ListProperty,
NumericProperty,
ObjectProperty,
StringProperty
)
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.spinner import Spinner
from kivymd.uix.dialog import MDDialog
from kivymd.uix.label import MDLabel
from kivymd.uix.button import MDRaisedButton
from kivymd.uix.list import (
IRightBodyTouch,
OneLineAvatarIconListItem,
OneLineListItem
)
from kivy.uix.screenmanager import RiseInTransition, SlideTransition, FallOutTransition
# import queues
from pybitmessage.semaphores import kivyuisignaler
from pybitmessage import state
from kivymd.uix.bottomsheet import MDCustomBottomSheet
from kivy.lang import Observable
# import gettext
# import l10n
# import locale
import ast
# from pybitmessage.common import toast
# from qr_scanner.zbarcam import ZBarCam
# from pyzbar.pyzbar import ZBarSymbol
def toast(text):
"""Method will display the toast message"""
kivytoast.toast(text)
if platform != "android":
from kivy.config import Config
Config.set("input", "mouse", "mouse, multitouch_on_demand")
elif platform == "android":
from jnius import autoclass, cast
from android.runnable import run_on_ui_thread
from android import python_act as PythonActivity
Toast = autoclass("android.widget.Toast")
String = autoclass("java.lang.String")
CharSequence = autoclass("java.lang.CharSequence")
context = PythonActivity.mActivity
@run_on_ui_thread
def show_toast(text, length):
"""Its showing toast on screen"""
t = Toast.makeText(context, text, length)
t.show()
with open(os.path.join(os.path.dirname(__file__), "screens_data.json")) as read_file:
all_data = ast.literal_eval(read_file.read())
data_screens = list(all_data.keys())
# for modules in data_screens:
# exec(all_data[modules]['Import'])
# pylint: disable=too-few-public-methods,too-many-arguments,attribute-defined-outside-init
class Lang(Observable):
observers = []
lang = None
def __init__(self, defaultlang):
super(Lang, self).__init__()
self.ugettext = None
self.lang = defaultlang
self.switch_lang(self.lang)
def _(self, text):
# return self.ugettext(text)
return text
def fbind(self, name, func, args, **kwargs):
if name == "_":
self.observers.append((func, args, kwargs))
else:
return super(Lang, self).fbind(name, func, *largs, **kwargs)
def funbind(self, name, func, args, **kwargs):
if name == "_":
key = (func, args, kwargs)
if key in self.observers:
self.observers.remove(key)
else:
return super(Lang, self).funbind(name, func, *args, **kwargs)
def switch_lang(self, lang):
# get the right locales directory, and instanciate a gettext
# locale_dir = os.path.join(os.path.dirname(__file__), 'translations', 'mo', 'locales')
# locales = gettext.translation('langapp', locale_dir, languages=[lang])
# self.ugettext = locales.gettext
# update all the kv rules attached to this text
for func, largs, kwargs in self.observers:
func(largs, None, None)
class NavigationItem(OneLineAvatarIconListItem):
"""NavigationItem class for kivy Ui"""
badge_text = StringProperty()
icon = StringProperty()
active = BooleanProperty(False)
def currentlyActive(self):
"""Currenly active"""
for nav_obj in self.parent.children:
nav_obj.active = False
self.active = True
class NavigationDrawerDivider(OneLineListItem):
"""
A small full-width divider that can be placed
in the :class:`MDNavigationDrawer`
"""
disabled = True
divider = None
_txt_top_pad = NumericProperty(dp(8))
_txt_bot_pad = NumericProperty(dp(8))
def __init__(self, **kwargs):
# pylint: disable=bad-super-call
super(OneLineListItem, self).__init__(**kwargs)
self.height = dp(16)
class NavigationDrawerSubheader(OneLineListItem):
"""
A subheader for separating content in :class:`MDNavigationDrawer`
Works well alongside :class:`NavigationDrawerDivider`
"""
disabled = True
divider = None
theme_text_color = 'Secondary'
class ContentNavigationDrawer(BoxLayout):
"""ContentNavigationDrawer class for kivy Uir"""
def __init__(self, *args, **kwargs):
"""Method used for contentNavigationDrawer"""
super(ContentNavigationDrawer, self).__init__(*args, **kwargs)
Clock.schedule_once(self.init_ui, 0)
def init_ui(self, dt=0):
"""Clock Schdule for class contentNavigationDrawer"""
self.ids.scroll_y.bind(scroll_y=self.check_scroll_y)
def check_scroll_y(self, instance, somethingelse):
"""show data on scroll down"""
if self.ids.btn.is_open:
self.ids.btn.is_open = False
class BadgeText(IRightBodyTouch, MDLabel):
"""BadgeText class for kivy Ui"""
class CustomSpinner(Spinner):
"""CustomSpinner class for kivy Ui"""
def __init__(self, *args, **kwargs):
"""Method used for setting size of spinner"""
super(CustomSpinner, self).__init__(*args, **kwargs)
self.dropdown_cls.max_height = Window.size[1] / 3
self.values = list(addr for addr in BMConfigParser().addresses()
if BMConfigParser().get(str(addr), 'enabled') == 'true')
class NavigateApp(MDApp):
"""Navigation Layout of class"""
# pylint: disable=too-many-public-methods,inconsistent-return-statements
# theme_cls = ThemeManager()
previous_date = ObjectProperty()
obj_1 = ObjectProperty()
variable_1 = ListProperty(addr for addr in BMConfigParser().addresses()
if BMConfigParser().get(str(addr), 'enabled') == 'true')
nav_drawer = ObjectProperty()
state.screen_density = Window.size
window_size = state.screen_density
app_platform = platform
title = "PyBitmessage"
imgstatus = False
count = 0
manager_open = False
file_manager = None
state.imageDir = os.path.join('./images', 'kivy')
image_path = state.imageDir
tr = Lang("en") # for changing in franch replace en with fr
def build(self):
"""Method builds the widget"""
for kv in data_screens:
Builder.load_file(
os.path.join(
os.path.dirname(__file__),
'kv',
# f'{all_data[kv]["kv_string"]}.kv',
'{0}.kv'.format(all_data[kv]["kv_string"]),
)
)
print(os.path.join(
os.path.dirname(__file__),
'kv',
# f'{all_data[kv]["kv_string"]}.kv',
'{0}.kv'.format(all_data[kv]["kv_string"]),
))
# self.obj_1 = AddressBook()
kivysignalthread = UIkivySignaler()
kivysignalthread.daemon = True
kivysignalthread.start()
Window.bind(on_keyboard=self.on_key, on_request_close=self.on_request_close)
# return Builder.load_file('/home/cis/Bitmessagepeter/KivyPoject/PyBitmessage/src/tests/mock/pybitmessage/main.kv')
# import pdb; pdb.set_trace()
return Builder.load_file(
os.path.join(os.path.dirname(__file__), 'main.kv'))
def run(self):
"""Running the widgets"""
print('def run(self): ------------')
kivyuisignaler.release()
print('kivyuisignaler.release()')
super(NavigateApp, self).run()
@staticmethod
def showmeaddresses(name="text"):
"""Show the addresses in spinner to make as dropdown"""
if name == "text":
if BMConfigParser().addresses():
return BMConfigParser().addresses()[0][:16] + '..'
return "textdemo"
elif name == "values":
if BMConfigParser().addresses():
return [address[:16] + '..'
for address in BMConfigParser().addresses()]
return "valuesdemo"
def getCurrentAccountData(self, text):
"""Get Current Address Account Data"""
print("$$$$ getCurrentAccountData called")
if text != '':
if os.path.exists(state.imageDir + '/default_identicon/{}.png'.format(text)):
self.load_selected_Image(text)
else:
self.set_identicon(text)
self.root.ids.content_drawer.ids.reset_image.opacity = 0
self.root.ids.content_drawer.ids.reset_image.disabled = True
address_label = self.current_address_label(
BMConfigParser().get(text, 'label'), text)
self.root_window.children[1].ids.toolbar.title = address_label
state.association = text
state.searcing_text = ''
LoadingPopup().open()
self.set_message_count()
for nav_obj in self.root.ids.content_drawer.children[
0].children[0].children[0].children:
nav_obj.active = True if nav_obj.text == 'Inbox' else False
self.fileManagerSetting()
Clock.schedule_once(self.setCurrentAccountData, 0.5)
def fileManagerSetting(self):
"""This method is for file manager setting"""
if not self.root.ids.content_drawer.ids.file_manager.opacity and \
self.root.ids.content_drawer.ids.file_manager.disabled:
self.root.ids.content_drawer.ids.file_manager.opacity = 1
self.root.ids.content_drawer.ids.file_manager.disabled = False
def setCurrentAccountData(self, dt=0):
"""This method set the current accout data on all the screens"""
self.root.ids.sc1.ids.ml.clear_widgets()
self.root.ids.sc1.loadMessagelist(state.association)
self.root.ids.sc4.ids.ml.clear_widgets()
self.root.ids.sc4.children[2].children[2].ids.search_field.text = ''
self.root.ids.sc4.loadSent(state.association)
self.root.ids.sc16.clear_widgets()
self.root.ids.sc16.add_widget(Draft())
self.root.ids.sc5.clear_widgets()
self.root.ids.sc5.add_widget(Trash())
self.root.ids.sc17.clear_widgets()
self.root.ids.sc17.add_widget(Allmails())
self.root.ids.sc10.ids.ml.clear_widgets()
self.root.ids.sc10.init_ui()
self.root.ids.scr_mngr.current = 'inbox'
@staticmethod
def getCurrentAccount():
"""It uses to get current account label"""
if state.association:
return state.association
return "Bitmessage Login"
# @staticmethod
def addingtoaddressbook(self):
"""Adding to address Book"""
width = .85 if platform == 'android' else .8
self.add_popup = MDDialog(
title='Add contact\'s',
type="custom",
size_hint=(width, .23),
content_cls=GrashofPopup(),
buttons=[
MDRaisedButton(
text="Save",
on_release=self.savecontact,
),
MDRaisedButton(
text="Cancel",
on_release=self.close_pop,
),
MDRaisedButton(
text="Scan QR code",
on_release=self.scan_qr_code,
),
],
)
# self.add_popup.set_normal_height()
self.add_popup.auto_dismiss = False
self.add_popup.open()
# p = GrashofPopup()
# p.open()
def scan_qr_code(self, instance):
"""this method is used for showing QR code scanner"""
if self.is_camara_attached():
self.add_popup.dismiss()
self.root.ids.sc23.get_screen(self.root.ids.scr_mngr.current, self.add_popup)
self.root.ids.scr_mngr.current = 'scanscreen'
else:
altet_txt = (
'Currently this feature is not avaialbe!' if platform == 'android' else 'Camera is not available!')
self.add_popup.dismiss()
toast(altet_txt)
def is_camara_attached(self):
"""This method is for checking is camera available or not"""
self.root.ids.sc23.check_camera()
is_available = self.root.ids.sc23.camera_avaialbe
return is_available
def savecontact(self, instance):
"""Method is used for saving contacts"""
pupup_obj = self.add_popup.content_cls
label = pupup_obj.ids.label.text.strip()
address = pupup_obj.ids.address.text.strip()
if label == '' and address == '':
pupup_obj.ids.label.focus = True
pupup_obj.ids.address.focus = True
elif address == '':
pupup_obj.ids.address.focus = True
elif label == '':
pupup_obj.ids.label.focus = True
else:
pupup_obj.ids.address.focus = True
# pupup_obj.ids.label.focus = True
stored_address = [addr[1] for addr in kivy_helper_search.search_sql(
folder="addressbook")]
stored_labels = [labels[0] for labels in kivy_helper_search.search_sql(
folder="addressbook")]
if label and address and address not in stored_address \
and label not in stored_labels and pupup_obj.valid:
# state.navinstance = self.parent.children[1]
queues.UISignalQueue.put(('rerenderAddressBook', ''))
self.add_popup.dismiss()
sqlExecute("INSERT INTO addressbook VALUES(?,?)", label, address)
try:
rootIds = self.root.ids
except Exception as e:
rootIds = state.kivyapp.root.ids
rootIds.sc11.ids.ml.clear_widgets()
rootIds.sc11.loadAddresslist(None, 'All', '')
rootIds.scr_mngr.current = 'addressbook'
toast('Saved')
def close_pop(self, instance):
"""Pop is Canceled"""
self.add_popup.dismiss()
toast('Canceled')
def getDefaultAccData(self, instance):
"""Getting Default Account Data"""
print("@@@@ getDefaultAccData called")
if self.variable_1:
state.association = first_addr = self.variable_1[0]
# if BMConfigParser().get(str(first_addr), 'enabled') == 'true':
# img = identiconGeneration.generate(first_addr)
# print('line...........................................426')
# self.createFolder(state.imageDir + '/default_identicon/')
# if platform == 'android':
# # android_path = os.path.expanduser
# # ("~/user/0/org.test.bitapp/files/app/")
# if not os.path.exists(state.imageDir + '/default_identicon/{}.png'.format(
# BMConfigParser().addresses()[0])):
# android_path = os.path.join(
# os.environ['ANDROID_PRIVATE'] + '/app/')
# img.texture.save('{1}/images/kivy/default_identicon/{0}.png'.format(
# BMConfigParser().addresses()[0], android_path))
# else:
# if not os.path.exists(state.imageDir + '/default_identicon/{}.png'.format(
# BMConfigParser().addresses()[0])):
# img.texture.save(state.imageDir + '/default_identicon/{}.png'.format(
# BMConfigParser().addresses()[0]))
# instance.parent.parent.parent.parent.parent.ids.top_box.children[0].texture = (
# img.texture)
return first_addr
return 'Select Address'
def get_default_logo(self, instance):
"""Getting default logo image"""
if self.variable_1:
first_addr = self.variable_1[0]
if BMConfigParser().get(str(first_addr), 'enabled') == 'true':
if os.path.exists(
state.imageDir + '/default_identicon/{}.png'.format(first_addr)):
return state.imageDir + '/default_identicon/{}.png'.format(
first_addr)
# else:
# img = identiconGeneration.generate(first_addr)
# instance.texture = img.texture
# return
return state.imageDir + '/drawer_logo1.png'
@staticmethod
def addressexist():
"""Checking address existence"""
if BMConfigParser().addresses():
return True
return False
def on_key(self, window, key, *args):
# import
# pylint: disable=inconsistent-return-statements, too-many-branches
"""Method is used for going on previous screen"""
if key == 27:
if state.in_search_mode and self.root.ids.scr_mngr.current not in [
"mailDetail", "create"]:
self.closeSearchScreen()
elif self.root.ids.scr_mngr.current == "mailDetail":
self.root.ids.scr_mngr.current = 'sent'\
if state.detailPageType == 'sent' else 'inbox' \
if state.detailPageType == 'inbox' else 'draft'
self.back_press()
if state.in_search_mode and state.searcing_text:
toolbar_obj = self.root.ids.toolbar
toolbar_obj.left_action_items = [
['arrow-left', lambda x: self.closeSearchScreen()]]
toolbar_obj.right_action_items = []
self.root.ids.toolbar.title = ''
elif self.root.ids.scr_mngr.current == "create":
self.save_draft()
self.set_common_header()
state.in_composer = False
self.root.ids.scr_mngr.current = 'inbox'
elif self.root.ids.scr_mngr.current == "showqrcode":
self.set_common_header()
self.root.ids.scr_mngr.current = 'myaddress'
elif self.root.ids.scr_mngr.current == "random":
self.root.ids.scr_mngr.current = 'login'
elif self.root.ids.scr_mngr.current == 'pay-options':
self.set_common_header()
self.root.ids.scr_mngr.current = 'payment'
elif self.root.ids.scr_mngr.current == 'chroom':
if state.association:
address_label = self.current_address_label(
BMConfigParser().get(
state.association, 'label'), state.association)
self.root.ids.toolbar.title = address_label
self.set_common_header()
self.root.ids.scr_mngr.transition = FallOutTransition()
self.root.ids.scr_mngr.current = 'chlist'
self.root.ids.scr_mngr.transition = SlideTransition()
else:
if state.kivyapp.variable_1:
self.root.ids.scr_mngr.current = 'inbox'
self.root.ids.scr_mngr.transition.direction = 'right'
self.root.ids.scr_mngr.transition.bind(on_complete=self.reset)
return True
elif key == 13 and state.searcing_text and not state.in_composer:
if state.search_screen == 'inbox':
self.root.ids.sc1.children[1].active = True
Clock.schedule_once(self.search_callback, 0.5)
elif state.search_screen == 'addressbook':
self.root.ids.sc11.children[1].active = True
Clock.schedule_once(self.search_callback, 0.5)
elif state.search_screen == 'myaddress':
self.loadMyAddressScreen(True)
Clock.schedule_once(self.search_callback, 0.5)
elif state.search_screen == 'sent':
self.root.ids.sc4.children[1].active = True
Clock.schedule_once(self.search_callback, 0.5)
def search_callback(self, dt=0):
"""Show data after loader is loaded"""
if state.search_screen == 'inbox':
self.root.ids.sc1.ids.ml.clear_widgets()
self.root.ids.sc1.loadMessagelist(state.association)
self.root.ids.sc1.children[1].active = False
elif state.search_screen == 'addressbook':
self.root.ids.sc11.ids.ml.clear_widgets()
self.root.ids.sc11.loadAddresslist(None, 'All', '')
self.root.ids.sc11.children[1].active = False
elif state.search_screen == 'myaddress':
self.root.ids.sc10.ids.ml.clear_widgets()
self.root.ids.sc10.init_ui()
self.loadMyAddressScreen(False)
else:
self.root.ids.sc4.ids.ml.clear_widgets()
self.root.ids.sc4.loadSent(state.association)
self.root.ids.sc4.children[1].active = False
self.root.ids.scr_mngr.current = state.search_screen
def loadMyAddressScreen(self, action):
"""loadMyAddressScreen method spin the loader"""
if len(self.root.ids.sc10.children) <= 2:
self.root.ids.sc10.children[0].active = action
else:
self.root.ids.sc10.children[1].active = action
def save_draft(self):
"""Saving drafts messages"""
composer_objs = self.root
from_addr = str(self.root.ids.sc3.children[1].ids.ti.text)
# to_addr = str(self.root.ids.sc3.children[1].ids.txt_input.text)
if from_addr and state.detailPageType != 'draft' \
and not state.in_sent_method:
Draft().draft_msg(composer_objs)
return
def reset(self, *args):
"""Set transition direction"""
self.root.ids.scr_mngr.transition.direction = 'left'
self.root.ids.scr_mngr.transition.unbind(on_complete=self.reset)
@staticmethod
def status_dispatching(data):
"""Dispatching Status acknowledgment"""
ackData, message = data
if state.ackdata == ackData:
state.status.status = message
def clear_composer(self):
"""If slow down, the new composer edit screen"""
self.set_navbar_for_composer()
composer_obj = self.root.ids.sc3.children[1].ids
composer_obj.ti.text = ''
composer_obj.btn.text = 'Select'
composer_obj.txt_input.text = ''
composer_obj.subject.text = ''
composer_obj.body.text = ''
state.in_composer = True
state.in_sent_method = False
def set_navbar_for_composer(self):
"""Clearing toolbar data when composer open"""
self.root.ids.toolbar.left_action_items = [
['arrow-left', lambda x: self.back_press()]]
self.root.ids.toolbar.right_action_items = [
['refresh',
lambda x: self.root.ids.sc3.children[1].reset_composer()],
['send',
lambda x: self.root.ids.sc3.children[1].send(self)]]
def set_toolbar_for_QrCode(self):
"""This method is use for setting Qr code toolbar."""
self.root.ids.toolbar.left_action_items = [
['arrow-left', lambda x: self.back_press()]]
self.root.ids.toolbar.right_action_items = []
def set_common_header(self):
"""Common header for all window"""
self.root.ids.toolbar.right_action_items = [
['account-plus', lambda x: self.addingtoaddressbook()]]
# self.root.ids.toolbar.left_action_items = [
# ['menu', lambda x: self.root.toggle_nav_drawer()]]
self.root.ids.toolbar.left_action_items = [
['menu', lambda x: self.root.ids.nav_drawer.set_state("toggle")]]
return
def back_press(self):
"""Method for, reverting composer to previous page"""
if self.root.ids.scr_mngr.current == 'create':
self.save_draft()
if self.root.ids.scr_mngr.current == \
'mailDetail' and state.in_search_mode:
toolbar_obj = self.root.ids.toolbar
toolbar_obj.left_action_items = [
['arrow-left', lambda x: self.closeSearchScreen()]]
toolbar_obj.right_action_items = []
self.root.ids.toolbar.title = ''
else:
self.set_common_header()
if self.root.ids.scr_mngr.current == 'chroom' and state.association:
self.root.ids.scr_mngr.transition = FallOutTransition()
address_label = self.current_address_label(
BMConfigParser().get(
state.association, 'label'), state.association)
self.root.ids.toolbar.title = address_label
self.root.ids.scr_mngr.current = 'inbox' \
if state.in_composer else 'allmails'\
if state.is_allmail else state.detailPageType\
if state.detailPageType else 'myaddress'\
if self.root.ids.scr_mngr.current == 'showqrcode' else 'payment'\
if self.root.ids.scr_mngr.current == 'pay-options' else 'chlist'\
if self.root.ids.scr_mngr.current == 'chroom' else 'inbox'
if self.root.ids.scr_mngr.current == 'chlist':
self.root.ids.scr_mngr.transition = SlideTransition()
self.root.ids.scr_mngr.transition.direction = 'right'
self.root.ids.scr_mngr.transition.bind(on_complete=self.reset)
if state.is_allmail or state.detailPageType == 'draft':
state.is_allmail = False
state.detailPageType = ''
state.in_composer = False
@staticmethod
def get_inbox_count():
"""Getting inbox count"""
# state.inbox_count = str(sqlQuery(
# "SELECT COUNT(*) FROM inbox WHERE toaddress = '{}' and"
# " folder = 'inbox' ;".format(state.association))[0][0])
state.inbox_count = 0
@staticmethod
def get_sent_count():
"""Getting sent count"""
# state.sent_count = str(sqlQuery(
# "SELECT COUNT(*) FROM sent WHERE fromaddress = '{}' and"
# " folder = 'sent' ;".format(state.association))[0][0])
state.sent_count = 0
def set_message_count(self):
"""Setting message count"""
# msg_counter_objs = state.kivyapp.root.children[0].children[0].ids
# try:
# msg_counter_objs = (
# self.root_window.children[0].children[2].children[0].ids)
# except Exception:
# msg_counter_objs = (
# self.root_window.children[2].children[2].children[0].ids)
self.get_inbox_count()
self.get_sent_count()
# state.trash_count = str(sqlQuery(
# "SELECT (SELECT count(*) FROM sent"
# " where fromaddress = '{0}' and folder = 'trash' )"
# "+(SELECT count(*) FROM inbox where toaddress = '{0}' and"
# " folder = 'trash') AS SumCount".format(state.association))[0][0])
# state.draft_count = str(sqlQuery(
# "SELECT COUNT(*) FROM sent WHERE fromaddress = '{}' and"
# " folder = 'draft' ;".format(state.association))[0][0])
# state.all_count = str(int(state.sent_count) + int(state.inbox_count))
state.all_count = 0
# if msg_counter_objs:
# msg_counter_objs.send_cnt.badge_text = 0
# msg_counter_objs.inbox_cnt.badge_text = 0
# msg_counter_objs.trash_cnt.badge_text = 0
# msg_counter_objs.draft_cnt.badge_text = 0
# msg_counter_objs.allmail_cnt.badge_text = 0
def on_start(self):
"""Setting message count"""
print('on start ------------ ')
self.set_message_count()
# @staticmethod
# def on_stop():
# """On stop methos is used for stoping the runing script"""
# print("*******************EXITING FROM APPLICATION*******************")
# import shutdown
# shutdown.doCleanShutdown()
@staticmethod
def current_address_label(current_add_label=None, current_addr=None):
"""Getting current address labels"""
addresses = [addr for addr in BMConfigParser().addresses()
if BMConfigParser().get(str(addr), 'enabled') == 'true']
if addresses:
if current_add_label:
first_name = current_add_label
addr = current_addr
else:
addr = addresses[0]
first_name = BMConfigParser().get(addr, 'label')
if BMConfigParser().get(addr, 'enabled') != 'true':
return ''
f_name = first_name.split()
label = f_name[0][:14].capitalize() + '...' if len(
f_name[0]) > 15 else f_name[0].capitalize()
address = ' (' + addr + ')'
return label + address
return ''
def searchQuery(self, instance):
"""Showing searched mails"""
state.search_screen = self.root.ids.scr_mngr.current
state.searcing_text = str(instance.text).strip()
if instance.focus and state.searcing_text:
toolbar_obj = self.root.ids.toolbar
toolbar_obj.left_action_items = [
['arrow-left', lambda x: self.closeSearchScreen()]]
toolbar_obj.right_action_items = []
self.root.ids.toolbar.title = ''
state.in_search_mode = True
def closeSearchScreen(self):
"""Function for close search screen"""
self.set_common_header()
if state.association:
address_label = self.current_address_label(
BMConfigParser().get(
state.association, 'label'), state.association)
self.root.ids.toolbar.title = address_label
state.searcing_text = ''
self.refreshScreen()
state.in_search_mode = False
def refreshScreen(self):
"""Method show search button only on inbox or sent screen"""
# pylint: disable=unused-variable
state.searcing_text = ''
if state.search_screen == 'inbox':
self.root.ids.sc1.ids.inbox_search.ids.search_field.text = ''
# try:
# self.root.ids.sc1.children[
# 3].children[2].ids.search_field.text = ''
# except Exception:
# self.root.ids.sc1.children[
# 2].children[2].ids.search_field.text = ''
self.root.ids.sc1.children[1].active = True
Clock.schedule_once(self.search_callback, 0.5)
elif state.search_screen == 'addressbook':
self.root.ids.sc11.ids.address_search.ids.search_field.text = ''
# self.root.ids.sc11.children[
# 2].children[2].ids.search_field.text = ''
self.root.ids.sc11.children[
1].active = True
Clock.schedule_once(self.search_callback, 0.5)
elif state.search_screen == 'myaddress':
self.root.ids.sc10.ids.search_bar.ids.search_field.text = ''
# try:
# self.root.ids.sc10.children[
# 1].children[2].ids.search_field.text = ''
# except Exception:
# self.root.ids.sc10.children[
# 2].children[2].ids.search_field.text = ''
self.loadMyAddressScreen(True)
Clock.schedule_once(self.search_callback, 0.5)
else:
self.root.ids.sc4.ids.sent_search.ids.search_field.text = ''
# self.root.ids.sc4.children[
# 2].children[2].ids.search_field.text = ''
self.root.ids.sc4.children[1].active = True
Clock.schedule_once(self.search_callback, 0.5)
return
def set_identicon(self, text):
"""Show identicon in address spinner"""
# img = identiconGeneration.generate(text)
img = open('/home/cis/Bitmessagepeter/KivyPoject/PyBitmessage/src/images/kivy/drawer_logo1.png')
# self.root.children[0].children[0].ids.btn.children[1].texture = (img.texture)
# below line is for displaing logo
self.root.ids.content_drawer.ids.top_box.children[0].texture = (img.texture)
def set_mail_detail_header(self):
"""Setting the details of the page"""
if state.association and state.in_search_mode:
address_label = self.current_address_label(
BMConfigParser().get(
state.association, 'label'), state.association)
self.root.ids.toolbar.title = address_label
toolbar_obj = self.root.ids.toolbar
toolbar_obj.left_action_items = [
['arrow-left', lambda x: self.back_press()]]
delete_btn = ['delete-forever',
lambda x: self.root.ids.sc14.delete_mail()]
dynamic_list = []
if state.detailPageType == 'inbox':
dynamic_list = [
['reply', lambda x: self.root.ids.sc14.inbox_reply()],
delete_btn]
elif state.detailPageType == 'sent':
dynamic_list = [delete_btn]
elif state.detailPageType == 'draft':
dynamic_list = [
['pencil', lambda x: self.root.ids.sc14.write_msg(self)],
delete_btn]
toolbar_obj.right_action_items = dynamic_list
def load_screen(self, instance):
"""This method is used for loading screen on every click"""
if instance.text == 'Inbox':
self.root.ids.scr_mngr.current = 'inbox'
self.root.ids.sc1.children[1].active = True
elif instance.text == 'All Mails':
self.root.ids.scr_mngr.current = 'allmails'
try:
self.root.ids.sc17.children[1].active = True
except Exception:
self.root.ids.sc17.children[0].children[1].active = True
elif instance.text == 'Trash':
self.root.ids.scr_mngr.current = 'trash'
try:
self.root.ids.sc5.children[1].active = True
except Exception as e:
self.root.ids.sc5.children[0].children[1].active = True
Clock.schedule_once(partial(self.load_screen_callback, instance), 1)
def load_screen_callback(self, instance, dt=0):
"""This method is rotating loader for few seconds"""
if instance.text == 'Inbox':
self.root.ids.sc1.ids.ml.clear_widgets()
self.root.ids.sc1.loadMessagelist(state.association)
self.root.ids.sc1.children[1].active = False
elif instance.text == 'All Mails':
self.root.ids.sc17.clear_widgets()
self.root.ids.sc17.add_widget(Allmails())
try:
self.root.ids.sc17.children[1].active = False
except Exception:
self.root.ids.sc17.children[0].children[1].active = False
elif instance.text == 'Trash':
# self.root.ids.sc5.ids.ml.clear_widgets()
# self.root.ids.sc5.init_ui(0)
self.root.ids.sc5.clear_widgets()
self.root.ids.sc5.add_widget(Trash())
try:
self.root.ids.sc5.children[1].active = False
except Exception as e:
self.root.ids.sc5.children[0].children[1].active = False
def on_request_close(self, *args): # pylint: disable=no-self-use
"""This method is for app closing request"""
# AppClosingPopup().open()
return True
def file_manager_open(self):
"""This method open the file manager of local system"""
from kivymd.uix.filemanager import MDFileManager
if not self.file_manager:
self.file_manager = MDFileManager(
exit_manager=self.exit_manager,
select_path=self.select_path,
ext=['.png', '.jpg']
)
self.file_manager.previous = False
self.file_manager.current_path = '/'
if platform == 'android':
from android.permissions import request_permissions, Permission, check_permission
if check_permission(Permission.WRITE_EXTERNAL_STORAGE) and \
check_permission(Permission.READ_EXTERNAL_STORAGE):
self.file_manager.show(os.getenv('EXTERNAL_STORAGE'))
self.manager_open = True
else:
request_permissions([Permission.WRITE_EXTERNAL_STORAGE, Permission.READ_EXTERNAL_STORAGE])
else:
self.file_manager.show(os.environ["HOME"])
self.manager_open = True
def select_path(self, path):
"""This method is used to save the select image"""
try:
from PIL import Image as PilImage
newImg = PilImage.open(path).resize((300, 300))
if platform == 'android':
android_path = os.path.join(
os.environ['ANDROID_PRIVATE'] + '/app' + '/images' + '/kivy/')
if not os.path.exists(android_path + '/default_identicon/'):
os.makedirs(android_path + '/default_identicon/')
newImg.save('{1}/default_identicon/{0}.png'.format(
state.association, android_path))
else:
if not os.path.exists(state.imageDir + '/default_identicon/'):
os.makedirs(state.imageDir + '/default_identicon/')
newImg.save(state.imageDir + '/default_identicon/{0}.png'.format(state.association))
self.load_selected_Image(state.association)
toast('Image changed')
except Exception:
toast('Exit')
self.exit_manager()
def exit_manager(self, *args):
"""Called when the user reaches the root of the directory tree."""
self.manager_open = False
self.file_manager.close()
def load_selected_Image(self, curerentAddr):
"""This method load the selected image on screen"""
top_box_obj = self.root.ids.content_drawer.ids.top_box.children[0]
# spinner_img_obj = self.root.ids.content_drawer.ids.btn.children[1]
# spinner_img_obj.source = top_box_obj.source ='./images/default_identicon/{0}.png'.format(curerentAddr)
top_box_obj.source = state.imageDir + '/default_identicon/{0}.png'.format(curerentAddr)
self.root.ids.content_drawer.ids.reset_image.opacity = 1
self.root.ids.content_drawer.ids.reset_image.disabled = False
top_box_obj.reload()
# spinner_img_obj.reload()
def rest_default_avatar_img(self):
"""set default avatar generated image"""
self.set_identicon(state.association)
img_path = state.imageDir + '/default_identicon/{}.png'.format(state.association)
try:
if os.path.exists(img_path):
os.remove(img_path)
self.root.ids.content_drawer.ids.reset_image.opacity = 0
self.root.ids.content_drawer.ids.reset_image.disabled = True
except Exception as e:
pass
toast('Avatar reset')
def copy_composer_text(self, text): # pylint: disable=no-self-use
"""Copy the data from mail detail page"""
Clipboard.copy(text)
toast('Copied')
def reset_login_screen(self):
"""This method is used for clearing random screen"""
if self.root.ids.sc7.ids.add_random_bx.children:
self.root.ids.sc7.ids.add_random_bx.clear_widgets()
def open_payment_layout(self, sku):
"""It basically open up a payment layout for kivy Ui"""
pml = PaymentMethodLayout()
self.product_id = sku
self.custom_sheet = MDCustomBottomSheet(screen=pml)
self.custom_sheet.open()
def initiate_purchase(self, method_name):
"""initiate_purchase module"""
print("Purchasing {} through {}".format(self.product_id, method_name))
def _after_scan(self, text):
# if platform == 'android':
# toast_txt = cast(CharSequence, String(text))
# show_toast(toast_txt, Toast.LENGTH_SHORT)
if self.root.ids.sc23.previous_open_screen == 'composer':
self.root.ids.sc3.children[1].ids.txt_input.text = text
self.root.ids.scr_mngr.current = 'create'
elif self.root.ids.sc23.previous_open_screen:
back_screen = self.root.ids.sc23.previous_open_screen
self.root.ids.scr_mngr.current = 'inbox' if back_screen == 'scanscreen' else back_screen
add_obj = self.root.ids.sc23.pop_up_instance
add_obj.content_cls.ids.address.text = text
Clock.schedule_once(partial(self.open_popup, add_obj), .5)
@staticmethod
def open_popup(instance, dt):
"""This method is used for opening popup"""
instance.open()
class PaymentMethodLayout(BoxLayout):
"""PaymentMethodLayout class for kivy Ui"""

View File

@ -0,0 +1,48 @@
"""
Announce addresses as they are received from other hosts
"""
import queue as Queue
import state
from helper_random import randomshuffle
from network.assemble import assemble_addr
from network.connectionpool import BMConnectionPool
from queues import addrQueue
from network.threads import StoppableThread
class AddrThread(StoppableThread):
"""(Node) address broadcasting thread"""
name = "AddrBroadcaster"
def run(self):
while not state.shutdown:
chunk = []
while True:
try:
data = addrQueue.get(False)
chunk.append(data)
except Queue.Empty:
break
if chunk:
# Choose peers randomly
connections = BMConnectionPool().establishedConnections()
randomshuffle(connections)
for i in connections:
randomshuffle(chunk)
filtered = []
for stream, peer, seen, destination in chunk:
# peer's own address or address received from peer
if i.destination in (peer, destination):
continue
if stream not in i.streams:
continue
filtered.append((stream, peer, seen))
if filtered:
i.append_write_buf(assemble_addr(filtered))
addrQueue.iterate()
for i in range(len(chunk)):
addrQueue.task_done()
self.stop.wait(1)

View File

@ -0,0 +1,174 @@
"""
Improved version of asyncore dispatcher
"""
# pylint: disable=attribute-defined-outside-init
import socket
import threading
import time
import network.asyncore_pollchoose as asyncore
import state
from network.threads import BusyError, nonBlocking
class ProcessingError(Exception):
"""General class for protocol parser exception,
use as a base for others."""
pass
class UnknownStateError(ProcessingError):
"""Parser points to an unknown (unimplemented) state."""
pass
class AdvancedDispatcher(asyncore.dispatcher):
"""Improved version of asyncore dispatcher,
with buffers and protocol state."""
# pylint: disable=too-many-instance-attributes
_buf_len = 131072 # 128kB
def __init__(self, sock=None):
# python 2 below condition is used
# if not hasattr(self, '_map'):
# python 3 below condition is used
if '_map' not in dir(self):
asyncore.dispatcher.__init__(self, sock)
self.read_buf = bytearray()
self.write_buf = bytearray()
self.state = "init"
self.lastTx = time.time()
self.sentBytes = 0
self.receivedBytes = 0
self.expectBytes = 0
self.readLock = threading.RLock()
self.writeLock = threading.RLock()
self.processingLock = threading.RLock()
def append_write_buf(self, data):
"""Append binary data to the end of stream write buffer."""
if data:
if isinstance(data, list):
with self.writeLock:
for chunk in data:
self.write_buf.extend(chunk)
else:
with self.writeLock:
self.write_buf.extend(data)
def slice_write_buf(self, length=0):
"""Cut the beginning of the stream write buffer."""
if length > 0:
with self.writeLock:
if length >= len(self.write_buf):
del self.write_buf[:]
else:
del self.write_buf[0:length]
def slice_read_buf(self, length=0):
"""Cut the beginning of the stream read buffer."""
if length > 0:
with self.readLock:
if length >= len(self.read_buf):
del self.read_buf[:]
else:
del self.read_buf[0:length]
def process(self):
"""Process (parse) data that's in the buffer,
as long as there is enough data and the connection is open."""
while self.connected and not state.shutdown:
try:
with nonBlocking(self.processingLock):
if not self.connected or state.shutdown:
break
if len(self.read_buf) < self.expectBytes:
return False
try:
cmd = getattr(self, "state_" + str(self.state))
except AttributeError:
self.logger.error(
'Unknown state %s', self.state, exc_info=True)
raise UnknownStateError(self.state)
if not cmd():
break
except BusyError:
return False
return False
def set_state(self, state_str, length=0, expectBytes=0):
"""Set the next processing state."""
self.expectBytes = expectBytes
self.slice_read_buf(length)
self.state = state_str
def writable(self):
"""Is data from the write buffer ready to be sent to the network?"""
self.uploadChunk = AdvancedDispatcher._buf_len
if asyncore.maxUploadRate > 0:
self.uploadChunk = int(asyncore.uploadBucket)
self.uploadChunk = min(self.uploadChunk, len(self.write_buf))
return asyncore.dispatcher.writable(self) and (
self.connecting or (
self.connected and self.uploadChunk > 0))
def readable(self):
"""Is the read buffer ready to accept data from the network?"""
self.downloadChunk = AdvancedDispatcher._buf_len
if asyncore.maxDownloadRate > 0:
self.downloadChunk = int(asyncore.downloadBucket)
try:
if self.expectBytes > 0 and not self.fullyEstablished:
self.downloadChunk = min(
self.downloadChunk, self.expectBytes - len(self.read_buf))
if self.downloadChunk < 0:
self.downloadChunk = 0
except AttributeError:
pass
return asyncore.dispatcher.readable(self) and (
self.connecting or self.accepting or (
self.connected and self.downloadChunk > 0))
def handle_read(self):
"""Append incoming data to the read buffer."""
self.lastTx = time.time()
newData = self.recv(self.downloadChunk)
self.receivedBytes += len(newData)
asyncore.update_received(len(newData))
with self.readLock:
self.read_buf.extend(newData)
def handle_write(self):
"""Send outgoing data from write buffer."""
self.lastTx = time.time()
written = self.send(self.write_buf[0:self.uploadChunk])
asyncore.update_sent(written)
self.sentBytes += written
self.slice_write_buf(written)
def handle_connect_event(self):
"""Callback for connection established event."""
try:
asyncore.dispatcher.handle_connect_event(self)
except socket.error as e:
# pylint: disable=protected-access
if e.args[0] not in asyncore._DISCONNECTED:
raise
def handle_connect(self):
"""Method for handling connection established implementations."""
self.lastTx = time.time()
def state_close(self): # pylint: disable=no-self-use
"""Signal to the processing loop to end."""
return False
def handle_close(self):
"""Callback for connection being closed,
but can also be called directly when you want connection to close."""
with self.readLock:
self.read_buf = bytearray()
with self.writeLock:
self.write_buf = bytearray()
self.set_state("close")
self.close()

View File

@ -0,0 +1,46 @@
"""
Announce myself (node address)
"""
import time
import state
from bmconfigparser import BMConfigParser
from network.assemble import assemble_addr
from network.connectionpool import BMConnectionPool
from network.udp import UDPSocket
from network.node import Peer
from network.threads import StoppableThread
class AnnounceThread(StoppableThread):
"""A thread to manage regular announcing of this node"""
name = "Announcer"
def run(self):
lastSelfAnnounced = 0
while not self._stopped and state.shutdown == 0:
processed = 0
if lastSelfAnnounced < time.time() - UDPSocket.announceInterval:
self.announceSelf()
lastSelfAnnounced = time.time()
if processed == 0:
self.stop.wait(10)
@staticmethod
def announceSelf():
"""Announce our presence"""
for connection in [udpSockets for udpSockets in BMConnectionPool().udpSockets.values()]:
if not connection.announcing:
continue
for stream in state.streamsInWhichIAmParticipating:
addr = (
stream,
# state.Peer('127.0.0.1',int( BMConfigParser().safeGet("bitmessagesettings", "port"))),
# int(time.time()))
# connection.append_write_buf(BMProto.assembleAddr([addr]))
Peer(
'127.0.0.1',
BMConfigParser().safeGetInt(
'bitmessagesettings', 'port')),
time.time())
connection.append_write_buf(assemble_addr([addr]))

View File

@ -0,0 +1,32 @@
"""
Create bitmessage protocol command packets
"""
import struct
import addresses
from network.constants import MAX_ADDR_COUNT
from network.node import Peer
from protocol import CreatePacket, encodeHost
def assemble_addr(peerList):
"""Create address command"""
if isinstance(peerList, Peer):
peerList = [peerList]
if not peerList:
return bytes()
retval = bytes()
for i in range(0, len(peerList), MAX_ADDR_COUNT):
payload = addresses.encodeVarint(len(peerList[i:i + MAX_ADDR_COUNT]))
for stream, peer, timestamp in peerList[i:i + MAX_ADDR_COUNT]:
payload += struct.pack(
'>Q', int(timestamp)) # 64-bit time
payload += struct.pack('>I', stream)
# service bit flags offered by this node
payload += struct.pack('>q', 1)
payload += encodeHost(peer.host)
# remote port
payload += struct.pack('>H', peer.port)
retval += CreatePacket('addr', payload)
return retval

View File

@ -0,0 +1,1012 @@
"""
Basic infrastructure for asynchronous socket service clients and servers.
"""
# -*- Mode: Python -*-
# Id: asyncore.py,v 2.51 2000/09/07 22:29:26 rushing Exp
# Author: Sam Rushing <rushing@nightmare.com>
# pylint: disable=too-many-branches,too-many-lines,global-statement
# pylint: disable=redefined-builtin,no-self-use
import os
import select
import socket
import sys
import time
import warnings
from errno import (
EADDRINUSE, EAGAIN, EALREADY, EBADF, ECONNABORTED, ECONNREFUSED,
ECONNRESET, EHOSTUNREACH, EINPROGRESS, EINTR, EINVAL, EISCONN, ENETUNREACH,
ENOTCONN, ENOTSOCK, EPIPE, ESHUTDOWN, ETIMEDOUT, EWOULDBLOCK, errorcode
)
from threading import current_thread
from pybitmessage import helper_random
try:
from errno import WSAEWOULDBLOCK
except (ImportError, AttributeError):
WSAEWOULDBLOCK = EWOULDBLOCK
try:
from errno import WSAENOTSOCK
except (ImportError, AttributeError):
WSAENOTSOCK = ENOTSOCK
try:
from errno import WSAECONNRESET
except (ImportError, AttributeError):
WSAECONNRESET = ECONNRESET
try:
# Desirable side-effects on Windows; imports winsock error numbers
from errno import WSAEADDRINUSE # pylint: disable=unused-import
except (ImportError, AttributeError):
WSAEADDRINUSE = EADDRINUSE
_DISCONNECTED = frozenset((
ECONNRESET, ENOTCONN, ESHUTDOWN, ECONNABORTED, EPIPE, EBADF, ECONNREFUSED,
EHOSTUNREACH, ENETUNREACH, ETIMEDOUT, WSAECONNRESET))
OP_READ = 1
OP_WRITE = 2
try:
socket_map
except NameError:
socket_map = {}
def _strerror(err):
try:
return os.strerror(err)
except (ValueError, OverflowError, NameError):
if err in errorcode:
return errorcode[err]
return "Unknown error %s" % err
# ret18 ("Unknown error {}".format(err))
class ExitNow(Exception):
"""We don't use directly but may be necessary as we replace
asyncore due to some library raising or expecting it"""
pass
_reraised_exceptions = (ExitNow, KeyboardInterrupt, SystemExit)
maxDownloadRate = 0
downloadTimestamp = 0
downloadBucket = 0
receivedBytes = 0
maxUploadRate = 0
uploadTimestamp = 0
uploadBucket = 0
sentBytes = 0
def read(obj):
"""Event to read from the object, i.e. its network socket."""
if not can_receive():
return
try:
obj.handle_read_event()
except _reraised_exceptions:
raise
except BaseException:
obj.handle_error()
def write(obj):
"""Event to write to the object, i.e. its network socket."""
if not can_send():
return
try:
obj.handle_write_event()
except _reraised_exceptions:
raise
except BaseException:
obj.handle_error()
def set_rates(download, upload):
"""Set throttling rates"""
global maxDownloadRate, maxUploadRate, downloadBucket
global uploadBucket, downloadTimestamp, uploadTimestamp
maxDownloadRate = float(download) * 1024
maxUploadRate = float(upload) * 1024
downloadBucket = maxDownloadRate
uploadBucket = maxUploadRate
downloadTimestamp = time.time()
uploadTimestamp = time.time()
def can_receive():
"""Predicate indicating whether the download throttle is in effect"""
return maxDownloadRate == 0 or downloadBucket > 0
def can_send():
"""Predicate indicating whether the upload throttle is in effect"""
return maxUploadRate == 0 or uploadBucket > 0
def update_received(download=0):
"""Update the receiving throttle"""
global receivedBytes, downloadBucket, downloadTimestamp
currentTimestamp = time.time()
receivedBytes += download
if maxDownloadRate > 0:
bucketIncrease = \
maxDownloadRate * (currentTimestamp - downloadTimestamp)
downloadBucket += bucketIncrease
if downloadBucket > maxDownloadRate:
downloadBucket = int(maxDownloadRate)
downloadBucket -= download
downloadTimestamp = currentTimestamp
def update_sent(upload=0):
"""Update the sending throttle"""
global sentBytes, uploadBucket, uploadTimestamp
currentTimestamp = time.time()
sentBytes += upload
if maxUploadRate > 0:
bucketIncrease = maxUploadRate * (currentTimestamp - uploadTimestamp)
uploadBucket += bucketIncrease
if uploadBucket > maxUploadRate:
uploadBucket = int(maxUploadRate)
uploadBucket -= upload
uploadTimestamp = currentTimestamp
def _exception(obj):
"""Handle exceptions as appropriate"""
try:
obj.handle_expt_event()
except _reraised_exceptions:
raise
except BaseException:
obj.handle_error()
def readwrite(obj, flags):
"""Read and write any pending data to/from the object"""
try:
if flags & select.POLLIN and can_receive():
obj.handle_read_event()
if flags & select.POLLOUT and can_send():
obj.handle_write_event()
if flags & select.POLLPRI:
obj.handle_expt_event()
if flags & (select.POLLHUP | select.POLLERR | select.POLLNVAL):
obj.handle_close()
except socket.error as e:
if e.args[0] not in _DISCONNECTED:
obj.handle_error()
else:
obj.handle_close()
except _reraised_exceptions:
raise
except BaseException:
obj.handle_error()
def select_poller(timeout=0.0, map=None):
"""A poller which uses select(), available on most platforms."""
if map is None:
map = socket_map
if map:
rd = []
wt = []
ex = []
for fd, obj in list(map.items()):
is_r = obj.readable()
is_w = obj.writable()
if is_r:
rd.append(fd)
# accepting sockets should not be writable
if is_w and not obj.accepting:
wt.append(fd)
if is_r or is_w:
ex.append(fd)
if [] == rd == wt == ex:
time.sleep(timeout)
return
try:
rd, wt, ex = select.select(rd, wt, ex, timeout)
except KeyboardInterrupt:
return
except socket.error as err:
if err.args[0] in (EBADF, EINTR):
return
except Exception as err:
if err.args[0] in (WSAENOTSOCK, ):
return
for fd in helper_random.randomsample(rd, len(rd)):
obj = map.get(fd)
if obj is None:
continue
read(obj)
for fd in helper_random.randomsample(wt, len(wt)):
obj = map.get(fd)
if obj is None:
continue
write(obj)
for fd in ex:
obj = map.get(fd)
if obj is None:
continue
_exception(obj)
else:
current_thread().stop.wait(timeout)
def poll_poller(timeout=0.0, map=None):
"""A poller which uses poll(), available on most UNIXen."""
if map is None:
map = socket_map
if timeout is not None:
# timeout is in milliseconds
timeout = int(timeout * 1000)
try:
poll_poller.pollster
except AttributeError:
poll_poller.pollster = select.poll()
if map:
for fd, obj in list(map.items()):
flags = newflags = 0
if obj.readable():
flags |= select.POLLIN | select.POLLPRI
newflags |= OP_READ
else:
newflags &= ~ OP_READ
# accepting sockets should not be writable
if obj.writable() and not obj.accepting:
flags |= select.POLLOUT
newflags |= OP_WRITE
else:
newflags &= ~ OP_WRITE
if newflags != obj.poller_flags:
obj.poller_flags = newflags
try:
if obj.poller_registered:
poll_poller.pollster.modify(fd, flags)
else:
poll_poller.pollster.register(fd, flags)
obj.poller_registered = True
except IOError:
pass
try:
r = poll_poller.pollster.poll(timeout)
except KeyboardInterrupt:
r = []
except socket.error as err:
if err.args[0] in (EBADF, WSAENOTSOCK, EINTR):
return
for fd, flags in helper_random.randomsample(r, len(r)):
obj = map.get(fd)
if obj is None:
continue
readwrite(obj, flags)
else:
current_thread().stop.wait(timeout)
# Aliases for backward compatibility
poll = select_poller
poll2 = poll3 = poll_poller
def epoll_poller(timeout=0.0, map=None):
"""A poller which uses epoll(), supported on Linux 2.5.44 and newer."""
if map is None:
map = socket_map
try:
epoll_poller.pollster
except AttributeError:
epoll_poller.pollster = select.epoll()
if map:
for fd, obj in map.items():
flags = newflags = 0
if obj.readable():
flags |= select.POLLIN | select.POLLPRI
newflags |= OP_READ
else:
newflags &= ~ OP_READ
# accepting sockets should not be writable
if obj.writable() and not obj.accepting:
flags |= select.POLLOUT
newflags |= OP_WRITE
else:
newflags &= ~ OP_WRITE
if newflags != obj.poller_flags:
obj.poller_flags = newflags
# Only check for exceptions if object was either readable
# or writable.
flags |= select.POLLERR | select.POLLHUP | select.POLLNVAL
try:
if obj.poller_registered:
epoll_poller.pollster.modify(fd, flags)
else:
epoll_poller.pollster.register(fd, flags)
obj.poller_registered = True
except IOError:
pass
try:
r = epoll_poller.pollster.poll(timeout)
except IOError as e:
if e.errno != EINTR:
raise
r = []
except select.error as err:
if err.args[0] != EINTR:
raise
r = []
for fd, flags in helper_random.randomsample(r, len(r)):
obj = map.get(fd)
if obj is None:
continue
readwrite(obj, flags)
else:
current_thread().stop.wait(timeout)
def kqueue_poller(timeout=0.0, map=None):
"""A poller which uses kqueue(), BSD specific."""
# pylint: disable=no-member,too-many-statements
if map is None:
map = socket_map
try:
kqueue_poller.pollster
except AttributeError:
kqueue_poller.pollster = select.kqueue()
if map:
updates = []
selectables = 0
for fd, obj in map.items():
kq_filter = 0
if obj.readable():
kq_filter |= 1
selectables += 1
if obj.writable() and not obj.accepting:
kq_filter |= 2
selectables += 1
if kq_filter != obj.poller_filter:
# unlike other pollers, READ and WRITE aren't OR able but have
# to be set and checked separately
if kq_filter & 1 != obj.poller_filter & 1:
poller_flags = select.KQ_EV_ADD
if kq_filter & 1:
poller_flags |= select.KQ_EV_ENABLE
else:
poller_flags |= select.KQ_EV_DISABLE
updates.append(
select.kevent(
fd, filter=select.KQ_FILTER_READ,
flags=poller_flags))
if kq_filter & 2 != obj.poller_filter & 2:
poller_flags = select.KQ_EV_ADD
if kq_filter & 2:
poller_flags |= select.KQ_EV_ENABLE
else:
poller_flags |= select.KQ_EV_DISABLE
updates.append(
select.kevent(
fd, filter=select.KQ_FILTER_WRITE,
flags=poller_flags))
obj.poller_filter = kq_filter
if not selectables:
# unlike other pollers, kqueue poll does not wait if there are no
# filters setup
current_thread().stop.wait(timeout)
return
events = kqueue_poller.pollster.control(updates, selectables, timeout)
if len(events) > 1:
events = helper_random.randomsample(events, len(events))
for event in events:
fd = event.ident
obj = map.get(fd)
if obj is None:
continue
if event.flags & select.KQ_EV_ERROR:
_exception(obj)
continue
if event.flags & select.KQ_EV_EOF and event.data and event.fflags:
obj.handle_close()
continue
if event.filter == select.KQ_FILTER_READ:
read(obj)
if event.filter == select.KQ_FILTER_WRITE:
write(obj)
else:
current_thread().stop.wait(timeout)
def loop(timeout=30.0, _=False, map=None, count=None, poller=None):
"""Poll in a loop, until count or timeout is reached"""
if map is None:
map = socket_map
if count is None:
count = True
# code which grants backward compatibility with "use_poll"
# argument which should no longer be used in favor of
# "poller"
# if poller is None:
# if use_poll:
# poller = poll_poller
# elif hasattr(select, 'epoll'):
# poller = epoll_poller
# elif hasattr(select, 'kqueue'):
# poller = kqueue_poller
# elif hasattr(select, 'poll'):
# poller = poll_poller
# elif hasattr(select, 'select'):
# poller = select_poller
poller = select_poller
if timeout == 0:
deadline = 0
else:
deadline = time.time() + timeout
while count:
# fill buckets first
update_sent()
update_received()
subtimeout = deadline - time.time()
if subtimeout <= 0:
break
# then poll
poller(subtimeout, map)
if isinstance(count, int):
count = count - 1
class dispatcher(object):
"""Dispatcher for socket objects"""
# pylint: disable=too-many-public-methods,too-many-instance-attributes
debug = False
connected = False
accepting = False
connecting = False
closing = False
addr = None
ignore_log_types = frozenset(['warning'])
poller_registered = False
poller_flags = 0
# don't do network IO with a smaller bucket than this
minTx = 1500
def __init__(self, sock=None, map=None):
if map is None:
self._map = socket_map
else:
self._map = map
self._fileno = None
if sock:
# Set to nonblocking just to make sure for cases where we
# get a socket from a blocking source.
sock.setblocking(0)
self.set_socket(sock, map)
self.connected = True
# The constructor no longer requires that the socket
# passed be connected.
try:
self.addr = sock.getpeername()
except socket.error as err:
if err.args[0] in (ENOTCONN, EINVAL):
# To handle the case where we got an unconnected
# socket.
self.connected = False
else:
# The socket is broken in some unknown way, alert
# the user and remove it from the map (to prevent
# polling of broken sockets).
self.del_channel(map)
raise
else:
self.socket = None
def __repr__(self):
status = [self.__class__.__module__ + "." + self.__class__.__name__]
if self.accepting and self.addr:
status.append('listening')
elif self.connected:
status.append('connected')
if self.addr is not None:
try:
status.append('%s:%d' % self.addr)
except TypeError:
status.append(repr(self.addr))
return '<%s at %#x>' % (' '.join(status), id(self))
__str__ = __repr__
def add_channel(self, map=None):
"""Add a channel"""
# pylint: disable=attribute-defined-outside-init
if map is None:
map = self._map
map[self._fileno] = self
self.poller_flags = 0
self.poller_filter = 0
def del_channel(self, map=None):
"""Delete a channel"""
fd = self._fileno
if map is None:
map = self._map
if fd in map:
del map[fd]
if self._fileno:
try:
kqueue_poller.pollster.control([select.kevent(
fd, select.KQ_FILTER_READ, select.KQ_EV_DELETE)], 0)
except(AttributeError, KeyError, TypeError, IOError, OSError):
pass
try:
kqueue_poller.pollster.control([select.kevent(
fd, select.KQ_FILTER_WRITE, select.KQ_EV_DELETE)], 0)
except(AttributeError, KeyError, TypeError, IOError, OSError):
pass
try:
epoll_poller.pollster.unregister(fd)
except (AttributeError, KeyError, TypeError, IOError):
# no epoll used, or not registered
pass
try:
poll_poller.pollster.unregister(fd)
except (AttributeError, KeyError, TypeError, IOError):
# no poll used, or not registered
pass
self._fileno = None
self.poller_flags = 0
self.poller_filter = 0
self.poller_registered = False
def create_socket(
self, family=socket.AF_INET, socket_type=socket.SOCK_STREAM):
"""Create a socket"""
# pylint: disable=attribute-defined-outside-init
self.family_and_type = family, socket_type
sock = socket.socket(family, socket_type)
sock.setblocking(0)
self.set_socket(sock)
def set_socket(self, sock, map=None):
"""Set socket"""
self.socket = sock
self._fileno = sock.fileno()
self.add_channel(map)
def set_reuse_addr(self):
"""try to re-use a server port if possible"""
try:
self.socket.setsockopt(
socket.SOL_SOCKET, socket.SO_REUSEADDR, self.socket.getsockopt(
socket.SOL_SOCKET, socket.SO_REUSEADDR) | 1
)
except socket.error:
pass
# ==================================================
# predicates for select()
# these are used as filters for the lists of sockets
# to pass to select().
# ==================================================
def readable(self):
"""Predicate to indicate download throttle status"""
if maxDownloadRate > 0:
return downloadBucket > dispatcher.minTx
return True
def writable(self):
"""Predicate to indicate upload throttle status"""
if maxUploadRate > 0:
return uploadBucket > dispatcher.minTx
return True
# ==================================================
# socket object methods.
# ==================================================
def listen(self, num):
"""Listen on a port"""
self.accepting = True
if os.name == 'nt' and num > 5:
num = 5
return self.socket.listen(num)
def bind(self, addr):
"""Bind to an address"""
self.addr = addr
return self.socket.bind(addr)
def connect(self, address):
"""Connect to an address"""
self.connected = False
self.connecting = True
err = self.socket.connect_ex(address)
if err in (EINPROGRESS, EALREADY, EWOULDBLOCK, WSAEWOULDBLOCK) \
or err == EINVAL and os.name in ('nt', 'ce'):
self.addr = address
return
if err in (0, EISCONN):
self.addr = address
self.handle_connect_event()
else:
raise socket.error(err, errorcode[err])
def accept(self):
"""Accept incoming connections.
Returns either an address pair or None."""
try:
conn, addr = self.socket.accept()
except TypeError:
return None
except socket.error as why:
if why.args[0] in (
EWOULDBLOCK, WSAEWOULDBLOCK, ECONNABORTED,
EAGAIN, ENOTCONN):
return None
else:
raise
else:
return conn, addr
def send(self, data):
"""Send data"""
try:
result = self.socket.send(data)
return result
except socket.error as why:
if why.args[0] in (EAGAIN, EWOULDBLOCK, WSAEWOULDBLOCK):
return 0
elif why.args[0] in _DISCONNECTED:
self.handle_close()
return 0
else:
raise
def recv(self, buffer_size):
"""Receive data"""
try:
data = self.socket.recv(buffer_size)
if not data:
# a closed connection is indicated by signaling
# a read condition, and having recv() return 0.
self.handle_close()
return b''
return data
except socket.error as why:
# winsock sometimes raises ENOTCONN
if why.args[0] in (EAGAIN, EWOULDBLOCK, WSAEWOULDBLOCK):
return b''
if why.args[0] in _DISCONNECTED:
self.handle_close()
return b''
else:
raise
def close(self):
"""Close connection"""
self.connected = False
self.accepting = False
self.connecting = False
self.del_channel()
try:
self.socket.close()
except socket.error as why:
if why.args[0] not in (ENOTCONN, EBADF):
raise
# cheap inheritance, used to pass all other attribute
# references to the underlying socket object.
def __getattr__(self, attr):
try:
retattr = getattr(self.socket, attr)
except AttributeError:
raise AttributeError("{} instance has no attribute {}"
.format(self.__class__.__name__, attr))
else:
msg = "%(me)s.%(attr)s is deprecated; use %(me)s.socket.%(attr)s"\
" instead" % {'me': self.__class__.__name__, 'attr': attr}
warnings.warn(msg, DeprecationWarning, stacklevel=2)
return retattr
# log and log_info may be overridden to provide more sophisticated
# logging and warning methods. In general, log is for 'hit' logging
# and 'log_info' is for informational, warning and error logging.
def log(self, message):
"""Log a message to stderr"""
sys.stderr.write('log: %s\n' % str(message))
def log_info(self, message, log_type='info'):
"""Conditionally print a message"""
if log_type not in self.ignore_log_types:
print('{}: {}'.format(log_type, message))
def handle_read_event(self):
"""Handle a read event"""
if self.accepting:
# accepting sockets are never connected, they "spawn" new
# sockets that are connected
self.handle_accept()
elif not self.connected:
if self.connecting:
self.handle_connect_event()
self.handle_read()
else:
self.handle_read()
def handle_connect_event(self):
"""Handle a connection event"""
err = self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
if err != 0:
raise socket.error(err, _strerror(err))
self.handle_connect()
self.connected = True
self.connecting = False
def handle_write_event(self):
"""Handle a write event"""
if self.accepting:
# Accepting sockets shouldn't get a write event.
# We will pretend it didn't happen.
return
if not self.connected:
if self.connecting:
self.handle_connect_event()
self.handle_write()
def handle_expt_event(self):
"""Handle expected exceptions"""
# handle_expt_event() is called if there might be an error on the
# socket, or if there is OOB data
# check for the error condition first
err = self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
if err != 0:
# we can get here when select.select() says that there is an
# exceptional condition on the socket
# since there is an error, we'll go ahead and close the socket
# like we would in a subclassed handle_read() that received no
# data
self.handle_close()
elif sys.platform.startswith("win"):
# async connect failed
self.handle_close()
else:
self.handle_expt()
def handle_error(self):
"""Handle unexpected exceptions"""
_, t, v, tbinfo = compact_traceback()
# sometimes a user repr method will crash.
try:
self_repr = repr(self)
except BaseException:
self_repr = '<__repr__(self) failed for object at %0x>' % id(self)
self.log_info(
'uncaptured python exception, closing channel %s (%s:%s %s)' % (
self_repr, t, v, tbinfo),
'error')
self.handle_close()
def handle_accept(self):
"""Handle an accept event"""
pair = self.accept()
if pair is not None:
self.handle_accepted(*pair)
def handle_expt(self):
"""Log that the subclass does not implement handle_expt"""
self.log_info('unhandled incoming priority event', 'warning')
def handle_read(self):
"""Log that the subclass does not implement handle_read"""
self.log_info('unhandled read event', 'warning')
def handle_write(self):
"""Log that the subclass does not implement handle_write"""
self.log_info('unhandled write event', 'warning')
def handle_connect(self):
"""Log that the subclass does not implement handle_connect"""
self.log_info('unhandled connect event', 'warning')
def handle_accepted(self, sock, addr):
"""Log that the subclass does not implement handle_accepted"""
sock.close()
self.log_info('unhandled accepted event on %s' % (addr), 'warning')
def handle_close(self):
"""Log that the subclass does not implement handle_close"""
self.log_info('unhandled close event', 'warning')
self.close()
class dispatcher_with_send(dispatcher):
"""
adds simple buffered output capability, useful for simple clients.
[for more sophisticated usage use asynchat.async_chat]
"""
def __init__(self, sock=None, map=None):
dispatcher.__init__(self, sock, map)
self.out_buffer = b''
def initiate_send(self):
"""Initiate a send"""
num_sent = 0
num_sent = dispatcher.send(self, self.out_buffer[:512])
self.out_buffer = self.out_buffer[num_sent:]
def handle_write(self):
"""Handle a write event"""
self.initiate_send()
def writable(self):
"""Predicate to indicate if the object is writable"""
return not self.connected or len(self.out_buffer)
def send(self, data):
"""Send data"""
if self.debug:
self.log_info('sending %s' % repr(data))
self.out_buffer = self.out_buffer + data
self.initiate_send()
# ---------------------------------------------------------------------------
# used for debugging.
# ---------------------------------------------------------------------------
def compact_traceback():
"""Return a compact traceback"""
t, v, tb = sys.exc_info()
tbinfo = []
# Must have a traceback
if not tb:
raise AssertionError("traceback does not exist")
while tb:
tbinfo.append((
tb.tb_frame.f_code.co_filename,
tb.tb_frame.f_code.co_name,
str(tb.tb_lineno)
))
tb = tb.tb_next
# just to be safe
del tb
filename, function, line = tbinfo[-1]
info = ' '.join(['[%s|%s|%s]' % x for x in tbinfo])
return (filename, function, line), t, v, info
def close_all(map=None, ignore_all=False):
"""Close all connections"""
if map is None:
map = socket_map
for x in list(map.values()):
try:
x.close()
except OSError as e:
if e.args[0] == EBADF:
pass
elif not ignore_all:
raise
except _reraised_exceptions:
raise
except BaseException:
if not ignore_all:
raise
map.clear()
# Asynchronous File I/O:
#
# After a little research (reading man pages on various unixen, and
# digging through the linux kernel), I've determined that select()
# isn't meant for doing asynchronous file i/o.
# Heartening, though - reading linux/mm/filemap.c shows that linux
# supports asynchronous read-ahead. So _MOST_ of the time, the data
# will be sitting in memory for us already when we go to read it.
#
# What other OS's (besides NT) support async file i/o? [VMS?]
#
# Regardless, this is useful for pipes, and stdin/stdout...
if os.name == 'posix':
import fcntl
class file_wrapper: # pylint: disable=old-style-class
"""
Here we override just enough to make a file look
like a socket for the purposes of asyncore.
The passed fd is automatically os.dup()'d
"""
def __init__(self, fd):
self.fd = os.dup(fd)
def recv(self, *args):
"""Fake recv()"""
return os.read(self.fd, *args)
def send(self, *args):
"""Fake send()"""
return os.write(self.fd, *args)
def getsockopt(self, level, optname, buflen=None):
"""Fake getsockopt()"""
if (level == socket.SOL_SOCKET and optname == socket.SO_ERROR and
not buflen):
return 0
raise NotImplementedError(
"Only asyncore specific behaviour implemented.")
read = recv
write = send
def close(self):
"""Fake close()"""
os.close(self.fd)
def fileno(self):
"""Fake fileno()"""
return self.fd
class file_dispatcher(dispatcher):
"""A dispatcher for file_wrapper objects"""
def __init__(self, fd, map=None):
dispatcher.__init__(self, None, map)
self.connected = True
try:
fd = fd.fileno()
except AttributeError:
pass
self.set_file(fd)
# set it to non-blocking mode
flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0)
flags = flags | os.O_NONBLOCK
fcntl.fcntl(fd, fcntl.F_SETFL, flags)
def set_file(self, fd):
"""Set file"""
self.socket = file_wrapper(fd)
self._fileno = self.socket.fileno()
self.add_channel()

View File

@ -0,0 +1,165 @@
"""
BMObject and it's exceptions.
"""
import logging
import time
import protocol
import state
from addresses import calculateInventoryHash
from inventory import Inventory
from network.dandelion import Dandelion
logger = logging.getLogger('default')
class BMObjectInsufficientPOWError(Exception):
"""Exception indicating the object
doesn't have sufficient proof of work."""
errorCodes = ("Insufficient proof of work")
class BMObjectInvalidDataError(Exception):
"""Exception indicating the data being parsed
does not match the specification."""
errorCodes = ("Data invalid")
class BMObjectExpiredError(Exception):
"""Exception indicating the object's lifetime has expired."""
errorCodes = ("Object expired")
class BMObjectUnwantedStreamError(Exception):
"""Exception indicating the object is in a stream
we didn't advertise as being interested in."""
errorCodes = ("Object in unwanted stream")
class BMObjectInvalidError(Exception):
"""The object's data does not match object specification."""
errorCodes = ("Invalid object")
class BMObjectAlreadyHaveError(Exception):
"""We received a duplicate object (one we already have)"""
errorCodes = ("Already have this object")
class BMObject(object): # pylint: disable=too-many-instance-attributes
"""Bitmessage Object as a class."""
# max TTL, 28 days and 3 hours
maxTTL = 28 * 24 * 60 * 60 + 10800
# min TTL, 3 hour (in the past
minTTL = -3600
def __init__(
self,
nonce,
expiresTime,
objectType,
version,
streamNumber,
data,
payloadOffset
): # pylint: disable=too-many-arguments
self.nonce = nonce
self.expiresTime = expiresTime
self.objectType = objectType
self.version = version
self.streamNumber = streamNumber
self.inventoryHash = calculateInventoryHash(data)
# copy to avoid memory issues
self.data = bytearray(data)
self.tag = self.data[payloadOffset:payloadOffset + 32]
def checkProofOfWorkSufficient(self):
"""Perform a proof of work check for sufficiency."""
# Let us check to make sure that the proof of work is sufficient.
if not protocol.isProofOfWorkSufficient(self.data):
logger.info('Proof of work is insufficient.')
raise BMObjectInsufficientPOWError()
def checkEOLSanity(self):
"""Check if object's lifetime
isn't ridiculously far in the past or future."""
# EOL sanity check
if self.expiresTime - int(time.time()) > BMObject.maxTTL:
logger.info(
'This object\'s End of Life time is too far in the future.'
' Ignoring it. Time is %i', self.expiresTime)
# .. todo:: remove from download queue
raise BMObjectExpiredError()
if self.expiresTime - int(time.time()) < BMObject.minTTL:
logger.info(
'This object\'s End of Life time was too long ago.'
' Ignoring the object. Time is %i', self.expiresTime)
# .. todo:: remove from download queue
raise BMObjectExpiredError()
def checkStream(self):
"""Check if object's stream matches streams we are interested in"""
if self.streamNumber not in state.streamsInWhichIAmParticipating:
logger.debug(
'The streamNumber %i isn\'t one we are interested in.',
self.streamNumber)
raise BMObjectUnwantedStreamError()
def checkAlreadyHave(self):
"""
Check if we already have the object
(so that we don't duplicate it in inventory
or advertise it unnecessarily)
"""
# if it's a stem duplicate, pretend we don't have it
# pylint: disable=protected-access
if Dandelion().hasHash(self.inventoryHash):
return
if self.inventoryHash in Inventory():
raise BMObjectAlreadyHaveError()
def checkObjectByType(self):
"""Call a object type specific check
(objects can have additional checks based on their types)"""
if self.objectType == protocol.OBJECT_GETPUBKEY:
self.checkGetpubkey()
elif self.objectType == protocol.OBJECT_PUBKEY:
self.checkPubkey()
elif self.objectType == protocol.OBJECT_MSG:
self.checkMessage()
elif self.objectType == protocol.OBJECT_BROADCAST:
self.checkBroadcast()
# other objects don't require other types of tests
def checkMessage(self): # pylint: disable=no-self-use
""""Message" object type checks."""
return
def checkGetpubkey(self):
""""Getpubkey" object type checks."""
if len(self.data) < 42:
logger.info(
'getpubkey message doesn\'t contain enough data. Ignoring.')
raise BMObjectInvalidError()
def checkPubkey(self):
""""Pubkey" object type checks."""
# sanity check
if len(self.data) < 146 or len(self.data) > 440:
logger.info('pubkey object too short or too long. Ignoring.')
raise BMObjectInvalidError()
def checkBroadcast(self):
""""Broadcast" object type checks."""
if len(self.data) < 180:
logger.debug(
'The payload length of this broadcast'
' packet is unreasonably low. Someone is probably'
' trying funny business. Ignoring message.')
raise BMObjectInvalidError()
# this isn't supported anymore
if self.version < 2:
raise BMObjectInvalidError()

View File

@ -0,0 +1,702 @@
"""
Bitmessage Protocol
"""
# pylint: disable=attribute-defined-outside-init,too-few-public-methods,logging-format-interpolation,protected-access
import base64
import hashlib
import logging
import socket
import struct
import time
from binascii import hexlify
import addresses
from network import connectionpool
import knownnodes
import protocol
import state
from bmconfigparser import BMConfigParser
from inventory import Inventory
from network.advanceddispatcher import AdvancedDispatcher
from network.bmobject import (
BMObject, BMObjectAlreadyHaveError, BMObjectExpiredError,
BMObjectInsufficientPOWError, BMObjectInvalidDataError,
BMObjectInvalidError, BMObjectUnwantedStreamError
)
from network.constants import (
ADDRESS_ALIVE, MAX_MESSAGE_SIZE, MAX_OBJECT_COUNT,
MAX_OBJECT_PAYLOAD_SIZE, MAX_TIME_OFFSET
)
from network.dandelion import Dandelion
from network.proxy import ProxyError
from network.objectracker import missingObjects, ObjectTracker
from network.node import Node, Peer
from queues import objectProcessorQueue, portCheckerQueue, invQueue
from network.randomtrackingdict import RandomTrackingDict
logger = logging.getLogger('default')
class BMProtoError(ProxyError):
"""A Bitmessage Protocol Base Error"""
errorCodes = ("Protocol error")
class BMProtoInsufficientDataError(BMProtoError):
"""A Bitmessage Protocol Insufficient Data Error"""
errorCodes = ("Insufficient data")
class BMProtoExcessiveDataError(BMProtoError):
"""A Bitmessage Protocol Excessive Data Error"""
errorCodes = ("Too much data")
class BMProto(AdvancedDispatcher, ObjectTracker):
"""A parser for the Bitmessage Protocol"""
# pylint: disable=too-many-instance-attributes, too-many-public-methods
timeOffsetWrongCount = 0
def __init__(self, address=None, sock=None):
# pylint: disable=unused-argument, super-init-not-called
AdvancedDispatcher.__init__(self, sock)
self.isOutbound = False
# packet/connection from a local IP
self.local = False
self.pendingUpload = RandomTrackingDict()
# canonical identifier of network group
self.network_group = None
def bm_proto_reset(self):
"""Reset the bitmessage object parser"""
self.magic = None
self.command = None
self.payloadLength = 0
self.checksum = None
self.payload = None
self.invalid = False
self.payloadOffset = 0
self.expectBytes = protocol.Header.size
self.object = None
def state_bm_header(self):
"""Process incoming header"""
self.magic, self.command, self.payloadLength, self.checksum = \
protocol.Header.unpack(self.read_buf[:protocol.Header.size])
# its shoule be in string
self.command = self.command.rstrip('\x00'.encode('utf-8'))
# pylint: disable=global-statement
if self.magic != 0xE9BEB4D9:
self.set_state("bm_header", length=1)
self.bm_proto_reset()
logger.debug('Bad magic')
if self.socket.type == socket.SOCK_STREAM:
self.close_reason = "Bad magic"
self.set_state("close")
return False
if self.payloadLength > MAX_MESSAGE_SIZE:
self.invalid = True
self.set_state(
"bm_command",
length=protocol.Header.size, expectBytes=self.payloadLength)
return True
def state_bm_command(self):
# pylint: disable=too-many-branches, too-many-statements
"""Process incoming command"""
self.payload = self.read_buf[:self.payloadLength]
if self.checksum != hashlib.sha512(self.payload).digest()[0:4]:
logger.debug('Bad checksum, ignoring')
self.invalid = True
retval = True
if not self.fullyEstablished and self.command not in (
"error".encode(), "version".encode(), "verack".encode()):
logger.error(
'Received command {} before connection was fully'
' established, ignoring'.format(self.command))
self.invalid = True
if not self.invalid:
try:
command = self.command.decode() if self.command else self.command
retval = getattr(
self, "bm_command_" + command)()
except AttributeError:
# unimplemented command
logger.debug('unimplemented command %s', self.command)
except BMProtoInsufficientDataError:
logger.debug('packet length too short, skipping')
except BMProtoExcessiveDataError:
logger.debug('too much data, skipping')
except BMObjectInsufficientPOWError:
logger.debug('insufficient PoW, skipping')
except BMObjectInvalidDataError:
logger.debug('object invalid data, skipping')
except BMObjectExpiredError:
logger.debug('object expired, skipping')
except BMObjectUnwantedStreamError:
logger.debug('object not in wanted stream, skipping')
except BMObjectInvalidError:
logger.debug('object invalid, skipping')
except BMObjectAlreadyHaveError:
logger.debug(
'%(host)s:%(port)i already got object, skipping',
self.destination._asdict())
except struct.error:
logger.debug('decoding error, skipping')
except ValueError:
pass
elif self.socket.type == socket.SOCK_DGRAM:
# broken read, ignore
pass
else:
logger.debug('Closing due to invalid command {}'.format(self.command))
self.close_reason = ("Invalid command {}".format(self.command))
self.set_state("close")
return False
if retval:
self.set_state("bm_header", length=self.payloadLength)
self.bm_proto_reset()
# else assume the command requires a different state to follow
return True
def decode_payload_string(self, length):
"""Read and return `length` bytes from payload"""
value = self.payload[self.payloadOffset:self.payloadOffset + length]
self.payloadOffset += length
return value
def decode_payload_varint(self):
"""Decode a varint from the payload"""
value, offset = addresses.decodeVarint(
self.payload[self.payloadOffset:])
self.payloadOffset += offset
return value
def decode_payload_node(self):
"""Decode node details from the payload"""
# protocol.checkIPAddress()
services, host, port = self.decode_payload_content("Q16sH")
if host[0:12] == '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF'.encode('raw_unicode_escape'):
host = socket.inet_ntop(socket.AF_INET, host[12:16])
elif host[0:6] == '\xfd\x87\xd8\x7e\xeb\x43'.encode('raw_unicode_escape'):
# Onion, based on BMD/bitcoind
host = base64.b32encode(host[6:]).lower() + ".onion"
else:
host = socket.inet_ntop(socket.AF_INET6, host)
if host == "":
# This can happen on Windows systems which are not 64-bit
# compatible so let us drop the IPv6 address.
host = socket.inet_ntop(socket.AF_INET, host[12:16])
return Node(services, host, port)
# pylint: disable=too-many-branches, too-many-statements
def decode_payload_content(self, pattern="v"):
"""
Decode the payload depending on pattern:
L = varint indicating the length of the next array
l = varint indicating the length of the next item
v = varint (or array)
H = uint16
I = uint32
Q = uint64
i = net_addr (without time and stream number)
s = string
0-9 = length of the next item
, = end of array
"""
# pylint: disable=inconsistent-return-statements
def decode_simple(self, char="v"):
"""Decode the payload using one char pattern"""
if char == "v":
return self.decode_payload_varint()
if char == "i":
return self.decode_payload_node()
if char == "H":
self.payloadOffset += 2
return struct.unpack(">H", self.payload[
self.payloadOffset - 2:self.payloadOffset])[0]
if char == "I":
self.payloadOffset += 4
return struct.unpack(">I", self.payload[
self.payloadOffset - 4:self.payloadOffset])[0]
if char == "Q":
self.payloadOffset += 8
return struct.unpack(">Q", self.payload[
self.payloadOffset - 8:self.payloadOffset])[0]
size = None
isArray = False
# size
# iterator starting from size counting to 0
# isArray?
# subpattern
# position of parser in subpattern
# retval (array)
parserStack = [[1, 1, False, pattern, 0, []]]
while True:
i = parserStack[-1][3][parserStack[-1][4]]
if i in "0123456789" and (
size is None or parserStack[-1][3][parserStack[-1][4] - 1]
not in "lL"):
try:
size = size * 10 + int(i)
except TypeError:
size = int(i)
isArray = False
elif i in "Ll" and size is None:
size = self.decode_payload_varint()
isArray = i == "L"
elif size is not None:
if isArray:
parserStack.append([
size, size, isArray,
parserStack[-1][3][parserStack[-1][4]:], 0, []
])
parserStack[-2][4] = len(parserStack[-2][3])
else:
for j in range(parserStack[-1][4], len(parserStack[-1][3])):
if parserStack[-1][3][j] not in "lL0123456789":
break
# pylint: disable=undefined-loop-variable
parserStack.append([
size, size, isArray,
parserStack[-1][3][parserStack[-1][4]:j + 1], 0, []
])
parserStack[-2][4] += len(parserStack[-1][3]) - 1
size = None
continue
elif i == "s":
# if parserStack[-2][2]:
# parserStack[-1][5].append(self.payload[
# self.payloadOffset:self.payloadOffset + parserStack[-1][0]])
# else:
parserStack[-1][5] = self.payload[
self.payloadOffset:self.payloadOffset + parserStack[-1][0]]
self.payloadOffset += parserStack[-1][0]
parserStack[-1][1] = 0
parserStack[-1][2] = True
# del parserStack[-1]
size = None
elif i in "viHIQ":
parserStack[-1][5].append(decode_simple(
self, parserStack[-1][3][parserStack[-1][4]]))
size = None
else:
size = None
for depth in range(len(parserStack) - 1, -1, -1):
parserStack[depth][4] += 1
if parserStack[depth][4] >= len(parserStack[depth][3]):
parserStack[depth][1] -= 1
parserStack[depth][4] = 0
if depth > 0:
if parserStack[depth][2]:
parserStack[depth - 1][5].append(
parserStack[depth][5])
else:
parserStack[depth - 1][5].extend(
parserStack[depth][5])
parserStack[depth][5] = []
if parserStack[depth][1] <= 0:
if depth == 0:
# we're done, at depth 0 counter is at 0
# and pattern is done parsing
return parserStack[depth][5]
del parserStack[-1]
continue
break
break
if self.payloadOffset > self.payloadLength:
logger.debug(
'Insufficient data %i/%i',
self.payloadOffset, self.payloadLength)
raise BMProtoInsufficientDataError()
def bm_command_error(self):
"""Decode an error message and log it"""
err_values = self.decode_payload_content("vvlsls")
fatalStatus = err_values[0]
# banTime = err_values[1]
# inventoryVector = err_values[2]
errorText = err_values[3]
logger.error(
'%s:%i error: %i, %s', self.destination.host,
self.destination.port, fatalStatus, errorText)
return True
def bm_command_getdata(self):
"""
Incoming request for object(s).
If we have them and some other conditions are fulfilled,
append them to the write queue.
"""
# 32 an array bit long strings
items = self.decode_payload_content("l32s")
# skip?
now = time.time()
if now < self.skipUntil:
return True
for i in items:
self.pendingUpload[bytes(i)] = now
return True
def _command_inv(self, dandelion=False):
items = self.decode_payload_content("l32s")
if len(items) > MAX_OBJECT_COUNT:
logger.error(
'Too many items in %sinv message!', 'd' if dandelion else '')
raise BMProtoExcessiveDataError()
# ignore dinv if dandelion turned off
if dandelion and not state.dandelion:
return True
for i in map(bytes, items):
if i in Inventory() and not Dandelion().hasHash(i):
continue
if dandelion and not Dandelion().hasHash(i):
Dandelion().addHash(i, self)
self.handleReceivedInventory(i)
return True
def bm_command_inv(self):
"""Non-dandelion announce"""
return self._command_inv(False)
def bm_command_dinv(self):
"""Dandelion stem announce"""
return self._command_inv(True)
def bm_command_object(self):
"""Incoming object, process it"""
objectOffset = self.payloadOffset
nonce, expiresTime, objectType, version, streamNumber = \
self.decode_payload_content("QQIvv")
self.object = BMObject(
nonce, expiresTime, objectType, version, streamNumber,
self.payload, self.payloadOffset)
if len(self.payload) - self.payloadOffset > MAX_OBJECT_PAYLOAD_SIZE:
logger.info(
'The payload length of this object is too large (%d bytes).'
' Ignoring it.', len(self.payload) - self.payloadOffset)
raise BMProtoExcessiveDataError()
try:
self.object.checkProofOfWorkSufficient()
self.object.checkEOLSanity()
self.object.checkAlreadyHave()
except (BMObjectExpiredError, BMObjectAlreadyHaveError,
BMObjectInsufficientPOWError):
BMProto.stopDownloadingObject(self.object.inventoryHash)
raise
try:
self.object.checkStream()
except BMObjectUnwantedStreamError:
acceptmismatch = BMConfigParser().get(
"inventory", "acceptmismatch")
BMProto.stopDownloadingObject(
self.object.inventoryHash, acceptmismatch)
if not acceptmismatch:
raise
try:
self.object.checkObjectByType()
objectProcessorQueue.put((
self.object.objectType, memoryview(self.object.data)))
except BMObjectInvalidError:
BMProto.stopDownloadingObject(self.object.inventoryHash, True)
else:
try:
del missingObjects[self.object.inventoryHash]
except KeyError:
pass
if self.object.inventoryHash in Inventory() and Dandelion().hasHash(self.object.inventoryHash):
Dandelion().removeHash(self.object.inventoryHash, "cycle detection")
Inventory()[self.object.inventoryHash] = (
self.object.objectType, self.object.streamNumber,
memoryview(self.payload[objectOffset:]), self.object.expiresTime,
memoryview(self.object.tag)
)
self.handleReceivedObject(
self.object.streamNumber, self.object.inventoryHash)
invQueue.put((
self.object.streamNumber, self.object.inventoryHash,
self.destination))
return True
def _decode_addr(self):
return self.decode_payload_content("LQIQ16sH")
def bm_command_addr(self):
"""Incoming addresses, process them"""
addresses = self._decode_addr() # pylint: disable=redefined-outer-name
for i in addresses:
seenTime, stream, _, ip, port = i
decodedIP = protocol.checkIPAddress(bytes(ip))
if stream not in state.streamsInWhichIAmParticipating:
continue
if (
decodedIP
and time.time() - seenTime > 0
and seenTime > time.time() - ADDRESS_ALIVE
and port > 0
):
peer = Peer(decodedIP, port)
try:
if knownnodes.knownNodes[stream][peer]["lastseen"] > \
seenTime:
continue
except KeyError:
pass
if len(knownnodes.knownNodes[stream]) < BMConfigParser().safeGetInt("knownnodes", "maxnodes"):
with knownnodes.knownNodesLock:
try:
knownnodes.knownNodes[stream][peer]["lastseen"] = \
seenTime
except (TypeError, KeyError):
knownnodes.knownNodes[stream][peer] = {
"lastseen": seenTime,
"rating": 0,
"self": False,
}
# since we don't track peers outside of knownnodes,
# only spread if in knownnodes to prevent flood
# DISABLED TO WORKAROUND FLOOD/LEAK
# addrQueue.put((stream, peer, seenTime,
# self.destination))
return True
def bm_command_portcheck(self):
"""Incoming port check request, queue it."""
portCheckerQueue.put(Peer(self.destination, self.peerNode.port))
return True
def bm_command_ping(self):
"""Incoming ping, respond to it."""
self.append_write_buf(protocol.CreatePacket('pong'))
return True
def bm_command_pong(self): # pylint: disable=no-self-use
"""
Incoming pong.
Ignore it. PyBitmessage pings connections after about 5 minutes
of inactivity, and leaves it to the TCP stack to handle actual
timeouts. So there is no need to do anything when a pong arrives.
"""
# nothing really
return True
def bm_command_verack(self):
"""
Incoming verack.
If already sent my own verack, handshake is complete (except
potentially waiting for buffers to flush), so we can continue
to the main connection phase. If not sent verack yet,
continue processing.
"""
self.verackReceived = True
if not self.verackSent:
return True
self.set_state(
"tls_init" if self.isSSL else "connection_fully_established",
length=self.payloadLength, expectBytes=0)
return False
def bm_command_version(self):
"""
Incoming version.
Parse and log, remember important things, like streams, bitfields, etc.
"""
decoded = self.decode_payload_content("IQQiiQlslv")
(self.remoteProtocolVersion, self.services, self.timestamp,
self.sockNode, self.peerNode, self.nonce, self.userAgent
) = decoded[:7]
self.streams = decoded[7:]
self.nonce = struct.pack('>Q', self.nonce)
self.timeOffset = self.timestamp - int(time.time())
logger.debug('remoteProtocolVersion: %i', self.remoteProtocolVersion)
logger.debug('services: 0x%08X', self.services)
logger.debug('time offset: %i', self.timeOffset)
logger.debug('my external IP: %s', self.sockNode.host)
logger.debug(
'remote node incoming address: %s:%i',
self.destination.host, self.peerNode.port)
logger.debug('user agent: %s', self.userAgent)
logger.debug('streams: [%s]', ','.join(map(str, self.streams)))
if not self.peerValidityChecks():
# ABORT afterwards
return True
self.append_write_buf(protocol.CreatePacket('verack'))
self.verackSent = True
if not self.isOutbound:
self.append_write_buf(protocol.assembleVersionMessage(
self.destination.host, self.destination.port,
connectionpool.BMConnectionPool().streams, True,
nodeid=self.nodeid))
logger.debug(
'%(host)s:%(port)i sending version',
self.destination._asdict())
if ((self.services & protocol.NODE_SSL == protocol.NODE_SSL)
and protocol.haveSSL(not self.isOutbound)):
self.isSSL = True
if not self.verackReceived:
return True
self.set_state(
"tls_init" if self.isSSL else "connection_fully_established",
length=self.payloadLength, expectBytes=0)
return False
# pylint: disable=too-many-return-statements
def peerValidityChecks(self):
"""Check the validity of the peer"""
if self.remoteProtocolVersion < 3:
self.append_write_buf(protocol.assembleErrorMessage(
errorText="Your is using an old protocol. Closing connection.",
fatal=2))
logger.debug(
'Closing connection to old protocol version %s, node: %s',
self.remoteProtocolVersion, self.destination)
return False
if self.timeOffset > MAX_TIME_OFFSET:
self.append_write_buf(protocol.assembleErrorMessage(
errorText="Your time is too far in the future"
" compared to mine. Closing connection.", fatal=2))
logger.info(
"%s's time is too far in the future (%s seconds)."
" Closing connection to it.", self.destination, self.timeOffset)
BMProto.timeOffsetWrongCount += 1
return False
elif self.timeOffset < -MAX_TIME_OFFSET:
self.append_write_buf(protocol.assembleErrorMessage(
errorText="Your time is too far in the past compared to mine."
" Closing connection.", fatal=2))
logger.info(
"%s's time is too far in the past (timeOffset %s seconds)."
" Closing connection to it.", self.destination, self.timeOffset)
BMProto.timeOffsetWrongCount += 1
return False
else:
BMProto.timeOffsetWrongCount = 0
if not self.streams:
self.append_write_buf(protocol.assembleErrorMessage(
errorText="We don't have shared stream interests."
" Closing connection.", fatal=2))
logger.debug(
'Closed connection to %s because there is no overlapping'
' interest in streams.', self.destination)
return False
if self.destination in connectionpool.BMConnectionPool().inboundConnections:
try:
if not protocol.checkSocksIP(self.destination.host):
self.append_write_buf(protocol.assembleErrorMessage(
errorText="Too many connections from your IP."
" Closing connection.", fatal=2))
logger.debug(
'Closed connection to {} because we are already connected'
' to that IP.'.format(self.destination))
return False
except Exception:
pass
if not self.isOutbound:
# incoming from a peer we're connected to as outbound,
# or server full report the same error to counter deanonymisation
if (
Peer(self.destination.host, self.peerNode.port)
in connectionpool.BMConnectionPool().inboundConnections
or len(connectionpool.BMConnectionPool().inboundConnections)
+ len(connectionpool.BMConnectionPool().outboundConnections)
> BMConfigParser().safeGetInt(
'bitmessagesettings', 'maxtotalconnections')
+ BMConfigParser().safeGetInt(
'bitmessagesettings', 'maxbootstrapconnections')
):
self.append_write_buf(protocol.assembleErrorMessage(
errorText="Server full, please try again later.", fatal=2))
logger.debug(
'Closed connection to %s due to server full'
' or duplicate inbound/outbound.', self.destination)
return False
if connectionpool.BMConnectionPool().isAlreadyConnected(
self.nonce):
self.append_write_buf(protocol.assembleErrorMessage(
errorText="I'm connected to myself. Closing connection.",
fatal=2))
logger.debug(
"Closed connection to %s because I'm connected to myself.",
self.destination)
return False
return True
@staticmethod
def stopDownloadingObject(hashId, forwardAnyway=False):
"""Stop downloading an object"""
for connection in connectionpool.BMConnectionPool().connections():
try:
del connection.objectsNewToMe[hashId]
except KeyError:
pass
if not forwardAnyway:
try:
with connection.objectsNewToThemLock:
del connection.objectsNewToThem[hashId]
except KeyError:
pass
try:
del missingObjects[hashId]
except KeyError:
pass
def handle_close(self):
"""Handle close"""
self.set_state("close")
if not (self.accepting or self.connecting or self.connected):
# already disconnected
return
try:
logger.debug(
'%s:%i: closing, %s', self.destination.host,
self.destination.port, self.close_reason)
except AttributeError:
try:
logger.debug(
'%(host)s:%(port)i: closing', self.destination._asdict())
except AttributeError:
logger.debug('Disconnected socket closing')
AdvancedDispatcher.handle_close(self)
class BMStringParser(BMProto):
"""
A special case of BMProto used by objectProcessor to send ACK
"""
def __init__(self):
super(BMStringParser, self).__init__()
self.destination = Peer('127.0.0.1', 8444)
self.payload = None
ObjectTracker.__init__(self)
def send_data(self, data):
"""Send object given by the data string"""
# This class is introduced specially for ACK sending, please
# change log strings if you are going to use it for something else
self.bm_proto_reset()
self.payload = data
try:
self.bm_command_object()
except BMObjectAlreadyHaveError:
pass # maybe the same msg received on different nodes
except BMObjectExpiredError:
logger.debug(
'Sending ACK failure (expired): %s', hexlify(data))
except Exception as e:
logger.debug(
'Exception of type %s while sending ACK',
type(e), exc_info=True)

View File

@ -0,0 +1,77 @@
"""
Select which node to connect to
"""
# pylint: disable=too-many-branches, logging-format-interpolation, unidiomatic-typecheck
import logging
import random # nosec
import knownnodes
import protocol
import state
from bmconfigparser import BMConfigParser
from queues import Queue, portCheckerQueue
logger = logging.getLogger('default')
def getDiscoveredPeer():
"""Get a peer from the local peer discovery list"""
try:
peer = random.choice([key for key in state.discoveredPeers.keys()])
except (IndexError, KeyError):
raise ValueError
try:
del state.discoveredPeers[peer]
except KeyError:
pass
return peer
def chooseConnection(stream):
"""Returns an appropriate connection"""
haveOnion = BMConfigParser().safeGet(
"bitmessagesettings", "socksproxytype")[0:5] == 'SOCKS'
onionOnly = BMConfigParser().safeGetBoolean(
"bitmessagesettings", "onionservicesonly")
try:
retval = portCheckerQueue.get(False)
portCheckerQueue.task_done()
return retval
except Queue.Empty:
pass
# with a probability of 0.5, connect to a discovered peer
if random.choice((False, True)) and not haveOnion:
# discovered peers are already filtered by allowed streams
return getDiscoveredPeer()
for _ in range(50):
peer = random.choice([key for key in knownnodes.knownNodes[stream].keys()])
try:
peer_info = knownnodes.knownNodes[stream][peer]
if peer_info.get('self'):
continue
rating = peer_info["rating"]
except TypeError:
logger.warning('Error in {}'.format(peer))
rating = 0
if haveOnion:
# do not connect to raw IP addresses
# --keep all traffic within Tor overlay
if onionOnly and not peer.host.endswith('.onion'):
continue
# onion addresses have a higher priority when SOCKS
if peer.host.endswith('.onion') and rating > 0:
rating = 1
# TODO: need better check
elif not peer.host.startswith('bootstrap'):
encodedAddr = protocol.encodeHost(peer.host)
# don't connect to local IPs when using SOCKS
if not protocol.checkIPAddress(encodedAddr, False):
continue
if rating > 1:
rating = 1
try:
if 0.05 / (1.0 - rating) > random.random():
return peer
except ZeroDivisionError:
return peer
raise ValueError

View File

@ -0,0 +1,419 @@
"""
`BMConnectionPool` class definition
"""
import errno
import logging
import re
import socket
import sys
import time
import pybitmessage.network.asyncore_pollchoose as asyncore
from pybitmessage import helper_random
# import knownnodes
# import protocol
from pybitmessage import state
from pybitmessage.bmconfigparser import BMConfigParser
# from network.connectionchooser import chooseConnection
# from network.proxy import Proxy
# from network.tcp import (
# TCPServer, Socks5BMConnection, Socks4aBMConnection, TCPConnection, bootstrap)
# from network.udp import UDPSocket
from pybitmessage.singleton import Singleton
# from .node import Peer
logger = logging.getLogger('default')
@Singleton
class BMConnectionPool(object):
"""Pool of all existing connections"""
# pylint: disable=too-many-instance-attributes
trustedPeer = None
"""
If the trustedpeer option is specified in keys.dat then this will
contain a Peer which will be connected to instead of using the
addresses advertised by other peers.
The expected use case is where the user has a trusted server where
they run a Bitmessage daemon permanently. If they then run a second
instance of the client on a local machine periodically when they want
to check for messages it will sync with the network a lot faster
without compromising security.
"""
def __init__(self):
asyncore.set_rates(
BMConfigParser().safeGetInt(
"bitmessagesettings", "maxdownloadrate"),
BMConfigParser().safeGetInt(
"bitmessagesettings", "maxuploadrate")
)
self.outboundConnections = {}
self.inboundConnections = {}
self.listeningSockets = {}
self.udpSockets = {}
self.streams = []
self._lastSpawned = 0
self._spawnWait = 2
self._bootstrapped = False
trustedPeer = BMConfigParser().safeGet(
'bitmessagesettings', 'trustedpeer')
try:
if trustedPeer:
host, port = trustedPeer.split(':')
self.trustedPeer = Peer(host, int(port))
except ValueError:
sys.exit(
'Bad trustedpeer config setting! It should be set as'
' trustedpeer=<hostname>:<portnumber>'
)
def connections(self):
"""
Shortcut for combined list of connections from
`inboundConnections` and `outboundConnections` dicts
"""
inboundConnections = [inboundConnections for inboundConnections in self.inboundConnections.values()]
outboundConnections = [outboundConnections for outboundConnections in self.outboundConnections.values()]
return [connections for connections in inboundConnections + outboundConnections]
def establishedConnections(self):
"""Shortcut for list of connections having fullyEstablished == True"""
return [
x for x in self.connections() if x.fullyEstablished]
def connectToStream(self, streamNumber):
"""Connect to a bitmessage stream"""
self.streams.append(streamNumber)
state.streamsInWhichIAmParticipating.append(streamNumber)
def getConnectionByAddr(self, addr):
"""
Return an (existing) connection object based on a `Peer` object
(IP and port)
"""
try:
return self.inboundConnections[addr]
except KeyError:
pass
try:
return self.inboundConnections[addr.host]
except (KeyError, AttributeError):
pass
try:
return self.outboundConnections[addr]
except KeyError:
pass
try:
return self.udpSockets[addr.host]
except (KeyError, AttributeError):
pass
raise KeyError
def isAlreadyConnected(self, nodeid):
"""Check if we're already connected to this peer"""
# for i in (
# self.inboundConnections.values() +
# self.outboundConnections.values()
# ):
# for i in (
# [inboundConnections for inboundConnections in self.inboundConnections.values()] +
# [outboundConnections for outboundConnections in self.outboundConnections.values()]
# ):
for i in self.connections():
try:
if nodeid == i.nodeid:
return True
except AttributeError:
pass
return False
def addConnection(self, connection):
"""Add a connection object to our internal dict"""
if isinstance(connection, UDPSocket):
return
if connection.isOutbound:
self.outboundConnections[connection.destination] = connection
else:
if connection.destination.host in self.inboundConnections:
self.inboundConnections[connection.destination] = connection
else:
self.inboundConnections[connection.destination.host] = \
connection
# def removeConnection(self, connection):
# """Remove a connection from our internal dict"""
# if isinstance(connection, UDPSocket):
# del self.udpSockets[connection.listening.host]
# elif isinstance(connection, TCPServer):
# del self.listeningSockets[Peer(
# connection.destination.host, connection.destination.port)]
# elif connection.isOutbound:
# try:
# del self.outboundConnections[connection.destination]
# except KeyError:
# pass
# else:
# try:
# del self.inboundConnections[connection.destination]
# except KeyError:
# try:
# del self.inboundConnections[connection.destination.host]
# except KeyError:
# pass
# connection.handle_close()
@staticmethod
def getListeningIP():
"""What IP are we supposed to be listening on?"""
if BMConfigParser().safeGet(
"bitmessagesettings", "onionhostname").endswith(".onion"):
host = BMConfigParser().safeGet(
"bitmessagesettings", "onionbindip")
else:
host = '127.0.0.1'
if (
BMConfigParser().safeGetBoolean("bitmessagesettings", "sockslisten")
or BMConfigParser().safeGet("bitmessagesettings", "socksproxytype")
== "none"
):
# python doesn't like bind + INADDR_ANY?
# host = socket.INADDR_ANY
host = BMConfigParser().get("network", "bind")
return host
def startListening(self, bind=None):
"""Open a listening socket and start accepting connections on it"""
if bind is None:
bind = self.getListeningIP()
port = BMConfigParser().safeGetInt("bitmessagesettings", "port")
# correct port even if it changed
ls = TCPServer(host=bind, port=port)
self.listeningSockets[ls.destination] = ls
def startUDPSocket(self, bind=None):
"""
Open an UDP socket. Depending on settings, it can either only
accept incoming UDP packets, or also be able to send them.
"""
if bind is None:
host = self.getListeningIP()
udpSocket = UDPSocket(host=host, announcing=True)
else:
if bind is False:
udpSocket = UDPSocket(announcing=False)
else:
udpSocket = UDPSocket(host=bind, announcing=True)
self.udpSockets[udpSocket.listening.host] = udpSocket
def startBootstrappers(self):
"""Run the process of resolving bootstrap hostnames"""
proxy_type = BMConfigParser().safeGet(
'bitmessagesettings', 'socksproxytype')
# A plugins may be added here
hostname = None
if not proxy_type or proxy_type == 'none':
connection_base = TCPConnection
elif proxy_type == 'SOCKS5':
connection_base = Socks5BMConnection
hostname = helper_random.randomchoice([
'quzwelsuziwqgpt2.onion', None
])
elif proxy_type == 'SOCKS4a':
connection_base = Socks4aBMConnection # FIXME: I cannot test
else:
# This should never happen because socksproxytype setting
# is handled in bitmessagemain before starting the connectionpool
return
bootstrapper = bootstrap(connection_base)
if not hostname:
port = helper_random.randomchoice([8080, 8444])
hostname = ('bootstrap{}.bitmessage.org'.format(port))
else:
port = 8444
self.addConnection(bootstrapper(hostname, port))
def loop(self): # pylint: disable=too-many-branches,too-many-statements
"""Main Connectionpool's loop"""
# pylint: disable=too-many-locals
# defaults to empty loop if outbound connections are maxed
spawnConnections = False
acceptConnections = True
if BMConfigParser().safeGetBoolean(
'bitmessagesettings', 'dontconnect'):
acceptConnections = False
elif bool(BMConfigParser().safeGet(
'bitmessagesettings', 'sendoutgoingconnections')):
spawnConnections = True
socksproxytype = BMConfigParser().safeGet(
'bitmessagesettings', 'socksproxytype', '')
onionsocksproxytype = BMConfigParser().safeGet(
'bitmessagesettings', 'onionsocksproxytype', '')
if (
socksproxytype[:5] == 'SOCKS'
and not BMConfigParser().safeGetBoolean(
'bitmessagesettings', 'sockslisten')
and '.onion' not in BMConfigParser().safeGet(
'bitmessagesettings', 'onionhostname', '')
):
acceptConnections = False
# pylint: disable=too-many-nested-blocks
# if spawnConnections:
# if not knownnodes.knownNodesActual:
# self.startBootstrappers()
# knownnodes.knownNodesActual = True
# if not self._bootstrapped:
# self._bootstrapped = True
# Proxy.proxy = (
# BMConfigParser().safeGet(
# 'bitmessagesettings', 'sockshostname'),
# BMConfigParser().safeGetInt(
# 'bitmessagesettings', 'socksport')
# )
# # TODO AUTH
# # TODO reset based on GUI settings changes
# try:
# if not onionsocksproxytype.startswith("SOCKS"):
# raise ValueError
# Proxy.onion_proxy = (
# BMConfigParser().safeGet(
# 'network', 'onionsockshostname', None),
# BMConfigParser().safeGet(
# 'network', 'onionsocksport', None)
# )
# except ValueError:
# Proxy.onion_proxy = None
# established = sum(
# 1 for c in [outboundConnections for outboundConnections in self.outboundConnections.values()]
# if (c.connected and c.fullyEstablished))
# pending = len(self.outboundConnections) - established
# if established < BMConfigParser().safeGetInt(
# 'bitmessagesettings', 'maxoutboundconnections'):
# for i in range(
# state.maximumNumberOfHalfOpenConnections - pending):
# try:
# chosen = self.trustedPeer or chooseConnection(
# helper_random.randomchoice(self.streams))
# except ValueError:
# continue
# if chosen in self.outboundConnections:
# continue
# if chosen.host in self.inboundConnections:
# continue
# # don't connect to self
# if chosen in state.ownAddresses:
# continue
# # don't connect to the hosts from the same
# # network group, defense against sibyl attacks
# host_network_group = protocol.network_group(
# chosen.host)
# same_group = False
# for j in self.outboundConnections.values():
# if host_network_group == j.network_group:
# same_group = True
# if chosen.host == j.destination.host:
# knownnodes.decreaseRating(chosen)
# break
# if same_group:
# continue
# try:
# # pylint: disable=unidiomatic-typecheck
# if type(chosen.host) == bytes:
# onion = '.onion'.encode()
# else:
# onion = '.onion'
# if chosen.host.endswith(onion) and Proxy.onion_proxy:
# if onionsocksproxytype == "SOCKS5":
# self.addConnection(Socks5BMConnection(chosen))
# elif onionsocksproxytype == "SOCKS4a":
# self.addConnection(Socks4aBMConnection(chosen))
# elif socksproxytype == "SOCKS5":
# self.addConnection(Socks5BMConnection(chosen))
# elif socksproxytype == "SOCKS4a":
# self.addConnection(Socks4aBMConnection(chosen))
# else:
# self.addConnection(TCPConnection(chosen))
# except socket.error as e:
# if e.errno == errno.ENETUNREACH:
# continue
# self._lastSpawned = time.time()
# else:
# for i in self.connections():
# # FIXME: rating will be increased after next connection
# i.handle_close()
if acceptConnections:
if not self.listeningSockets:
if BMConfigParser().safeGet('network', 'bind') == '':
self.startListening()
else:
for bind in re.sub(
r'[^\w.]+', ' ',
BMConfigParser().safeGet('network', 'bind')
).split():
self.startListening(bind)
logger.info('Listening for incoming connections.')
if False:
if BMConfigParser().safeGet('network', 'bind') == '':
self.startUDPSocket()
else:
for bind in re.sub(
r'[^\w.]+', ' ',
BMConfigParser().safeGet('network', 'bind')
).split():
self.startUDPSocket(bind)
self.startUDPSocket(False)
logger.info('Starting UDP socket(s).')
else:
if self.listeningSockets:
for i in self.listeningSockets.values():
i.close_reason = "Stopping listening"
i.accepting = i.connecting = i.connected = False
logger.info('Stopped listening for incoming connections.')
if self.udpSockets:
for i in self.udpSockets.values():
i.close_reason = "Stopping UDP socket"
i.accepting = i.connecting = i.connected = False
logger.info('Stopped udp sockets.')
loopTime = float(self._spawnWait)
if self._lastSpawned < time.time() - self._spawnWait:
loopTime = 2.0
asyncore.loop(timeout=loopTime, count=1000)
reaper = []
# for i in self.connections():
# minTx = time.time() - 20
# if i.fullyEstablished:
# minTx -= 300 - 20
# if i.lastTx < minTx:
# if i.fullyEstablished:
# i.append_write_buf(protocol.CreatePacket('ping'))
# else:
# i.close_reason = "Timeout (%is)" % (
# time.time() - i.lastTx)
# i.set_state("close")
for i in (
self.connections() +
[listeningSockets for listeningSockets in self.listeningSockets.values()] +
[udpSockets for udpSockets in self.udpSockets.values()]
):
if not (i.accepting or i.connecting or i.connected):
reaper.append(i)
else:
try:
if i.state == "close":
reaper.append(i)
except AttributeError:
pass
# for i in reaper:
# self.removeConnection(i)

View File

@ -0,0 +1,17 @@
"""
Network protocol constants
"""
#: address is online if online less than this many seconds ago
ADDRESS_ALIVE = 10800
#: protocol specification says max 1000 addresses in one addr command
MAX_ADDR_COUNT = 1000
#: ~1.6 MB which is the maximum possible size of an inv message.
MAX_MESSAGE_SIZE = 1600100
#: 2**18 = 256kB is the maximum size of an object payload
MAX_OBJECT_PAYLOAD_SIZE = 2**18
#: protocol specification says max 50000 objects in one inv command
MAX_OBJECT_COUNT = 50000
#: maximum time offset
MAX_TIME_OFFSET = 3600

View File

@ -0,0 +1,204 @@
"""
Dandelion class definition, tracks stages
"""
import logging
from collections import namedtuple
from random import choice, expovariate, sample
from threading import RLock
from time import time
from network import connectionpool
import state
from queues import invQueue
from singleton import Singleton
# randomise routes after 600 seconds
REASSIGN_INTERVAL = 600
# trigger fluff due to expiration
FLUFF_TRIGGER_FIXED_DELAY = 10
FLUFF_TRIGGER_MEAN_DELAY = 30
MAX_STEMS = 2
Stem = namedtuple('Stem', ['child', 'stream', 'timeout'])
logger = logging.getLogger('default')
@Singleton
class Dandelion(object):
"""Dandelion class for tracking stem/fluff stages."""
def __init__(self):
# currently assignable child stems
self.stem = []
# currently assigned parent <-> child mappings
self.nodeMap = {}
# currently existing objects in stem mode
self.hashMap = {}
# when to rerandomise routes
self.refresh = time() + REASSIGN_INTERVAL
self.lock = RLock()
@staticmethod
def poissonTimeout(start=None, average=0):
"""Generate deadline using Poisson distribution"""
if start is None:
start = time()
if average == 0:
average = FLUFF_TRIGGER_MEAN_DELAY
return start + expovariate(1.0 / average) + FLUFF_TRIGGER_FIXED_DELAY
def addHash(self, hashId, source=None, stream=1):
"""Add inventory vector to dandelion stem"""
if not state.dandelion:
return
with self.lock:
self.hashMap[hashId] = Stem(
self.getNodeStem(source),
stream,
self.poissonTimeout())
def setHashStream(self, hashId, stream=1):
"""
Update stream for inventory vector (as inv/dinv commands don't
include streams, we only learn this after receiving the object)
"""
with self.lock:
if hashId in self.hashMap:
self.hashMap[hashId] = Stem(
self.hashMap[hashId].child,
stream,
self.poissonTimeout())
def removeHash(self, hashId, reason="no reason specified"):
"""Switch inventory vector from stem to fluff mode"""
if logger.isEnabledFor(logging.DEBUG):
logger.debug(
'%s entering fluff mode due to %s.',
''.join('%02x' % ord(i) for i in hashId), reason)
with self.lock:
try:
del self.hashMap[hashId]
except KeyError:
pass
def hasHash(self, hashId):
"""Is inventory vector in stem mode?"""
return hashId in self.hashMap
def objectChildStem(self, hashId):
"""Child (i.e. next) node for an inventory vector during stem mode"""
return self.hashMap[hashId].child
def maybeAddStem(self, connection):
"""
If we had too few outbound connections, add the current one to the
current stem list. Dandelion as designed by the authors should
always have two active stem child connections.
"""
# fewer than MAX_STEMS outbound connections at last reshuffle?
with self.lock:
if len(self.stem) < MAX_STEMS:
self.stem.append(connection)
for k in (k for k, v in iter(self.nodeMap.items()) if v is None):
self.nodeMap[k] = connection
# The Purpose of adding this condition that if self
# hashMap is has any value
# if not [hasmap for hasmap in self.hashMap.items()] ==[]:
try:
for k, v in {
k: v for k, v in iter([hasmap for hasmap in self.hashMap.items()])
if v.child is None
}.items():
self.hashMap[k] = Stem(
connection, v.stream, self.poissonTimeout())
invQueue.put((v.stream, k, v.child))
except AttributeError:
pass
def maybeRemoveStem(self, connection):
"""
Remove current connection from the stem list (called e.g. when
a connection is closed).
"""
# is the stem active?
with self.lock:
if connection in self.stem:
self.stem.remove(connection)
# active mappings to pointing to the removed node
for k in (
k for k, v in iter(self.nodeMap.items()) if v == connection
# k for k, v in self.nodeMap.iteritems()
# if v == connection
):
self.nodeMap[k] = None
for k, v in {
k: v for k, v in iter(iter([hasmap for hasmap in self.hashMap.items()]))
if v.child == connection
}.items():
self.hashMap[k] = Stem(
None, v.stream, self.poissonTimeout())
def pickStem(self, parent=None):
"""
Pick a random active stem, but not the parent one
(the one where an object came from)
"""
try:
# pick a random from available stems
stem = choice(range(len(self.stem)))
if self.stem[stem] == parent:
# one stem available and it's the parent
if len(self.stem) == 1:
return None
# else, pick the other one
return self.stem[1 - stem]
# all ok
return self.stem[stem]
except IndexError:
# no stems available
return None
def getNodeStem(self, node=None):
"""
Return child stem node for a given parent stem node
(the mapping is static for about 10 minutes, then it reshuffles)
"""
with self.lock:
try:
return self.nodeMap[node]
except KeyError:
self.nodeMap[node] = self.pickStem(node)
return self.nodeMap[node]
def expire(self):
"""Switch expired objects from stem to fluff mode"""
with self.lock:
deadline = time()
toDelete = [
[v.stream, k, v.child] for k, v in iter(self.hashMap.items())
if v.timeout < deadline
]
for row in toDelete:
self.removeHash(row[1], 'expiration')
invQueue.put(row)
return toDelete
def reRandomiseStems(self):
"""Re-shuffle stem mapping (parent <-> child pairs)"""
with self.lock:
try:
# random two connections
self.stem = sample(
list(connectionpool.BMConnectionPool(
).outboundConnections.values()), MAX_STEMS)
# not enough stems available
except ValueError:
self.stem = connectionpool.BMConnectionPool(
).outboundConnections.values()
self.nodeMap = {}
# hashMap stays to cater for pending stems
self.refresh = time() + REASSIGN_INTERVAL

View File

@ -0,0 +1,84 @@
"""
`DownloadThread` class definition
"""
import time
import addresses
import helper_random
import protocol
from network.dandelion import Dandelion
from inventory import Inventory
from network.connectionpool import BMConnectionPool
from network.objectracker import missingObjects
from network.threads import StoppableThread
class DownloadThread(StoppableThread):
"""Thread-based class for downloading from connections"""
minPending = 200
maxRequestChunk = 1000
requestTimeout = 60
cleanInterval = 60
requestExpires = 3600
def __init__(self):
super(DownloadThread, self).__init__(name="Downloader")
self.lastCleaned = time.time()
def cleanPending(self):
"""Expire pending downloads eventually"""
deadline = time.time() - self.requestExpires
try:
toDelete = [k for k, v in iter(missingObjects.items()) if v < deadline]
# toDelete = [
# k for k, v in missingObjects.iteritems()
# if v < deadline]
except RuntimeError:
pass
else:
for i in toDelete:
del missingObjects[i]
self.lastCleaned = time.time()
def run(self): # pylint: disable=protected-access
while not self._stopped:
requested = 0
connections = BMConnectionPool().establishedConnections()
helper_random.randomshuffle(connections)
requestChunk = max(int(
min(self.maxRequestChunk, len(missingObjects))
/ len(connections)), 1) if connections else 1
for i in connections:
now = time.time()
# avoid unnecessary delay
if i.skipUntil >= now:
continue
try:
request = i.objectsNewToMe.randomKeys(requestChunk)
except KeyError:
continue
payload = bytearray()
chunkCount = 0
for chunk in request:
if chunk in Inventory() and not Dandelion().hasHash(chunk):
try:
del i.objectsNewToMe[chunk]
except KeyError:
pass
continue
payload.extend(chunk)
chunkCount += 1
missingObjects[chunk] = now
if not chunkCount:
continue
payload[0:0] = addresses.encodeVarint(chunkCount)
i.append_write_buf(protocol.CreatePacket('getdata', payload))
self.logger.debug(
'%s:%i Requesting %i objects',
i.destination.host, i.destination.port, chunkCount)
requested += chunkCount
if time.time() >= self.lastCleaned + self.cleanInterval:
self.cleanPending()
if not requested:
self.stop.wait(1)

View File

@ -0,0 +1,90 @@
# pylint: disable=redefined-outer-name, too-many-ancestors, missing-docstring
import socket
from advanceddispatcher import AdvancedDispatcher
import asyncore_pollchoose as asyncore
from network.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="/"):
AdvancedDispatcher.__init__(self)
self.path = path
self.destination = (host, 80)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.connect(self.destination)
print("connecting in background to %s:%i" % (self.destination[0], self.destination[1]))
def 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
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
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)

View File

@ -0,0 +1,55 @@
"""
src/network/http_old.py
"""
import asyncore
import socket
import time
requestCount = 0
parallel = 50
duration = 60
class HTTPClient(asyncore.dispatcher):
"""An asyncore dispatcher"""
port = 12345
def __init__(self, host, path, connect=True):
if not hasattr(self, '_map'):
asyncore.dispatcher.__init__(self)
if connect:
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.connect((host, HTTPClient.port))
self.buffer = 'GET %s HTTP/1.0\r\n\r\n' % path
def handle_close(self):
# pylint: disable=global-statement
global requestCount
requestCount += 1
self.close()
def handle_read(self):
# print self.recv(8192)
self.recv(8192)
def writable(self):
return len(self.buffer) > 0
def handle_write(self):
sent = self.send(self.buffer)
self.buffer = self.buffer[sent:]
if __name__ == "__main__":
# initial fill
for i in range(parallel):
HTTPClient('127.0.0.1', '/')
start = time.time()
while time.time() - start < duration:
if len(asyncore.socket_map) < parallel:
for i in range(parallel - len(asyncore.socket_map)):
HTTPClient('127.0.0.1', '/')
print("Active connections: %i" % (len(asyncore.socket_map)))
asyncore.loop(count=len(asyncore.socket_map) / 2)
if requestCount % 100 == 0:
print("Processed %i total messages" % (requestCount))

View File

@ -0,0 +1,155 @@
"""
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
<html>
<head>
<title>An Example Page</title>
</head>
<body>
Hello World, this is a very simple HTML document.
</body>
</html>"""
def __init__(self, sock):
if not hasattr(self, '_map'):
asyncore.dispatcher.__init__(self, sock)
self.inbuf = ""
self.ready = True
self.busy = False
self.respos = 0
def handle_close(self):
self.close()
def readable(self):
return self.ready
def writable(self):
return self.busy
def handle_read(self):
self.inbuf += self.recv(8192)
if self.inbuf[-4:] == "\r\n\r\n":
self.busy = True
self.ready = False
self.inbuf = ""
elif self.inbuf == "":
pass
def handle_write(self):
if self.busy and self.respos < len(HTTPRequestHandler.response):
written = 0
written = self.send(HTTPRequestHandler.response[self.respos:65536])
self.respos += written
elif self.busy:
self.busy = False
self.ready = True
self.close()
class HTTPSRequestHandler(HTTPRequestHandler, TLSHandshake):
"""Handling HTTPS request"""
def __init__(self, sock):
if not hasattr(self, '_map'):
asyncore.dispatcher.__init__(self, sock) # pylint: disable=non-parent-init-called
# self.tlsDone = False
TLSHandshake.__init__(
self,
sock=sock,
certfile='/home/shurdeek/src/PyBitmessage/src/sslkeys/cert.pem',
keyfile='/home/shurdeek/src/PyBitmessage/src/sslkeys/key.pem',
server_side=True)
HTTPRequestHandler.__init__(self, sock)
def handle_connect(self):
TLSHandshake.handle_connect(self)
def handle_close(self):
if self.tlsDone:
HTTPRequestHandler.close(self)
else:
TLSHandshake.close(self)
def readable(self):
if self.tlsDone:
return HTTPRequestHandler.readable(self)
return TLSHandshake.readable(self)
def handle_read(self):
if self.tlsDone:
HTTPRequestHandler.handle_read(self)
else:
TLSHandshake.handle_read(self)
def writable(self):
if self.tlsDone:
return HTTPRequestHandler.writable(self)
return TLSHandshake.writable(self)
def handle_write(self):
if self.tlsDone:
HTTPRequestHandler.handle_write(self)
else:
TLSHandshake.handle_write(self)
class HTTPServer(asyncore.dispatcher):
"""Handling HTTP Server"""
port = 12345
def __init__(self):
if not hasattr(self, '_map'):
asyncore.dispatcher.__init__(self)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.set_reuse_addr()
self.bind(('127.0.0.1', HTTPServer.port))
self.connections = 0
self.listen(5)
def handle_accept(self):
pair = self.accept()
if pair is not None:
sock, _ = pair
self.connections += 1
HTTPRequestHandler(sock)
class HTTPSServer(HTTPServer):
"""Handling HTTPS Server"""
port = 12345
def __init__(self):
if not hasattr(self, '_map'):
HTTPServer.__init__(self)
def handle_accept(self):
pair = self.accept()
if pair is not None:
sock, _ = pair
self.connections += 1
HTTPSRequestHandler(sock)
if __name__ == "__main__":
client = HTTPSServer()
asyncore.loop()

View File

@ -0,0 +1,72 @@
# pylint: disable=missing-docstring
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):
# pylint: disable=non-parent-init-called
if not hasattr(self, '_map'):
asyncore.dispatcher.__init__(self)
self.tlsDone = False
"""
TLSHandshake.__init__(
self,
address=(host, 443),
certfile='/home/shurdeek/src/PyBitmessage/sslsrc/keys/cert.pem',
keyfile='/home/shurdeek/src/PyBitmessage/src/sslkeys/key.pem',
server_side=False,
ciphers='AECDH-AES256-SHA')
"""
HTTPClient.__init__(self, host, path, connect=False)
TLSHandshake.__init__(self, address=(host, 443), server_side=False)
def handle_connect(self):
TLSHandshake.handle_connect(self)
def handle_close(self):
if self.tlsDone:
HTTPClient.close(self)
else:
TLSHandshake.close(self)
def readable(self):
if self.tlsDone:
return HTTPClient.readable(self)
else:
return TLSHandshake.readable(self)
def handle_read(self):
if self.tlsDone:
HTTPClient.handle_read(self)
else:
TLSHandshake.handle_read(self)
def writable(self):
if self.tlsDone:
return HTTPClient.writable(self)
else:
return TLSHandshake.writable(self)
def handle_write(self):
if self.tlsDone:
HTTPClient.handle_write(self)
else:
TLSHandshake.handle_write(self)
if __name__ == "__main__":
client = HTTPSClient('anarchy.economicsofbitcoin.com', '/')
asyncore.loop()

View File

@ -0,0 +1,110 @@
"""
Thread to send inv annoucements
"""
import queue as Queue
import random
from time import time
import addresses
import protocol
import state
from network.connectionpool import BMConnectionPool
from network.dandelion import Dandelion
from queues import invQueue
from network.threads import StoppableThread
def handleExpiredDandelion(expired):
"""For expired dandelion objects, mark all remotes as not having
the object"""
if not expired:
return
for i in BMConnectionPool().connections():
if not i.fullyEstablished:
continue
for x in expired:
streamNumber, hashid, _ = x
try:
del i.objectsNewToMe[hashid]
except KeyError:
if streamNumber in i.streams:
with i.objectsNewToThemLock:
i.objectsNewToThem[hashid] = time()
class InvThread(StoppableThread):
"""Main thread that sends inv annoucements"""
name = "InvBroadcaster"
@staticmethod
def handleLocallyGenerated(stream, hashId):
"""Locally generated inventory items require special handling"""
Dandelion().addHash(hashId, stream=stream)
for connection in BMConnectionPool().connections():
if state.dandelion and connection != \
Dandelion().objectChildStem(hashId):
continue
connection.objectsNewToThem[hashId] = time()
def run(self): # pylint: disable=too-many-branches
while not state.shutdown: # pylint: disable=too-many-nested-blocks
chunk = []
while True:
# Dandelion fluff trigger by expiration
handleExpiredDandelion(Dandelion().expire())
try:
data = invQueue.get(False)
chunk.append((data[0], data[1]))
# locally generated
if len(data) == 2 or data[2] is None:
self.handleLocallyGenerated(data[0], data[1])
except Queue.Empty:
break
if chunk:
for connection in BMConnectionPool().connections():
fluffs = []
stems = []
for inv in chunk:
if inv[0] not in connection.streams:
continue
try:
with connection.objectsNewToThemLock:
del connection.objectsNewToThem[inv[1]]
except KeyError:
continue
try:
if connection == Dandelion().objectChildStem(inv[1]):
# Fluff trigger by RNG
# auto-ignore if config set to 0, i.e. dandelion is off
if random.randint(1, 100) >= state.dandelion:
fluffs.append(inv[1])
# send a dinv only if the stem node supports dandelion
elif connection.services & protocol.NODE_DANDELION > 0:
stems.append(inv[1])
else:
fluffs.append(inv[1])
except KeyError:
fluffs.append(inv[1])
if fluffs:
random.shuffle(fluffs)
connection.append_write_buf(protocol.CreatePacket(
'inv',
addresses.encodeVarint(
len(fluffs)) + ('').encode().join([x for x in fluffs]))) # compare result with python2
if stems:
random.shuffle(stems)
connection.append_write_buf(protocol.CreatePacket(
'dinv',
addresses.encodeVarint(
len(stems)) + ('').encode().join([x for x in stems]))) # compare result with python2
invQueue.iterate()
for _ in range(len(chunk)):
invQueue.task_done()
if Dandelion().refresh < time():
Dandelion().reRandomiseStems()
self.stop.wait(1)

View File

@ -0,0 +1,42 @@
"""
A thread to handle network concerns
"""
import pybitmessage.network.asyncore_pollchoose as asyncore
from pybitmessage import state
from pybitmessage.network.connectionpool import BMConnectionPool
from pybitmessage.queues import excQueue
from pybitmessage.network.threads import StoppableThread
class BMNetworkThread(StoppableThread):
"""Main network thread"""
name = "Asyncore"
def run(self):
try:
while not self._stopped and state.shutdown == 0:
BMConnectionPool().loop()
except Exception as e:
excQueue.put((self.name, e))
raise
def stopThread(self):
super(BMNetworkThread, self).stopThread()
for i in [listeningSockets for listeningSockets in BMConnectionPool().listeningSockets.values()]:
try:
i.close()
except:
pass
for i in [outboundConnections for outboundConnections in BMConnectionPool().outboundConnections.values()]:
try:
i.close()
except:
pass
for i in [inboundConnections for inboundConnections in BMConnectionPool().inboundConnections.values()]:
try:
i.close()
except:
pass
# just in case
asyncore.close_all()

View File

@ -0,0 +1,7 @@
"""
Named tuples representing the network peers
"""
import collections
Peer = collections.namedtuple('Peer', ['host', 'port'])
Node = collections.namedtuple('Node', ['services', 'host', 'port'])

View File

@ -0,0 +1,137 @@
"""
Module for tracking objects
"""
import time
from threading import RLock
import network.connectionpool
from network.dandelion import Dandelion
from network.randomtrackingdict import RandomTrackingDict
haveBloom = False
try:
# pybloomfiltermmap
from pybloomfilter import BloomFilter
haveBloom = True
except ImportError:
try:
# pybloom
from pybloom import BloomFilter
haveBloom = True
except ImportError:
pass
# it isn't actually implemented yet so no point in turning it on
haveBloom = False
# tracking pending downloads globally, for stats
missingObjects = {}
class ObjectTracker(object):
"""Object tracker mixin"""
invCleanPeriod = 300
invInitialCapacity = 50000
invErrorRate = 0.03
trackingExpires = 3600
initialTimeOffset = 60
def __init__(self):
self.objectsNewToMe = RandomTrackingDict()
self.objectsNewToThem = {}
self.objectsNewToThemLock = RLock()
self.initInvBloom()
self.initAddrBloom()
self.lastCleaned = time.time()
def initInvBloom(self):
"""Init bloom filter for tracking. WIP."""
if haveBloom:
# lock?
self.invBloom = BloomFilter(
capacity=ObjectTracker.invInitialCapacity,
error_rate=ObjectTracker.invErrorRate)
def initAddrBloom(self):
"""Init bloom filter for tracking addrs, WIP.
This either needs to be moved to addrthread.py or removed."""
if haveBloom:
# lock?
self.addrBloom = BloomFilter(
capacity=ObjectTracker.invInitialCapacity,
error_rate=ObjectTracker.invErrorRate)
def clean(self):
"""Clean up tracking to prevent memory bloat"""
if self.lastCleaned < time.time() - ObjectTracker.invCleanPeriod:
if haveBloom:
if missingObjects == 0:
self.initInvBloom()
self.initAddrBloom()
else:
# release memory
deadline = time.time() - ObjectTracker.trackingExpires
with self.objectsNewToThemLock:
self.objectsNewToThem = {k: v for k, v in iter(self.objectsNewToThem.items()) if v >= deadline}
# self.objectsNewToThem = {
# k: v
# for k, v in self.objectsNewToThem.iteritems()
# if v >= deadline}
self.lastCleaned = time.time()
def hasObj(self, hashid):
"""Do we already have object?"""
if haveBloom:
return hashid in self.invBloom
return hashid in self.objectsNewToMe
def handleReceivedInventory(self, hashId):
"""Handling received inventory"""
if haveBloom:
self.invBloom.add(hashId)
try:
with self.objectsNewToThemLock:
del self.objectsNewToThem[hashId]
except KeyError:
pass
if hashId not in missingObjects:
missingObjects[hashId] = time.time()
self.objectsNewToMe[hashId] = True
def handleReceivedObject(self, streamNumber, hashid):
"""Handling received object"""
for i in network.connectionpool.BMConnectionPool().connections():
if not i.fullyEstablished:
continue
try:
del i.objectsNewToMe[hashid]
except KeyError:
if streamNumber in i.streams and (
not Dandelion().hasHash(hashid) or
Dandelion().objectChildStem(hashid) == i):
with i.objectsNewToThemLock:
i.objectsNewToThem[hashid] = time.time()
# update stream number,
# which we didn't have when we just received the dinv
# also resets expiration of the stem mode
Dandelion().setHashStream(hashid, streamNumber)
if i == self:
try:
with i.objectsNewToThemLock:
del i.objectsNewToThem[hashid]
except KeyError:
pass
self.objectsNewToMe.setLastObject()
def hasAddr(self, addr):
"""WIP, should be moved to addrthread.py or removed"""
if haveBloom:
return addr in self.invBloom
return None
def addAddr(self, hashid):
"""WIP, should be moved to addrthread.py or removed"""
if haveBloom:
self.addrBloom.add(hashid)

View File

@ -0,0 +1,151 @@
"""
Set proxy if avaiable otherwise exception
"""
# pylint: disable=protected-access
import logging
import socket
import time
import network.asyncore_pollchoose as asyncore
from network.advanceddispatcher import AdvancedDispatcher
from bmconfigparser import BMConfigParser
from .node import Peer
logger = logging.getLogger('default')
class ProxyError(Exception):
"""Base proxy exception class"""
errorCodes = ("Unknown error",)
def __init__(self, code=-1):
self.code = code
try:
self.message = self.errorCodes[code]
except IndexError:
self.message = self.errorCodes[-1]
super(ProxyError, self).__init__(self.message)
class GeneralProxyError(ProxyError):
"""General proxy error class (not specfic to an implementation)"""
errorCodes = (
"Success",
"Invalid data",
"Not connected",
"Not available",
"Bad proxy type",
"Bad input",
"Timed out",
"Network unreachable",
"Connection refused",
"Host unreachable"
)
class Proxy(AdvancedDispatcher):
"""Base proxy class"""
# these are global, and if you change config during runtime,
# all active/new instances should change too
_proxy = ("127.0.0.1", 9050)
_auth = None
_onion_proxy = None
_onion_auth = None
_remote_dns = True
@property
def proxy(self):
"""Return proxy IP and port"""
return self.__class__._proxy
@proxy.setter
def proxy(self, address):
"""Set proxy IP and port"""
if (not isinstance(address, tuple) or len(address) < 2 or
not isinstance(address[0], str) or
not isinstance(address[1], int)):
raise ValueError
self.__class__._proxy = address
@property
def auth(self):
"""Return proxy authentication settings"""
return self.__class__._auth
@auth.setter
def auth(self, authTuple):
"""Set proxy authentication (username and password)"""
self.__class__._auth = authTuple
@property
def onion_proxy(self):
"""
Return separate proxy IP and port for use only with onion
addresses. Untested.
"""
return self.__class__._onion_proxy
@onion_proxy.setter
def onion_proxy(self, address):
"""Set onion proxy address"""
if address is not None and (
not isinstance(address, tuple) or len(address) < 2
or not isinstance(address[0], str)
or not isinstance(address[1], int)
):
raise ValueError
self.__class__._onion_proxy = address
@property
def onion_auth(self):
"""Return proxy authentication settings for onion hosts only"""
return self.__class__._onion_auth
@onion_auth.setter
def onion_auth(self, authTuple):
"""Set proxy authentication for onion hosts only. Untested."""
self.__class__._onion_auth = authTuple
def __init__(self, address):
if not isinstance(address, Peer):
raise ValueError
AdvancedDispatcher.__init__(self)
self.destination = address
self.isOutbound = True
self.fullyEstablished = False
self.connectedAt = 0
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
if BMConfigParser().safeGetBoolean(
"bitmessagesettings", "socksauthentication"):
self.auth = (
BMConfigParser().safeGet(
"bitmessagesettings", "socksusername"),
BMConfigParser().safeGet(
"bitmessagesettings", "sockspassword"))
else:
self.auth = None
self.connect(
self.onion_proxy
if address.host.endswith(".onion") and self.onion_proxy else
self.proxy
)
def handle_connect(self):
"""Handle connection event (to the proxy)"""
self.set_state("init")
try:
AdvancedDispatcher.handle_connect(self)
except socket.error as e:
if e.errno in asyncore._DISCONNECTED:
logger.debug(
"%s:%i: Connection failed: %s",
self.destination.host, self.destination.port, e)
return
self.state_init()
def state_proxy_handshake_done(self):
"""Handshake is complete at this point"""
# pylint: disable=attribute-defined-outside-init
self.connectedAt = time.time()
return False

View File

@ -0,0 +1,168 @@
"""
Track randomize ordered dict
"""
import random
from threading import RLock
from time import time
import helper_random
class RandomTrackingDict(object):
"""
Dict with randomised order and tracking.
Keeps a track of how many items have been requested from the dict,
and timeouts. Resets after all objects have been retrieved and timed out.
The main purpose of this isn't as much putting related code together
as performance optimisation and anonymisation of downloading of objects
from other peers. If done using a standard dict or array, it takes
too much CPU (and looks convoluted). Randomisation helps with anonymity.
"""
# pylint: disable=too-many-instance-attributes
maxPending = 10
pendingTimeout = 60
def __init__(self):
self.dictionary = {}
self.indexDict = []
self.len = 0
self.pendingLen = 0
self.lastPoll = 0
self.lastObject = 0
self.lock = RLock()
def __len__(self):
return self.len
def __contains__(self, key):
return key in self.dictionary
def __getitem__(self, key):
return self.dictionary[key][1]
def _swap(self, i1, i2):
with self.lock:
key1 = self.indexDict[i1]
key2 = self.indexDict[i2]
self.indexDict[i1] = key2
self.indexDict[i2] = key1
self.dictionary[key1][0] = i2
self.dictionary[key2][0] = i1
# for quick reassignment
return i2
def __setitem__(self, key, value):
with self.lock:
if key in self.dictionary:
self.dictionary[key][1] = value
else:
self.indexDict.append(key)
self.dictionary[key] = [self.len, value]
self._swap(self.len, self.len - self.pendingLen)
self.len += 1
def __delitem__(self, key):
if key not in self.dictionary:
raise KeyError
with self.lock:
index = self.dictionary[key][0]
# not pending
if index < self.len - self.pendingLen:
# left of pending part
index = self._swap(index, self.len - self.pendingLen - 1)
# pending
else:
self.pendingLen -= 1
# end
self._swap(index, self.len - 1)
# if the following del is batched, performance of this single
# operation can improve 4x, but it's already very fast so we'll
# ignore it for the time being
del self.indexDict[-1]
del self.dictionary[key]
self.len -= 1
def setMaxPending(self, maxPending):
"""
Sets maximum number of objects that can be retrieved from the class
simultaneously as long as there is no timeout
"""
self.maxPending = maxPending
def setPendingTimeout(self, pendingTimeout):
"""Sets how long to wait for a timeout if max pending is reached
(or all objects have been retrieved)"""
self.pendingTimeout = pendingTimeout
def setLastObject(self):
"""Update timestamp for tracking of received objects"""
self.lastObject = time()
def randomKeys(self, count=1):
"""Retrieve count random keys from the dict
that haven't already been retrieved"""
if self.len == 0 or ((self.pendingLen >= self.maxPending or
self.pendingLen == self.len) and self.lastPoll +
self.pendingTimeout > time()):
raise KeyError
# pylint: disable=redefined-outer-name
with self.lock:
# reset if we've requested all
# and if last object received too long time ago
if self.pendingLen == self.len and self.lastObject + \
self.pendingTimeout < time():
self.pendingLen = 0
self.setLastObject()
available = self.len - self.pendingLen
if count > available:
count = available
randomIndex = helper_random.randomsample(
range(self.len - self.pendingLen), count)
retval = [self.indexDict[i] for i in randomIndex]
for i in sorted(randomIndex, reverse=True):
# swap with one below lowest pending
self._swap(i, self.len - self.pendingLen - 1)
self.pendingLen += 1
self.lastPoll = time()
return retval
if __name__ == '__main__':
# pylint: disable=redefined-outer-name
def randString():
"""helper function for tests, generates a random string"""
retval = b''
for _ in range(32):
retval += chr(random.randint(0, 255))
return retval
a = []
k = RandomTrackingDict()
d = {}
print("populating random tracking dict")
a.append(time())
for i in range(50000):
k[randString()] = True
a.append(time())
print("done")
while k:
retval = k.randomKeys(1000)
if not retval:
print("error getting random keys")
try:
k.randomKeys(100)
print("bad")
except KeyError:
pass
for i in retval:
del k[i]
a.append(time())
for x in range(len(a) - 1):
print("{}i: {}.3f".format(x, a[x + 1] - a[x]))

View File

@ -0,0 +1,56 @@
"""
Process data incoming from network
"""
import errno
import queue as Queue
import socket
from pybitmessage import state
from pybitmessage.network.advanceddispatcher import UnknownStateError
from pybitmessage.network.connectionpool import BMConnectionPool
from pybitmessage.queues import receiveDataQueue
from pybitmessage.network.threads import StoppableThread
class ReceiveQueueThread(StoppableThread):
"""This thread processes data received from the network
(which is done by the asyncore thread)"""
def __init__(self, num=0):
super(ReceiveQueueThread, self).__init__(name="ReceiveQueue_%i" % num)
def run(self):
while not self._stopped and state.shutdown == 0:
try:
dest = receiveDataQueue.get(block=True, timeout=1)
except Queue.Empty:
continue
if self._stopped or state.shutdown:
break
# cycle as long as there is data
# methods should return False if there isn't enough data,
# or the connection is to be aborted
# state_* methods should return False if there isn't
# enough data, or the connection is to be aborted
try:
connection = BMConnectionPool().getConnectionByAddr(dest)
# connection object not found
except KeyError:
receiveDataQueue.task_done()
continue
try:
connection.process()
# state isn't implemented
except UnknownStateError:
pass
except socket.error as err:
if err.errno == errno.EBADF:
connection.set_state("close", 0)
else:
self.logger.error('Socket error: %s', err)
except:
self.logger.error('Error processing', exc_info=True)
receiveDataQueue.task_done()

View File

@ -0,0 +1,143 @@
"""
SOCKS4a proxy module
"""
# pylint: disable=attribute-defined-outside-init
import socket
import struct
from network.proxy import Proxy, ProxyError, GeneralProxyError
class Socks4aError(ProxyError):
"""SOCKS4a error base class"""
errorCodes = (
"Request granted",
"Request rejected or failed",
"Request rejected because SOCKS server cannot connect to identd"
" on the client",
"Request rejected because the client program and identd report"
" different user-ids",
"Unknown error"
)
class Socks4a(Proxy):
"""SOCKS4a proxy class"""
def __init__(self, address=None):
Proxy.__init__(self, address)
self.ipaddr = None
self.destport = address[1]
def state_init(self):
"""Protocol initialisation (before connection is established)"""
self.set_state("auth_done", 0)
return True
def state_pre_connect(self):
"""Handle feedback from SOCKS4a while it is connecting on our behalf"""
# Get the response
if self.read_buf[0:1] != chr(0x00).encode():
# bad data
self.close()
raise GeneralProxyError(1)
elif self.read_buf[1:2] != chr(0x5A).encode():
# Connection failed
self.close()
if ord(self.read_buf[1:2]) in (91, 92, 93):
# socks 4 error
raise Socks4aError(ord(self.read_buf[1:2]) - 90)
else:
raise Socks4aError(4)
# Get the bound address/port
self.boundport = struct.unpack(">H", self.read_buf[2:4])[0]
self.boundaddr = self.read_buf[4:]
self.__proxysockname = (self.boundaddr, self.boundport)
if self.ipaddr:
self.__proxypeername = (
socket.inet_ntoa(self.ipaddr), self.destination[1])
else:
self.__proxypeername = (self.destination[0], self.destport)
self.set_state("proxy_handshake_done", length=8)
return True
def proxy_sock_name(self):
"""
Handle return value when using SOCKS4a for DNS resolving
instead of connecting.
"""
return socket.inet_ntoa(self.__proxysockname[0])
class Socks4aConnection(Socks4a):
"""Child SOCKS4a class used for making outbound connections."""
def __init__(self, address):
Socks4a.__init__(self, address=address)
def state_auth_done(self):
"""Request connection to be made"""
# Now we can request the actual connection
rmtrslv = False
self.append_write_buf(
struct.pack('>BBH', 0x04, 0x01, self.destination[1]))
# If the given destination address is an IP address, we'll
# use the IPv4 address request even if remote resolving was specified.
try:
self.ipaddr = socket.inet_aton(self.destination[0])
self.append_write_buf(self.ipaddr)
except socket.error:
# Well it's not an IP number, so it's probably a DNS name.
if self._remote_dns:
# Resolve remotely
rmtrslv = True
self.ipaddr = None
self.append_write_buf(
struct.pack("BBBB", 0x00, 0x00, 0x00, 0x01))
else:
# Resolve locally
self.ipaddr = socket.inet_aton(
socket.gethostbyname(self.destination[0]))
self.append_write_buf(self.ipaddr)
if self._auth:
self.append_write_buf(self._auth[0])
self.append_write_buf(chr(0x00).encode())
if rmtrslv:
self.append_write_buf(self.destination[0] + chr(0x00).encode())
self.set_state("pre_connect", length=0, expectBytes=8)
return True
def state_pre_connect(self):
"""Tell SOCKS4a to initiate a connection"""
try:
return Socks4a.state_pre_connect(self)
except Socks4aError as e:
self.close_reason = e.message
self.set_state("close")
class Socks4aResolver(Socks4a):
"""DNS resolver class using SOCKS4a"""
def __init__(self, host):
self.host = host
self.port = 8444
Socks4a.__init__(self, address=(self.host, self.port))
def state_auth_done(self):
"""Request connection to be made"""
# Now we can request the actual connection
self.append_write_buf(
struct.pack('>BBH', 0x04, 0xF0, self.destination[1]))
self.append_write_buf(struct.pack("BBBB", 0x00, 0x00, 0x00, 0x01))
if self._auth:
self.append_write_buf(self._auth[0])
self.append_write_buf(chr(0x00).encode())
self.append_write_buf(self.host + chr(0x00).encode())
self.set_state("pre_connect", length=0, expectBytes=8)
return True
def resolved(self):
"""
Resolving is done, process the return value. To use this within
PyBitmessage, a callback needs to be implemented which hasn't
been done yet.
"""
print("Resolved {} as {}".format(self.host, self.proxy_sock_name()))

View File

@ -0,0 +1,222 @@
"""
SOCKS5 proxy module
"""
# pylint: disable=attribute-defined-outside-init
import socket
import struct
from network.proxy import GeneralProxyError, Proxy, ProxyError
from .node import Peer
class Socks5AuthError(ProxyError):
"""Rised when the socks5 protocol encounters an authentication error"""
errorCodes = (
"Succeeded",
"Authentication is required",
"All offered authentication methods were rejected",
"Unknown username or invalid password",
"Unknown error"
)
class Socks5Error(ProxyError):
"""Rised when socks5 protocol encounters an error"""
errorCodes = (
"Succeeded",
"General SOCKS server failure",
"Connection not allowed by ruleset",
"Network unreachable",
"Host unreachable",
"Connection refused",
"TTL expired",
"Command not supported",
"Address type not supported",
"Unknown error"
)
class Socks5(Proxy):
"""A socks5 proxy base class"""
def __init__(self, address=None):
Proxy.__init__(self, address)
self.ipaddr = None
self.destport = address[1]
def state_init(self):
"""Protocol initialization (before connection is established)"""
if self._auth:
self.append_write_buf(struct.pack('BBBB', 0x05, 0x02, 0x00, 0x02))
else:
self.append_write_buf(struct.pack('BBB', 0x05, 0x01, 0x00))
self.set_state("auth_1", length=0, expectBytes=2)
return True
def state_auth_1(self):
"""Perform authentication if peer is requesting it."""
ret = struct.unpack('BB', self.read_buf[:2])
if ret[0] != 5:
# general error
raise GeneralProxyError(1)
elif ret[1] == 0:
# no auth required
self.set_state("auth_done", length=2)
elif ret[1] == 2:
# username/password
self.append_write_buf(
struct.pack(
'BB', 1, len(self._auth[0])) + self._auth[0] + struct.pack(
'B', len(self._auth[1])) + self._auth[1])
self.set_state("auth_needed", length=2, expectBytes=2)
else:
if ret[1] == 0xff:
# auth error
raise Socks5AuthError(2)
else:
# other error
raise GeneralProxyError(1)
return True
def state_auth_needed(self):
"""Handle response to authentication attempt"""
ret = struct.unpack('BB', self.read_buf[0:2])
if ret[0] != 1:
# general error
raise GeneralProxyError(1)
if ret[1] != 0:
# auth error
raise Socks5AuthError(3)
# all ok
self.set_state("auth_done", length=2)
return True
def state_pre_connect(self):
"""Handle feedback from socks5 while it is connecting on our behalf."""
# Get the response
if self.read_buf[0:1] != chr(0x05).encode():
self.close()
raise GeneralProxyError(1)
elif self.read_buf[1:2] != chr(0x00).encode():
# Connection failed
self.close()
if ord(self.read_buf[1:2]) <= 8:
raise Socks5Error(ord(self.read_buf[1:2]))
else:
raise Socks5Error(9)
# Get the bound address/port
elif self.read_buf[3:4] == chr(0x01).encode():
self.set_state("proxy_addr_1", length=4, expectBytes=4)
elif self.read_buf[3:4] == chr(0x03).encode():
self.set_state("proxy_addr_2_1", length=4, expectBytes=1)
else:
self.close()
raise GeneralProxyError(1)
return True
def state_proxy_addr_1(self):
"""Handle IPv4 address returned for peer"""
self.boundaddr = self.read_buf[0:4]
self.set_state("proxy_port", length=4, expectBytes=2)
return True
def state_proxy_addr_2_1(self):
"""
Handle other addresses than IPv4 returned for peer
(e.g. IPv6, onion, ...). This is part 1 which retrieves the
length of the data.
"""
self.address_length = ord(self.read_buf[0:1])
self.set_state(
"proxy_addr_2_2", length=1, expectBytes=self.address_length)
return True
def state_proxy_addr_2_2(self):
"""
Handle other addresses than IPv4 returned for peer
(e.g. IPv6, onion, ...). This is part 2 which retrieves the data.
"""
self.boundaddr = self.read_buf[0:self.address_length]
self.set_state("proxy_port", length=self.address_length, expectBytes=2)
return True
def state_proxy_port(self):
"""Handle peer's port being returned."""
self.boundport = struct.unpack(">H", self.read_buf[0:2])[0]
self.__proxysockname = (self.boundaddr, self.boundport)
if self.ipaddr is not None:
self.__proxypeername = (
socket.inet_ntoa(self.ipaddr), self.destination[1])
else:
self.__proxypeername = (self.destination[0], self.destport)
self.set_state("proxy_handshake_done", length=2)
return True
def proxy_sock_name(self):
"""Handle return value when using SOCKS5
for DNS resolving instead of connecting."""
return socket.inet_ntoa(self.__proxysockname[0])
class Socks5Connection(Socks5):
"""Child socks5 class used for making outbound connections."""
def state_auth_done(self):
"""Request connection to be made"""
# Now we can request the actual connection
self.append_write_buf(struct.pack('BBB', 0x05, 0x01, 0x00))
# If the given destination address is an IP address, we'll
# use the IPv4 address request even if remote resolving was specified.
try:
self.ipaddr = socket.inet_aton(self.destination[0])
self.append_write_buf(chr(0x01).encode() + self.ipaddr)
except socket.error: # may be IPv6!
# Well it's not an IP number, so it's probably a DNS name.
if self._remote_dns:
# Resolve remotely
self.ipaddr = None
self.append_write_buf(chr(0x03).encode() + chr(
len(self.destination[0])).encode() + self.destination[0])
else:
# Resolve locally
self.ipaddr = socket.inet_aton(
socket.gethostbyname(self.destination[0]))
self.append_write_buf(chr(0x01).encode() + self.ipaddr)
self.append_write_buf(struct.pack(">H", self.destination[1]))
self.set_state("pre_connect", length=0, expectBytes=4)
return True
def state_pre_connect(self):
"""Tell socks5 to initiate a connection"""
try:
return Socks5.state_pre_connect(self)
except Socks5Error as e:
self.close_reason = e.message
self.set_state("close")
class Socks5Resolver(Socks5):
"""DNS resolver class using socks5"""
def __init__(self, host):
self.host = host
self.port = 8444
Socks5.__init__(self, address=Peer(self.host, self.port))
def state_auth_done(self):
"""Perform resolving"""
# Now we can request the actual connection
self.append_write_buf(struct.pack('BBB', 0x05, 0xF0, 0x00))
self.append_write_buf(chr(0x03).encode() + chr(
len(self.host)).encode() + str(self.host))
self.append_write_buf(struct.pack(">H", self.port))
self.set_state("pre_connect", length=0, expectBytes=4)
return True
def resolved(self):
"""
Resolving is done, process the return value.
To use this within PyBitmessage, a callback needs to be
implemented which hasn't been done yet.
"""
print("Resolved {} as {}".format(self.host, self.proxy_sock_name()))

View File

@ -0,0 +1,71 @@
"""
Network statistics
"""
import time
from network import asyncore_pollchoose as asyncore
from network.connectionpool import BMConnectionPool
from network.objectracker import missingObjects
lastReceivedTimestamp = time.time()
lastReceivedBytes = 0
currentReceivedSpeed = 0
lastSentTimestamp = time.time()
lastSentBytes = 0
currentSentSpeed = 0
def connectedHostsList():
"""List of all the connected hosts"""
return BMConnectionPool().establishedConnections()
def sentBytes():
"""Sending Bytes"""
return asyncore.sentBytes
def uploadSpeed():
"""Getting upload speed"""
# pylint: disable=global-statement
global lastSentTimestamp, lastSentBytes, currentSentSpeed
currentTimestamp = time.time()
if int(lastSentTimestamp) < int(currentTimestamp):
currentSentBytes = asyncore.sentBytes
currentSentSpeed = int(
(currentSentBytes - lastSentBytes) / (
currentTimestamp - lastSentTimestamp))
lastSentBytes = currentSentBytes
lastSentTimestamp = currentTimestamp
return currentSentSpeed
def receivedBytes():
"""Receiving Bytes"""
return asyncore.receivedBytes
def downloadSpeed():
"""Getting download speed"""
# pylint: disable=global-statement
global lastReceivedTimestamp, lastReceivedBytes, currentReceivedSpeed
currentTimestamp = time.time()
if int(lastReceivedTimestamp) < int(currentTimestamp):
currentReceivedBytes = asyncore.receivedBytes
currentReceivedSpeed = int(
(currentReceivedBytes - lastReceivedBytes) / (
currentTimestamp - lastReceivedTimestamp))
lastReceivedBytes = currentReceivedBytes
lastReceivedTimestamp = currentTimestamp
return currentReceivedSpeed
def pendingDownload():
"""Getting pending downloads"""
return len(missingObjects)
def pendingUpload():
"""Getting pending uploads"""
return 0

View File

@ -0,0 +1,433 @@
"""
TCP protocol handler
"""
# pylint: disable=too-many-ancestors, protected-access
import logging
import math
import random
import socket
import time
import addresses
import network.asyncore_pollchoose as asyncore
from network import connectionpool
import helper_random
import knownnodes
import protocol
import state
from bmconfigparser import BMConfigParser
from helper_random import randomBytes
from inventory import Inventory
from network.advanceddispatcher import AdvancedDispatcher
from network.assemble import assemble_addr
from network.bmproto import BMProto
from network.constants import MAX_OBJECT_COUNT
from network.dandelion import Dandelion
from network.objectracker import ObjectTracker
from network.socks4a import Socks4aConnection
from network.socks5 import Socks5Connection
from network.tls import TLSDispatcher
from .node import Peer
from queues import UISignalQueue, invQueue, receiveDataQueue
# pylint: disable=logging-format-interpolation
logger = logging.getLogger('default')
maximumAgeOfNodesThatIAdvertiseToOthers = 10800 #: Equals three hours
class TCPConnection(BMProto, TLSDispatcher):
# pylint: disable=too-many-instance-attributes
"""
.. todo:: Look to understand and/or fix the non-parent-init-called
"""
def __init__(self, address=None, sock=None):
BMProto.__init__(self, address=address, sock=sock)
self.verackReceived = False
self.verackSent = False
self.streams = [0]
self.fullyEstablished = False
self.connectedAt = 0
self.skipUntil = 0
if address is None and sock is not None:
self.destination = Peer(*sock.getpeername())
self.isOutbound = False
TLSDispatcher.__init__(self, sock, server_side=True)
self.connectedAt = time.time()
logger.debug(
'Received connection from %s:%i',
self.destination.host, self.destination.port)
self.nodeid = randomBytes(8)
elif address is not None and sock is not None:
TLSDispatcher.__init__(self, sock, server_side=False)
self.isOutbound = True
logger.debug(
'Outbound proxy connection to %s:%i',
self.destination.host, self.destination.port)
else:
self.destination = address
self.isOutbound = True
try:
self.create_socket(
socket.AF_INET6 if ":" in address.host else socket.AF_INET,
socket.SOCK_STREAM)
except TypeError:
self.create_socket(
socket.AF_INET6 if ':'.encode() in address.host else socket.AF_INET,
socket.SOCK_STREAM)
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
TLSDispatcher.__init__(self, sock, server_side=False)
self.connect(self.destination)
logger.debug(
'Connecting to {}:{}'.format(
self.destination.host, self.destination.port))
try:
self.local = (
protocol.checkIPAddress(
protocol.encodeHost(self.destination.host), True) and
not protocol.checkSocksIP(self.destination.host)
)
except socket.error:
# it's probably a hostname
pass
self.network_group = protocol.network_group(self.destination.host)
ObjectTracker.__init__(self) # pylint: disable=non-parent-init-called
self.bm_proto_reset()
self.set_state("bm_header", expectBytes=protocol.Header.size)
def antiIntersectionDelay(self, initial=False):
"""
This is a defense against the so called intersection attacks.
It is called when you notice peer is requesting non-existing
objects, or right after the connection is established. It will
estimate how long an object will take to propagate across the
network, and skip processing "getdata" requests until then. This
means an attacker only has one shot per IP to perform the attack.
"""
# estimated time for a small object to propagate across the
# whole network
max_known_nodes = max(
len(knownnodes.knownNodes[x]) for x in knownnodes.knownNodes)
delay = math.ceil(math.log(max_known_nodes + 2, 20)) * (
0.2 + invQueue.queueCount / 2.0)
# take the stream with maximum amount of nodes
# +2 is to avoid problems with log(0) and log(1)
# 20 is avg connected nodes count
# 0.2 is avg message transmission time
if delay > 0:
if initial:
self.skipUntil = self.connectedAt + delay
if self.skipUntil > time.time():
logger.debug(
'Initial skipping processing getdata for %.2fs',
self.skipUntil - time.time())
else:
logger.debug(
'Skipping processing getdata due to missing object'
' for %.2fs', delay)
self.skipUntil = time.time() + delay
def state_connection_fully_established(self):
"""
State after the bitmessage protocol handshake is completed
(version/verack exchange, and if both side support TLS,
the TLS handshake as well).
"""
self.set_connection_fully_established()
self.set_state("bm_header")
self.bm_proto_reset()
return True
def set_connection_fully_established(self):
"""Initiate inventory synchronisation."""
if not self.isOutbound and not self.local:
state.clientHasReceivedIncomingConnections = True
UISignalQueue.put(('setStatusIcon', 'green'))
UISignalQueue.put(
('updateNetworkStatusTab', (
self.isOutbound, True, self.destination)))
self.antiIntersectionDelay(True)
self.fullyEstablished = True
if self.isOutbound:
knownnodes.increaseRating(self.destination)
Dandelion().maybeAddStem(self)
self.sendAddr()
self.sendBigInv()
def sendAddr(self):
"""Send a partial list of known addresses to peer."""
# We are going to share a maximum number of 1000 addrs (per overlapping
# stream) with our peer. 500 from overlapping streams, 250 from the
# left child stream, and 250 from the right child stream.
maxAddrCount = BMConfigParser().safeGetInt(
"bitmessagesettings", "maxaddrperstreamsend", 500)
templist = []
addrs = {}
for stream in self.streams:
with knownnodes.knownNodesLock:
for nitro, sitro in enumerate((stream, stream * 2, stream * 2 + 1)):
nodes = knownnodes.knownNodes.get(sitro)
if not nodes:
continue
# only if more recent than 3 hours
# and having positive or neutral rating
filtered = [
(k, v) for k, v in iter(nodes.items())
if v["lastseen"] > int(time.time()) -
maximumAgeOfNodesThatIAdvertiseToOthers and
v["rating"] >= 0 and len(k.host) <= 22
]
# sent 250 only if the remote isn't interested in it
elemCount = min(
len(filtered),
maxAddrCount / 2 if nitro else maxAddrCount)
addrs[sitro] = helper_random.randomsample(filtered, elemCount)
for substream in addrs:
for peer, params in addrs[substream]:
templist.append((substream, peer, params["lastseen"]))
if templist:
self.append_write_buf(assemble_addr(templist))
def sendBigInv(self):
"""
Send hashes of all inventory objects, chunked as the protocol has
a per-command limit.
"""
def sendChunk():
"""Send one chunk of inv entries in one command"""
if objectCount == 0:
return
logger.debug(
'Sending huge inv message with {} objects to jcust this'
' one peer'.format(objectCount))
self.append_write_buf(protocol.CreatePacket(
'inv', addresses.encodeVarint(objectCount) + payload))
# Select all hashes for objects in this stream.
bigInvList = {}
for stream in self.streams:
# may lock for a long time, but I think it's better than
# thousands of small locks
with self.objectsNewToThemLock:
for objHash in Inventory().unexpired_hashes_by_stream(stream):
# don't advertise stem objects on bigInv
if Dandelion().hasHash(objHash):
continue
bigInvList[objHash] = 0
objectCount = 0
payload = bytes()
# Now let us start appending all of these hashes together. They will be
# sent out in a big inv message to our new peer.
for obj_hash, _ in bigInvList.items():
payload += obj_hash
objectCount += 1
# Remove -1 below when sufficient time has passed for users to
# upgrade to versions of PyBitmessage that accept inv with 50,000
# items
if objectCount >= MAX_OBJECT_COUNT - 1:
sendChunk()
payload = b''
objectCount = 0
# flush
sendChunk()
def handle_connect(self):
"""Callback for TCP connection being established."""
try:
AdvancedDispatcher.handle_connect(self)
except socket.error as e:
# pylint: disable=protected-access
if e.errno in asyncore._DISCONNECTED:
logger.debug(
'%s:%i: Connection failed: %s',
self.destination.host, self.destination.port, e)
return
self.nodeid = randomBytes(8)
self.append_write_buf(
protocol.assembleVersionMessage(
self.destination.host, self.destination.port,
connectionpool.BMConnectionPool().streams,
False, nodeid=self.nodeid))
self.connectedAt = time.time()
receiveDataQueue.put(self.destination)
def handle_read(self):
"""Callback for reading from a socket"""
TLSDispatcher.handle_read(self)
if self.isOutbound and self.fullyEstablished:
for s in self.streams:
try:
with knownnodes.knownNodesLock:
knownnodes.knownNodes[s][self.destination][
"lastseen"] = time.time()
except KeyError:
pass
receiveDataQueue.put(self.destination)
def handle_write(self):
"""Callback for writing to a socket"""
TLSDispatcher.handle_write(self)
def handle_close(self):
"""Callback for connection being closed."""
if self.isOutbound and not self.fullyEstablished:
knownnodes.decreaseRating(self.destination)
if self.fullyEstablished:
UISignalQueue.put((
'updateNetworkStatusTab',
(self.isOutbound, False, self.destination)
))
if self.isOutbound:
Dandelion().maybeRemoveStem(self)
BMProto.handle_close(self)
class Socks5BMConnection(Socks5Connection, TCPConnection):
"""SOCKS5 wrapper for TCP connections"""
def __init__(self, address):
Socks5Connection.__init__(self, address=address)
TCPConnection.__init__(self, address=address, sock=self.socket)
self.set_state("init")
def state_proxy_handshake_done(self):
"""
State when SOCKS5 connection succeeds, we need to send a
Bitmessage handshake to peer.
"""
Socks5Connection.state_proxy_handshake_done(self)
self.nodeid = randomBytes(8)
self.append_write_buf(
protocol.assembleVersionMessage(
self.destination.host, self.destination.port,
connectionpool.BMConnectionPool().streams,
False, nodeid=self.nodeid))
self.set_state("bm_header", expectBytes=protocol.Header.size)
return True
class Socks4aBMConnection(Socks4aConnection, TCPConnection):
"""SOCKS4a wrapper for TCP connections"""
def __init__(self, address):
Socks4aConnection.__init__(self, address=address)
TCPConnection.__init__(self, address=address, sock=self.socket)
self.set_state("init")
def state_proxy_handshake_done(self):
"""
State when SOCKS4a connection succeeds, we need to send a
Bitmessage handshake to peer.
"""
Socks4aConnection.state_proxy_handshake_done(self)
self.nodeid = randomBytes(8)
self.append_write_buf(
protocol.assembleVersionMessage(
self.destination.host, self.destination.port,
connectionpool.BMConnectionPool().streams,
False, nodeid=self.nodeid))
self.set_state("bm_header", expectBytes=protocol.Header.size)
return True
def bootstrap(connection_class):
"""Make bootstrapper class for connection type (connection_class)"""
class Bootstrapper(connection_class):
"""Base class for bootstrappers"""
_connection_base = connection_class
def __init__(self, host, port):
self._connection_base.__init__(self, Peer(host, port))
self.close_reason = self._succeed = False
def bm_command_addr(self):
"""
Got addr message - the bootstrap succeed.
Let BMProto process the addr message and switch state to 'close'
"""
BMProto.bm_command_addr(self)
self._succeed = True
# pylint: disable=attribute-defined-outside-init
self.close_reason = "Thanks for bootstrapping!"
self.set_state("close")
def handle_close(self):
"""
After closing the connection switch knownnodes.knownNodesActual
back to False if the bootstrapper failed.
"""
self._connection_base.handle_close(self)
if not self._succeed:
knownnodes.knownNodesActual = False
return Bootstrapper
class TCPServer(AdvancedDispatcher):
"""TCP connection server for Bitmessage protocol"""
def __init__(self, host='127.0.0.1', port=8444):
if '_map' not in dir(self):
AdvancedDispatcher.__init__(self)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.set_reuse_addr()
for attempt in range(50):
try:
if attempt > 0:
logger.warning('Failed to bind on port %s', port)
port = random.randint(32767, 65535)
self.bind((host, port))
except socket.error as e:
if e.errno in (asyncore.EADDRINUSE, asyncore.WSAEADDRINUSE):
continue
else:
if attempt > 0:
logger.warning('Setting port to %s', port)
BMConfigParser().set(
'bitmessagesettings', 'port', str(port))
BMConfigParser().save()
break
self.destination = Peer(host, port)
self.bound = True
self.listen(5)
def is_bound(self):
"""Is the socket bound?"""
try:
return self.bound
except AttributeError:
return False
def handle_accept(self):
"""Incoming connection callback"""
try:
sock = self.accept()[0]
except (TypeError, IndexError):
return
state.ownAddresses[Peer(*sock.getsockname())] = True
if (
len(connectionpool.BMConnectionPool().inboundConnections) +
len(connectionpool.BMConnectionPool().outboundConnections) >
BMConfigParser().safeGetInt(
'bitmessagesettings', 'maxtotalconnections') +
BMConfigParser().safeGetInt(
'bitmessagesettings', 'maxbootstrapconnections') + 10
):
# 10 is a sort of buffer, in between it will go through
# the version handshake and return an error to the peer
logger.warning("Server full, dropping connection")
sock.close()
return
try:
connectionpool.BMConnectionPool().addConnection(
TCPConnection(sock=sock))
except socket.error:
pass

View File

@ -0,0 +1,33 @@
"""Threading primitives for the network package"""
import logging
import random
import threading
class StoppableThread(threading.Thread):
"""Base class for application threads with stopThread method"""
name = None
logger = logging.getLogger('default')
def __init__(self, name=None):
if name:
self.name = name
super(StoppableThread, self).__init__(name=self.name)
self.stop = threading.Event()
self._stopped = False
random.seed()
self.logger.info('Init thread %s', self.name)
def stopThread(self):
"""Stop the thread"""
self._stopped = True
self.stop.set()
class BusyError(threading.ThreadError):
"""
Thread error raised when another connection holds the lock
we are trying to acquire.
"""
pass

View File

@ -0,0 +1,241 @@
"""
SSL/TLS negotiation.
"""
import logging
import os
import socket
import ssl
import sys
import network.asyncore_pollchoose as asyncore
import paths
from network.advanceddispatcher import AdvancedDispatcher
from queues import receiveDataQueue
logger = logging.getLogger('default')
_DISCONNECTED_SSL = frozenset((ssl.SSL_ERROR_EOF,))
# sslProtocolVersion
if sys.version_info >= (2, 7, 13):
# this means TLSv1 or higher
# in the future change to
# ssl.PROTOCOL_TLS1.2
# Right now I am using the python3.5.2 and I faced the ssl for protocol due to this I
# have used try and catch
try:
sslProtocolVersion = ssl.PROTOCOL_TLS # pylint: disable=no-member
except AttributeError:
sslProtocolVersion = ssl.PROTOCOL_SSLv23
elif sys.version_info >= (2, 7, 9):
# this means any SSL/TLS.
# SSLv2 and 3 are excluded with an option after context is created
sslProtocolVersion = ssl.PROTOCOL_SSLv23
else:
# this means TLSv1, there is no way to set "TLSv1 or higher" or
# "TLSv1.2" in < 2.7.9
sslProtocolVersion = ssl.PROTOCOL_TLSv1
# ciphers
if ssl.OPENSSL_VERSION_NUMBER >= 0x10100000 and not \
ssl.OPENSSL_VERSION.startswith("LibreSSL"):
sslProtocolCiphers = "AECDH-AES256-SHA@SECLEVEL=0"
else:
sslProtocolCiphers = "AECDH-AES256-SHA"
class TLSDispatcher(AdvancedDispatcher):
"""TLS functionality for classes derived from AdvancedDispatcher"""
# pylint: disable=too-many-instance-attributes, too-many-arguments
# pylint: disable=super-init-not-called
def __init__(self, _=None, sock=None, certfile=None, keyfile=None,
server_side=False, ciphers=sslProtocolCiphers):
self.want_read = self.want_write = True
if certfile is None:
self.certfile = os.path.join(
paths.codePath(), 'sslkeys', 'cert.pem')
else:
self.certfile = certfile
if keyfile is None:
self.keyfile = os.path.join(
paths.codePath(), 'sslkeys', 'key.pem')
else:
self.keyfile = keyfile
self.server_side = server_side
self.ciphers = ciphers
self.tlsStarted = False
self.tlsDone = False
self.tlsVersion = "N/A"
self.isSSL = False
self.sslSocket = None
def state_tls_init(self):
"""Prepare sockets for TLS handshake"""
# pylint: disable=attribute-defined-outside-init
self.isSSL = True
self.tlsStarted = True
# Once the connection has been established,
# it's safe to wrap the socket.
self.want_read = self.want_write = True
self.set_state("tls_handshake")
return False
# if hasattr(self.socket, "context"):
# self.socket.context.set_ecdh_curve("secp256k1")
@staticmethod
def state_tls_handshake():
"""
Do nothing while TLS handshake is pending, as during this phase
we need to react to callbacks instead
"""
return False
def writable(self):
"""Handle writable checks for TLS-enabled sockets"""
try:
if self.tlsStarted and not self.tlsDone and not self.write_buf:
return self.want_write
return AdvancedDispatcher.writable(self)
except AttributeError:
return AdvancedDispatcher.writable(self)
def readable(self):
"""Handle readable check for TLS-enabled sockets"""
try:
# during TLS handshake, and after flushing write buffer,
# return status of last handshake attempt
if self.tlsStarted and not self.tlsDone and not self.write_buf:
return self.want_read
# prior to TLS handshake,
# receiveDataThread should emulate synchronous behaviour
elif not self.fullyEstablished and (
self.expectBytes == 0 or not self.write_buf_empty()):
return False
return AdvancedDispatcher.readable(self)
except AttributeError:
return AdvancedDispatcher.readable(self)
def handle_read(self): # pylint: disable=inconsistent-return-statements
"""
Handle reads for sockets during TLS handshake. Requires special
treatment as during the handshake, buffers must remain empty
and normal reads must be ignored.
"""
try:
if self.tlsStarted and not self.tlsDone and not self.write_buf:
# logger.debug(
# "%s:%i TLS handshaking (read)", self.destination.host,
# self.destination.port)
self.tls_handshake()
else:
# logger.debug(
# "%s:%i Not TLS handshaking (read)", self.destination.host,
# self.destination.port)
return AdvancedDispatcher.handle_read(self)
except AttributeError:
return AdvancedDispatcher.handle_read(self)
except ssl.SSLError as err:
if err.errno == ssl.SSL_ERROR_WANT_READ:
return
elif err.errno in _DISCONNECTED_SSL:
self.handle_close()
return
logger.info("SSL Error: %s", str(err))
self.handle_close()
return
def handle_write(self): # pylint: disable=inconsistent-return-statements
"""
Handle writes for sockets during TLS handshake. Requires special
treatment as during the handshake, buffers must remain empty
and normal writes must be ignored.
"""
try:
# wait for write buffer flush
if self.tlsStarted and not self.tlsDone and not self.write_buf:
# logger.debug(
# "%s:%i TLS handshaking (write)", self.destination.host,
# self.destination.port)
self.tls_handshake()
else:
return AdvancedDispatcher.handle_write(self)
except AttributeError:
return AdvancedDispatcher.handle_write(self)
except ssl.SSLError as err:
if err.errno == ssl.SSL_ERROR_WANT_WRITE:
return 0
elif err.errno in _DISCONNECTED_SSL:
self.handle_close()
return 0
logger.info("SSL Error: %s", str(err))
self.handle_close()
return
def tls_handshake(self): # pylint:disable=too-many-branches
"""Perform TLS handshake and handle its stages"""
# wait for flush
# self.sslSocket.setblocking(0)
if self.write_buf:
return False
if not self.sslSocket:
self.del_channel()
if sys.version_info >= (2, 7, 9):
context = ssl.create_default_context(
purpose=ssl.Purpose.SERVER_AUTH
if self.server_side else ssl.Purpose.CLIENT_AUTH)
context.set_ciphers(self.ciphers)
context.set_ecdh_curve("secp256k1")
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
# also exclude TLSv1 and TLSv1.1 in the future
context.options = ssl.OP_ALL | ssl.OP_NO_SSLv2 |\
ssl.OP_NO_SSLv3 | ssl.OP_SINGLE_ECDH_USE |\
ssl.OP_CIPHER_SERVER_PREFERENCE
self.sslSocket = context.wrap_socket(
self.socket, server_side=self.server_side,
do_handshake_on_connect=False)
else:
self.sslSocket = ssl.wrap_socket(
self.socket, server_side=self.server_side,
ssl_version=sslProtocolVersion,
certfile=self.certfile, keyfile=self.keyfile,
ciphers=self.ciphers, do_handshake_on_connect=False)
self.sslSocket.setblocking(0)
self.set_socket(self.sslSocket)
# Perform the handshake.
try:
self.sslSocket.do_handshake()
except ssl.SSLError as err:
self.want_read = self.want_write = False
if err.args[0] == ssl.SSL_ERROR_WANT_READ:
self.want_read = True
if err.args[0] == ssl.SSL_ERROR_WANT_WRITE:
self.want_write = True
if not (self.want_write or self.want_read):
raise
except socket.error as err:
# pylint: disable=protected-access
if err.errno in asyncore._DISCONNECTED:
self.handle_close()
else:
raise
else:
if sys.version_info >= (2, 7, 9):
self.tlsVersion = self.sslSocket.version()
logger.debug(
'%s:%i: TLS handshake success, TLS protocol version: %s',
self.destination.host, self.destination.port,
self.tlsVersion)
else:
self.tlsVersion = "TLSv1"
logger.debug(
'%s:%i: TLS handshake success',
self.destination.host, self.destination.port)
# The handshake has completed, so remove this channel and...
self.tlsDone = True
self.bm_proto_reset()
self.set_state("connection_fully_established")
receiveDataQueue.put(self.destination)
return False

View File

@ -0,0 +1,152 @@
"""
UDP protocol handler
"""
import logging
import socket
import time
import protocol
from network.bmproto import BMProto
from network.objectracker import ObjectTracker
from .node import Peer
import state
from queues import receiveDataQueue
logger = logging.getLogger('default')
# pylint: disable=logging-format-interpolation
class UDPSocket(BMProto): # pylint: disable=too-many-instance-attributes
"""Bitmessage protocol over UDP (class)"""
port = 8444
announceInterval = 60
def __init__(self, host=None, sock=None, announcing=False):
# pylint: disable=bad-super-call
super(BMProto, self).__init__(sock=sock)
self.verackReceived = True
self.verackSent = True
# .. todo:: sort out streams
self.streams = [1]
self.fullyEstablished = True
self.connectedAt = 0
self.skipUntil = 0
if sock is None:
if host is None:
host = ''
self.create_socket(
socket.AF_INET6 if ":" in host else socket.AF_INET,
socket.SOCK_DGRAM
)
self.set_socket_reuse()
logger.info("Binding UDP socket to %s:%i", host, self.port)
self.socket.bind((host, self.port))
else:
self.socket = sock
self.set_socket_reuse()
self.listening = Peer(*self.socket.getsockname())
self.destination = Peer(*self.socket.getsockname())
ObjectTracker.__init__(self)
self.connecting = False
self.connected = True
self.announcing = announcing
self.set_state("bm_header", expectBytes=protocol.Header.size)
def set_socket_reuse(self):
"""Set socket reuse option"""
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
except AttributeError:
pass
# disable most commands before doing research / testing
# only addr (peer discovery), error and object are implemented
def bm_command_getdata(self):
return True
def bm_command_inv(self):
return True
def bm_command_addr(self):
addresses = self._decode_addr()
# only allow peer discovery from private IPs in order to avoid
# attacks from random IPs on the internet
self.local = True
remoteport = False
for seenTime, stream, _, ip, port in addresses:
decodedIP = protocol.checkIPAddress(bytes(ip))
if stream not in state.streamsInWhichIAmParticipating:
continue
if (seenTime < time.time() - self.maxTimeOffset
or seenTime > time.time() + self.maxTimeOffset):
continue
if decodedIP is False:
# if the address isn't local, interpret it as
# the host's own announcement
remoteport = port
if remoteport is False:
return True
logger.debug(
"received peer discovery from {}:{} (port {}):".format(
self.destination.host, self.destination.port, remoteport))
if self.local:
state.discoveredPeers[Peer(self.destination.host, remoteport)] = \
time.time()
return True
def bm_command_portcheck(self):
return True
def bm_command_ping(self):
return True
def bm_command_pong(self):
return True
def bm_command_verack(self):
return True
def bm_command_version(self):
return True
def handle_connect(self):
return
def writable(self):
return self.write_buf
def readable(self):
return len(self.read_buf) < self._buf_len
def handle_read(self):
try:
(recdata, addr) = self.socket.recvfrom(self._buf_len)
except socket.error as e:
logger.error("socket error: %s", e)
return
self.destination = Peer(*addr)
encodedAddr = protocol.encodeHost(addr[0])
self.local = bool(protocol.checkIPAddress(encodedAddr, True))
# overwrite the old buffer to avoid mixing data and so that
# self.local works correctly
self.read_buf[0:] = recdata
self.bm_proto_reset()
receiveDataQueue.put(self.listening)
def handle_write(self):
try:
retval = self.socket.sendto(
self.write_buf, ('<broadcast>', self.port))
except socket.error as e:
logger.error("socket error on sendto: %s", e)
if e.errno == 101:
self.announcing = False
self.socket.close()
retval = 0
self.slice_write_buf(retval)

View File

@ -0,0 +1,71 @@
"""
`UploadThread` class definition
"""
import time
import helper_random
import protocol
from inventory import Inventory
from network.connectionpool import BMConnectionPool
from network.dandelion import Dandelion
from network.randomtrackingdict import RandomTrackingDict
from network.threads import StoppableThread
class UploadThread(StoppableThread):
"""
This is a thread that uploads the objects that the peers requested from me
"""
maxBufSize = 2097152 # 2MB
name = "Uploader"
def run(self):
while not self._stopped:
uploaded = 0
# Choose uploading peers randomly
connections = BMConnectionPool().establishedConnections()
helper_random.randomshuffle(connections)
for i in connections:
now = time.time()
# avoid unnecessary delay
if i.skipUntil >= now:
continue
if len(i.write_buf) > self.maxBufSize:
continue
try:
request = i.pendingUpload.randomKeys(
RandomTrackingDict.maxPending)
except KeyError:
continue
payload = bytearray()
chunk_count = 0
for chunk in request:
del i.pendingUpload[chunk]
if Dandelion().hasHash(chunk) and \
i != Dandelion().objectChildStem(chunk):
i.antiIntersectionDelay()
print
self.logger.info(
'%s asked for a stem object we didn\'t offer to it.',
i.destination)
break
try:
payload.extend(protocol.CreatePacket(
'object', Inventory()[chunk].payload))
chunk_count += 1
except KeyError:
i.antiIntersectionDelay()
self.logger.info(
'%s asked for an object we don\'t have.',
i.destination)
break
if not chunk_count:
continue
i.append_write_buf(payload)
self.logger.debug(
'%s:%i Uploading %i objects',
i.destination.host, i.destination.port, chunk_count)
uploaded += chunk_count
if not uploaded:
self.stop.wait(1)

View File

@ -0,0 +1,55 @@
"""Most of the queues used by bitmessage threads are defined here."""
import threading
import time
from six.moves import queue
# try:
# from multiqueue import MultiQueue
# except ImportError:
# from .multiqueue import MultiQueue
class ObjectProcessorQueue(queue.Queue):
"""Special queue class using lock for `.threads.objectProcessor`"""
maxSize = 32000000
def __init__(self):
queue.Queue.__init__(self)
self.sizeLock = threading.Lock()
#: in Bytes. We maintain this to prevent nodes from flooding us
#: with objects which take up too much memory. If this gets
#: too big we'll sleep before asking for further objects.
self.curSize = 0
def put(self, item, block=True, timeout=None):
while self.curSize >= self.maxSize:
time.sleep(1)
with self.sizeLock:
self.curSize += len(item[1])
queue.Queue.put(self, item, block, timeout)
def get(self, block=True, timeout=None):
item = queue.Queue.get(self, block, timeout)
with self.sizeLock:
self.curSize -= len(item[1])
return item
workerQueue = queue.Queue()
UISignalQueue = queue.Queue()
addressGeneratorQueue = queue.Queue()
#: `.network.ReceiveQueueThread` instances dump objects they hear
#: on the network into this queue to be processed.
objectProcessorQueue = ObjectProcessorQueue()
# invQueue = MultiQueue()
# addrQueue = MultiQueue()
portCheckerQueue = queue.Queue()
receiveDataQueue = queue.Queue()
#: The address generator thread uses this queue to get information back
#: to the API thread.
apiAddressGeneratorReturnQueue = queue.Queue()
#: for exceptions
excQueue = queue.Queue()

View File

@ -0,0 +1,78 @@
{
"Inbox": {
"kv_string": "inbox",
"name_screen": "inbox",
"Import": "from pybitmessage.baseclass.inbox import Inbox",
},
"Sent": {
"kv_string": "sent",
"name_screen": "sent",
"Import": "from pybitmessage.baseclass.sent import Sent",
},
"Draft": {
"kv_string": "draft",
"name_screen": "draft",
"Import": "from pybitmessage.baseclass.draft import Draft",
},
"Trash": {
"kv_string": "trash",
"name_screen": "trash",
"Import": "from pybitmessage.baseclass.trash import Trash",
},
"All Mails": {
"kv_string": "allmails",
"name_screen": "allmails",
"Import": "from pybitmessage.baseclass.allmail import Allmails",
},
"Address Book": {
"kv_string": "addressbook",
"name_screen": "addressbook",
"Import": "from pybitmessage.baseclass.addressbook import AddressBook",
},
"Settings": {
"kv_string": "settings",
"name_screen": "set",
"Import": "from pybitmessage.baseclass.settings import Setting",
},
"Payment": {
"kv_string": "payment",
"name_screen": "payment",
"Import": "from pybitmessage.baseclass.payment import Payment",
},
"Network status": {
"kv_string": "network",
"name_screen": "networkstat",
"Import": "from pybitmessage.baseclass.network import NetworkStat",
},
"My addresses": {
"kv_string": "myaddress",
"name_screen": "myaddress",
"Import": "from pybitmessage.baseclass.myaddress import MyAddress",
},
"MailDetail": {
"kv_string": "maildetail",
"name_screen": "mailDetail",
"Import": "from pybitmessage.baseclass.maildetail import MailDetail",
},
"Create": {
"kv_string": "msg_composer",
"name_screen": "create",
"Import": "from pybitmessage.baseclass.msg_composer import Create",
},
"Login": {
"kv_string": "login",
"Import": "from pybitmessage.baseclass.login import *",
},
"Scanner": {
"kv_string": "scan_screen",
"Import": "from pybitmessage.baseclass.scan_screen import ScanScreen",
},
"Popups": {
"kv_string": "popup",
"Import": "from pybitmessage.baseclass.popup import *",
},
"Qrcode": {
"kv_string": "qrcode",
"Import": "from pybitmessage.baseclass.qrcode import ShowQRCode",
},
}

View File

@ -0,0 +1,3 @@
from threading import Semaphore
kivyuisignaler = Semaphore(0)

View File

@ -0,0 +1,254 @@
"""
Some shared functions
.. deprecated:: 0.6.3
Should be moved to different places and this file removed,
but it needs refactoring.
"""
from __future__ import division
# Libraries.
import hashlib
import os
import stat
import subprocess
import sys
from binascii import hexlify
from pyelliptic import arithmetic
from kivy.utils import platform
# Project imports.
import highlevelcrypto
import state
from addresses import decodeAddress, encodeVarint
from bmconfigparser import BMConfigParser
from debug import logger
from helper_sql import sqlQuery
# pylint: disable=logging-format-interpolation
myECCryptorObjects = {}
MyECSubscriptionCryptorObjects = {}
# The key in this dictionary is the RIPE hash which is encoded
# in an address and value is the address itself.
myAddressesByHash = {}
# The key in this dictionary is the tag generated from the address.
myAddressesByTag = {}
broadcastSendersForWhichImWatching = {}
def isAddressInMyAddressBook(address):
"""Is address in my addressbook?"""
queryreturn = sqlQuery(
'''select address from addressbook where address=?''',
address)
return queryreturn != []
# At this point we should really just have a isAddressInMy(book, address)...
def isAddressInMySubscriptionsList(address):
"""Am I subscribed to this address?"""
queryreturn = sqlQuery(
'''select * from subscriptions where address=?''',
str(address))
return queryreturn != []
def isAddressInMyAddressBookSubscriptionsListOrWhitelist(address):
"""
Am I subscribed to this address, is it in my addressbook or whitelist?
"""
if isAddressInMyAddressBook(address):
return True
queryreturn = sqlQuery(
'''SELECT address FROM whitelist where address=?'''
''' and enabled = '1' ''',
address)
if queryreturn != []:
return True
queryreturn = sqlQuery(
'''select address from subscriptions where address=?'''
''' and enabled = '1' ''',
address)
if queryreturn != []:
return True
return False
def decodeWalletImportFormat(WIFstring):
# pylint: disable=inconsistent-return-statements
"""
Convert private key from base58 that's used in the config file to
8-bit binary string
"""
fullString = arithmetic.changebase(WIFstring, 58, 256)
privkey = fullString[:-4]
if fullString[-4:] != hashlib.sha256(hashlib.sha256(privkey).digest()).digest()[:4]:
logger.critical(
'Major problem! When trying to decode one of your'
' private keys, the checksum failed. Here are the first'
' 6 characters of the PRIVATE key: {}'.format(str(WIFstring)[:6])
)
os._exit(0) # pylint: disable=protected-access
if privkey[0:1] == '\x80'.encode()[1:]: # checksum passed
return privkey[1:]
logger.critical(
'Major problem! When trying to decode one of your private keys,'
' the checksum passed but the key doesn\'t begin with hex 80.'
' Here is the PRIVATE key: {}'.format(WIFstring)
)
os._exit(0) # pylint: disable=protected-access
def reloadMyAddressHashes():
"""Reload keys for user's addresses from the config file"""
logger.debug('reloading keys from keys.dat file')
myECCryptorObjects.clear()
myAddressesByHash.clear()
myAddressesByTag.clear()
# myPrivateKeys.clear()
keyfileSecure = checkSensitiveFilePermissions(os.path.join(
state.appdata, 'keys.dat'))
hasEnabledKeys = False
for addressInKeysFile in BMConfigParser().addresses(hidden=True):
isEnabled = BMConfigParser().safeGet(addressInKeysFile, 'enabled')
if isEnabled:
hasEnabledKeys = True
# status
addressVersionNumber, streamNumber, hashobj = decodeAddress(addressInKeysFile)[1:]
if addressVersionNumber in (2, 3, 4):
# Returns a simple 32 bytes of information encoded
# in 64 Hex characters, or null if there was an error.
privEncryptionKey = hexlify(decodeWalletImportFormat(
BMConfigParser().get(addressInKeysFile, 'privencryptionkey')))
# It is 32 bytes encoded as 64 hex characters
if len(privEncryptionKey) == 64:
myECCryptorObjects[hashobj] = \
highlevelcrypto.makeCryptor(privEncryptionKey)
myAddressesByHash[hashobj] = addressInKeysFile
tag = hashlib.sha512(hashlib.sha512(
encodeVarint(addressVersionNumber) +
encodeVarint(streamNumber) + hashobj).digest()).digest()[32:]
myAddressesByTag[tag] = addressInKeysFile
else:
logger.error(
'Error in reloadMyAddressHashes: Can\'t handle'
' address versions other than 2, 3, or 4.\n'
)
if not platform == "android":
if not keyfileSecure:
fixSensitiveFilePermissions(state.appdata + 'keys.dat', hasEnabledKeys)
def reloadBroadcastSendersForWhichImWatching():
"""
Reinitialize runtime data for the broadcasts I'm subscribed to
from the config file
"""
broadcastSendersForWhichImWatching.clear()
MyECSubscriptionCryptorObjects.clear()
queryreturn = sqlQuery('SELECT address FROM subscriptions where enabled=1')
logger.debug('reloading subscriptions...')
for row in queryreturn:
address, = row
# status
addressVersionNumber, streamNumber, hashobj = decodeAddress(address)[1:]
if addressVersionNumber == 2:
broadcastSendersForWhichImWatching[hashobj] = 0
# Now, for all addresses, even version 2 addresses,
# we should create Cryptor objects in a dictionary which we will
# use to attempt to decrypt encrypted broadcast messages.
if addressVersionNumber <= 3:
privEncryptionKey = hashlib.sha512(
encodeVarint(addressVersionNumber) +
encodeVarint(streamNumber) + hashobj
).digest()[:32]
MyECSubscriptionCryptorObjects[hashobj] = \
highlevelcrypto.makeCryptor(hexlify(privEncryptionKey))
else:
doubleHashOfAddressData = hashlib.sha512(hashlib.sha512(
encodeVarint(addressVersionNumber) +
encodeVarint(streamNumber) + hashobj
).digest()).digest()
tag = doubleHashOfAddressData[32:]
privEncryptionKey = doubleHashOfAddressData[:32]
MyECSubscriptionCryptorObjects[tag] = \
highlevelcrypto.makeCryptor(hexlify(privEncryptionKey))
def fixPotentiallyInvalidUTF8Data(text):
"""Sanitise invalid UTF-8 strings"""
try:
unicode(text, 'utf-8')
return text
except:
return 'Part of the message is corrupt. The message cannot be' \
' displayed the normal way.\n\n' + repr(text)
def checkSensitiveFilePermissions(filename):
"""
:param str filename: path to the file
:return: True if file appears to have appropriate permissions.
"""
if sys.platform == 'win32':
# .. todo:: This might deserve extra checks by someone familiar with
# Windows systems.
return True
elif sys.platform[:7] == 'freebsd':
# FreeBSD file systems are the same as major Linux file systems
present_permissions = os.stat(filename)[0]
disallowed_permissions = stat.S_IRWXG | stat.S_IRWXO
return present_permissions & disallowed_permissions == 0
try:
# Skip known problems for non-Win32 filesystems
# without POSIX permissions.
fstype = subprocess.check_output(
'stat -f -c "%%T" %s' % (filename),
shell=True,
stderr=subprocess.STDOUT
)
if 'fuseblk'.encode() in fstype:
logger.info(
'Skipping file permissions check for %s.'
' Filesystem fuseblk detected.', filename)
return True
except:
# Swallow exception here, but we might run into trouble later!
logger.error('Could not determine filesystem type. %s', filename)
present_permissions = os.stat(filename)[0]
disallowed_permissions = stat.S_IRWXG | stat.S_IRWXO
return present_permissions & disallowed_permissions == 0
# Fixes permissions on a sensitive file.
def fixSensitiveFilePermissions(filename, hasEnabledKeys):
"""Try to change file permissions to be more restrictive"""
if hasEnabledKeys:
logger.warning(
'Keyfile had insecure permissions, and there were enabled'
' keys. The truly paranoid should stop using them immediately.')
else:
logger.warning(
'Keyfile had insecure permissions, but there were no enabled keys.'
)
try:
present_permissions = os.stat(filename)[0]
disallowed_permissions = stat.S_IRWXG | stat.S_IRWXO
allowed_permissions = ((1 << 32) - 1) ^ disallowed_permissions
new_permissions = (
allowed_permissions & present_permissions)
os.chmod(filename, new_permissions)
logger.info('Keyfile permissions automatically fixed.')
except Exception:
logger.exception('Keyfile permissions could not be fixed.')
raise

View File

@ -0,0 +1,22 @@
"""
Singleton decorator definition
"""
from functools import wraps
def Singleton(cls):
"""
Decorator implementing the singleton pattern:
it restricts the instantiation of a class to one "single" instance.
"""
instances = {}
# https://github.com/sphinx-doc/sphinx/issues/3783
@wraps(cls)
def getinstance():
"""Find an instance or save newly created one"""
if cls not in instances:
instances[cls] = cls()
return instances[cls]
return getinstance

View File

@ -0,0 +1,72 @@
"""
Global runtime variables.
"""
neededPubkeys = {}
streamsInWhichIAmParticipating = []
extPort = None
"""For UPnP"""
socksIP = None
"""for Tor hidden service"""
appdata = ''
"""holds the location of the application data storage directory"""
shutdown = 0
"""
Set to 1 by the `.shutdown.doCleanShutdown` function.
Used to tell the threads to exit.
"""
# Component control flags - set on startup, do not change during runtime
# The defaults are for standalone GUI (default operating mode)
enableNetwork = True
"""enable network threads"""
enableObjProc = True
"""enable object processing thread"""
enableAPI = True
"""enable API (if configured)"""
enableGUI = True
"""enable GUI (QT or ncurses)"""
enableSTDIO = False
"""enable STDIO threads"""
enableKivy = False
"""enable kivy app and test cases"""
curses = False
maximumNumberOfHalfOpenConnections = 0
maximumLengthOfTimeToBotherResendingMessages = 0
invThread = None
addrThread = None
downloadThread = None
uploadThread = None
ownAddresses = {}
discoveredPeers = {}
dandelion = 0
testmode = False
clientHasReceivedIncomingConnections = False
"""used by API command clientStatus"""
numberOfMessagesProcessed = 0
numberOfBroadcastsProcessed = 0
numberOfPubkeysProcessed = 0
statusIconColor = 'red'
"""
GUI status icon color
.. note:: bad style, refactor it
"""
ackdataForWhichImWatching = {}
thisapp = None
"""Singleton instance"""

View File

@ -0,0 +1,34 @@
"""
Ui Singnaler for kivy interface
"""
from threading import Thread
from pybitmessage import queues
from pybitmessage import state
# from semaphores import kivyuisignaler
from threading import Semaphore
class UIkivySignaler(Thread):
"""Kivy ui signaler"""
def run(self):
kivyuisignaler = Semaphore(0)
kivyuisignaler.acquire()
while state.shutdown == 0:
try:
command, data = queues.UISignalQueue.get()
if command == 'writeNewAddressToTable':
address = data[1]
state.kivyapp.variable_1.append(address)
# elif command == 'rerenderAddressBook':
# state.kivyapp.obj_1.refreshs()
# Need to discuss this
elif command == 'writeNewpaymentAddressToTable':
pass
elif command == 'updateSentItemStatusByAckdata':
state.kivyapp.status_dispatching(data)
except Exception as e:
print(e)