mock changes
This commit is contained in:
parent
871157d0aa
commit
e9054a4d37
0
src/tests/mock/__init__.py
Normal file
0
src/tests/mock/__init__.py
Normal file
304
src/tests/mock/bitmessagemock-bkp.py
Normal file
304
src/tests/mock/bitmessagemock-bkp.py
Normal 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()
|
304
src/tests/mock/bitmessagemock.py
Normal file
304
src/tests/mock/bitmessagemock.py
Normal 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()
|
30
src/tests/mock/kivy_main.py
Normal file
30
src/tests/mock/kivy_main.py
Normal 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()
|
0
src/tests/mock/pybitmessage/__init__.py
Normal file
0
src/tests/mock/pybitmessage/__init__.py
Normal file
1504
src/tests/mock/pybitmessage/api.py
Normal file
1504
src/tests/mock/pybitmessage/api.py
Normal 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
|
191
src/tests/mock/pybitmessage/baseclass/addressbook.py
Normal file
191
src/tests/mock/pybitmessage/baseclass/addressbook.py
Normal 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()
|
216
src/tests/mock/pybitmessage/baseclass/allmail.py
Normal file
216
src/tests/mock/pybitmessage/baseclass/allmail.py
Normal 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)
|
144
src/tests/mock/pybitmessage/baseclass/common.py
Normal file
144
src/tests/mock/pybitmessage/baseclass/common.py
Normal 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)
|
199
src/tests/mock/pybitmessage/baseclass/draft.py
Normal file
199
src/tests/mock/pybitmessage/baseclass/draft.py
Normal 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
|
246
src/tests/mock/pybitmessage/baseclass/inbox.py
Normal file
246
src/tests/mock/pybitmessage/baseclass/inbox.py
Normal 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)
|
111
src/tests/mock/pybitmessage/baseclass/login.py
Normal file
111
src/tests/mock/pybitmessage/baseclass/login.py
Normal 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
|
241
src/tests/mock/pybitmessage/baseclass/maildetail.py
Normal file
241
src/tests/mock/pybitmessage/baseclass/maildetail.py
Normal 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)
|
325
src/tests/mock/pybitmessage/baseclass/msg_composer.py
Normal file
325
src/tests/mock/pybitmessage/baseclass/msg_composer.py
Normal 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"]
|
||||
)
|
251
src/tests/mock/pybitmessage/baseclass/myaddress.py
Normal file
251
src/tests/mock/pybitmessage/baseclass/myaddress.py
Normal 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)
|
40
src/tests/mock/pybitmessage/baseclass/network.py
Normal file
40
src/tests/mock/pybitmessage/baseclass/network.py
Normal 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))
|
74
src/tests/mock/pybitmessage/baseclass/payment.py
Normal file
74
src/tests/mock/pybitmessage/baseclass/payment.py
Normal 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"""
|
230
src/tests/mock/pybitmessage/baseclass/popup.py
Normal file
230
src/tests/mock/pybitmessage/baseclass/popup.py
Normal 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"""
|
20
src/tests/mock/pybitmessage/baseclass/qrcode.py
Normal file
20
src/tests/mock/pybitmessage/baseclass/qrcode.py
Normal 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')
|
120
src/tests/mock/pybitmessage/baseclass/scan_screen.py
Normal file
120
src/tests/mock/pybitmessage/baseclass/scan_screen.py
Normal 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
|
234
src/tests/mock/pybitmessage/baseclass/sent.py
Normal file
234
src/tests/mock/pybitmessage/baseclass/sent.py
Normal 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())
|
91
src/tests/mock/pybitmessage/baseclass/settings.py
Normal file
91
src/tests/mock/pybitmessage/baseclass/settings.py
Normal 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')
|
183
src/tests/mock/pybitmessage/baseclass/trash.py
Normal file
183
src/tests/mock/pybitmessage/baseclass/trash.py
Normal 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)
|
273
src/tests/mock/pybitmessage/bmconfigparser.py
Normal file
273
src/tests/mock/pybitmessage/bmconfigparser.py
Normal 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
|
75
src/tests/mock/pybitmessage/class_addressGenerator.py
Normal file
75
src/tests/mock/pybitmessage/class_addressGenerator.py
Normal 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')
|
52
src/tests/mock/pybitmessage/class_objectProcessor.py
Normal file
52
src/tests/mock/pybitmessage/class_objectProcessor.py
Normal 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
|
217
src/tests/mock/pybitmessage/class_singleCleaner.py
Normal file
217
src/tests/mock/pybitmessage/class_singleCleaner.py
Normal 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
|
44
src/tests/mock/pybitmessage/class_singleWorker.py
Normal file
44
src/tests/mock/pybitmessage/class_singleWorker.py
Normal 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...")
|
144
src/tests/mock/pybitmessage/common.py
Normal file
144
src/tests/mock/pybitmessage/common.py
Normal 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)
|
49
src/tests/mock/pybitmessage/get_platform.py
Normal file
49
src/tests/mock/pybitmessage/get_platform.py
Normal 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"
|
72
src/tests/mock/pybitmessage/helper_random.py
Normal file
72
src/tests/mock/pybitmessage/helper_random.py
Normal 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
|
124
src/tests/mock/pybitmessage/helper_sql.py
Normal file
124
src/tests/mock/pybitmessage/helper_sql.py
Normal 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()
|
81
src/tests/mock/pybitmessage/identiconGeneration.py
Normal file
81
src/tests/mock/pybitmessage/identiconGeneration.py
Normal 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
|
15
src/tests/mock/pybitmessage/inventory.py
Normal file
15
src/tests/mock/pybitmessage/inventory.py
Normal 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
|
26
src/tests/mock/pybitmessage/kv/addressbook.kv
Normal file
26
src/tests/mock/pybitmessage/kv/addressbook.kv
Normal 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:
|
25
src/tests/mock/pybitmessage/kv/allmails.kv
Normal file
25
src/tests/mock/pybitmessage/kv/allmails.kv
Normal 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:
|
58
src/tests/mock/pybitmessage/kv/chat_list.kv
Normal file
58
src/tests/mock/pybitmessage/kv/chat_list.kv
Normal 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
|
45
src/tests/mock/pybitmessage/kv/chat_room.kv
Normal file
45
src/tests/mock/pybitmessage/kv/chat_room.kv
Normal 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()
|
62
src/tests/mock/pybitmessage/kv/common_widgets.kv
Normal file
62
src/tests/mock/pybitmessage/kv/common_widgets.kv
Normal 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
|
28
src/tests/mock/pybitmessage/kv/credits.kv
Normal file
28
src/tests/mock/pybitmessage/kv/credits.kv
Normal 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'
|
23
src/tests/mock/pybitmessage/kv/draft.kv
Normal file
23
src/tests/mock/pybitmessage/kv/draft.kv
Normal 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:
|
39
src/tests/mock/pybitmessage/kv/inbox.kv
Normal file
39
src/tests/mock/pybitmessage/kv/inbox.kv
Normal 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'
|
264
src/tests/mock/pybitmessage/kv/login.kv
Normal file
264
src/tests/mock/pybitmessage/kv/login.kv
Normal 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)
|
87
src/tests/mock/pybitmessage/kv/maildetail.kv
Normal file
87
src/tests/mock/pybitmessage/kv/maildetail.kv
Normal 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)
|
178
src/tests/mock/pybitmessage/kv/msg_composer.kv
Normal file
178
src/tests/mock/pybitmessage/kv/msg_composer.kv
Normal 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
|
33
src/tests/mock/pybitmessage/kv/myaddress.kv
Normal file
33
src/tests/mock/pybitmessage/kv/myaddress.kv
Normal 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)
|
131
src/tests/mock/pybitmessage/kv/network.kv
Normal file
131
src/tests/mock/pybitmessage/kv/network.kv
Normal 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'
|
253
src/tests/mock/pybitmessage/kv/payment.kv
Normal file
253
src/tests/mock/pybitmessage/kv/payment.kv
Normal 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"
|
333
src/tests/mock/pybitmessage/kv/popup.kv
Normal file
333
src/tests/mock/pybitmessage/kv/popup.kv
Normal 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'
|
33
src/tests/mock/pybitmessage/kv/qrcode.kv
Normal file
33
src/tests/mock/pybitmessage/kv/qrcode.kv
Normal 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}
|
2
src/tests/mock/pybitmessage/kv/scan_screen.kv
Normal file
2
src/tests/mock/pybitmessage/kv/scan_screen.kv
Normal file
|
@ -0,0 +1,2 @@
|
|||
<ScanScreen>:
|
||||
name:'scanscreen'
|
37
src/tests/mock/pybitmessage/kv/scanner.kv
Normal file
37
src/tests/mock/pybitmessage/kv/scanner.kv
Normal 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
|
||||
|
||||
|
26
src/tests/mock/pybitmessage/kv/sent.kv
Normal file
26
src/tests/mock/pybitmessage/kv/sent.kv
Normal 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:
|
964
src/tests/mock/pybitmessage/kv/settings.kv
Normal file
964
src/tests/mock/pybitmessage/kv/settings.kv
Normal 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:
|
25
src/tests/mock/pybitmessage/kv/trash.kv
Normal file
25
src/tests/mock/pybitmessage/kv/trash.kv
Normal 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:
|
422
src/tests/mock/pybitmessage/main.kv
Normal file
422
src/tests/mock/pybitmessage/main.kv
Normal 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]
|
28
src/tests/mock/pybitmessage/mpybit-backup.py
Normal file
28
src/tests/mock/pybitmessage/mpybit-backup.py
Normal 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()
|
1005
src/tests/mock/pybitmessage/mpybit.py
Normal file
1005
src/tests/mock/pybitmessage/mpybit.py
Normal 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"""
|
0
src/tests/mock/pybitmessage/network/__init__.py
Normal file
0
src/tests/mock/pybitmessage/network/__init__.py
Normal file
48
src/tests/mock/pybitmessage/network/addrthread.py
Normal file
48
src/tests/mock/pybitmessage/network/addrthread.py
Normal 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)
|
174
src/tests/mock/pybitmessage/network/advanceddispatcher.py
Normal file
174
src/tests/mock/pybitmessage/network/advanceddispatcher.py
Normal 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()
|
46
src/tests/mock/pybitmessage/network/announcethread.py
Normal file
46
src/tests/mock/pybitmessage/network/announcethread.py
Normal 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]))
|
32
src/tests/mock/pybitmessage/network/assemble.py
Normal file
32
src/tests/mock/pybitmessage/network/assemble.py
Normal 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
|
1012
src/tests/mock/pybitmessage/network/asyncore_pollchoose.py
Normal file
1012
src/tests/mock/pybitmessage/network/asyncore_pollchoose.py
Normal 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()
|
165
src/tests/mock/pybitmessage/network/bmobject.py
Normal file
165
src/tests/mock/pybitmessage/network/bmobject.py
Normal 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()
|
702
src/tests/mock/pybitmessage/network/bmproto.py
Normal file
702
src/tests/mock/pybitmessage/network/bmproto.py
Normal 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)
|
77
src/tests/mock/pybitmessage/network/connectionchooser.py
Normal file
77
src/tests/mock/pybitmessage/network/connectionchooser.py
Normal 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
|
419
src/tests/mock/pybitmessage/network/connectionpool.py
Normal file
419
src/tests/mock/pybitmessage/network/connectionpool.py
Normal 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)
|
17
src/tests/mock/pybitmessage/network/constants.py
Normal file
17
src/tests/mock/pybitmessage/network/constants.py
Normal 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
|
204
src/tests/mock/pybitmessage/network/dandelion.py
Normal file
204
src/tests/mock/pybitmessage/network/dandelion.py
Normal 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
|
84
src/tests/mock/pybitmessage/network/downloadthread.py
Normal file
84
src/tests/mock/pybitmessage/network/downloadthread.py
Normal 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)
|
90
src/tests/mock/pybitmessage/network/http.py
Normal file
90
src/tests/mock/pybitmessage/network/http.py
Normal 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)
|
55
src/tests/mock/pybitmessage/network/http_old.py
Normal file
55
src/tests/mock/pybitmessage/network/http_old.py
Normal 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))
|
155
src/tests/mock/pybitmessage/network/httpd.py
Normal file
155
src/tests/mock/pybitmessage/network/httpd.py
Normal 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()
|
72
src/tests/mock/pybitmessage/network/https.py
Normal file
72
src/tests/mock/pybitmessage/network/https.py
Normal 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()
|
110
src/tests/mock/pybitmessage/network/invthread.py
Normal file
110
src/tests/mock/pybitmessage/network/invthread.py
Normal 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)
|
42
src/tests/mock/pybitmessage/network/networkthread.py
Normal file
42
src/tests/mock/pybitmessage/network/networkthread.py
Normal 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()
|
7
src/tests/mock/pybitmessage/network/node.py
Normal file
7
src/tests/mock/pybitmessage/network/node.py
Normal 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'])
|
137
src/tests/mock/pybitmessage/network/objectracker.py
Normal file
137
src/tests/mock/pybitmessage/network/objectracker.py
Normal 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)
|
151
src/tests/mock/pybitmessage/network/proxy.py
Normal file
151
src/tests/mock/pybitmessage/network/proxy.py
Normal 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
|
168
src/tests/mock/pybitmessage/network/randomtrackingdict.py
Normal file
168
src/tests/mock/pybitmessage/network/randomtrackingdict.py
Normal 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]))
|
56
src/tests/mock/pybitmessage/network/receivequeuethread.py
Normal file
56
src/tests/mock/pybitmessage/network/receivequeuethread.py
Normal 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()
|
143
src/tests/mock/pybitmessage/network/socks4a.py
Normal file
143
src/tests/mock/pybitmessage/network/socks4a.py
Normal 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()))
|
222
src/tests/mock/pybitmessage/network/socks5.py
Normal file
222
src/tests/mock/pybitmessage/network/socks5.py
Normal 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()))
|
71
src/tests/mock/pybitmessage/network/stats.py
Normal file
71
src/tests/mock/pybitmessage/network/stats.py
Normal 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
|
433
src/tests/mock/pybitmessage/network/tcp.py
Normal file
433
src/tests/mock/pybitmessage/network/tcp.py
Normal 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
|
33
src/tests/mock/pybitmessage/network/threads.py
Normal file
33
src/tests/mock/pybitmessage/network/threads.py
Normal 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
|
241
src/tests/mock/pybitmessage/network/tls.py
Normal file
241
src/tests/mock/pybitmessage/network/tls.py
Normal 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
|
152
src/tests/mock/pybitmessage/network/udp.py
Normal file
152
src/tests/mock/pybitmessage/network/udp.py
Normal 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)
|
71
src/tests/mock/pybitmessage/network/uploadthread.py
Normal file
71
src/tests/mock/pybitmessage/network/uploadthread.py
Normal 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)
|
55
src/tests/mock/pybitmessage/queues.py
Normal file
55
src/tests/mock/pybitmessage/queues.py
Normal 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()
|
78
src/tests/mock/pybitmessage/screens_data.json
Normal file
78
src/tests/mock/pybitmessage/screens_data.json
Normal 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",
|
||||
},
|
||||
}
|
3
src/tests/mock/pybitmessage/semaphores.py
Normal file
3
src/tests/mock/pybitmessage/semaphores.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from threading import Semaphore
|
||||
|
||||
kivyuisignaler = Semaphore(0)
|
254
src/tests/mock/pybitmessage/shared.py
Normal file
254
src/tests/mock/pybitmessage/shared.py
Normal 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
|
22
src/tests/mock/pybitmessage/singleton.py
Normal file
22
src/tests/mock/pybitmessage/singleton.py
Normal 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
|
72
src/tests/mock/pybitmessage/state.py
Normal file
72
src/tests/mock/pybitmessage/state.py
Normal 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"""
|
34
src/tests/mock/pybitmessage/uikivysignaler.py
Normal file
34
src/tests/mock/pybitmessage/uikivysignaler.py
Normal 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)
|
Reference in New Issue
Block a user