From b393cfc98d306897b509266f53b37ed772654875 Mon Sep 17 00:00:00 2001 From: surbhicis Date: Thu, 23 Dec 2021 12:33:14 +0530 Subject: [PATCH 001/424] update sdk and ant compatible version --- buildscripts/androiddev.sh | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) mode change 100644 => 100755 buildscripts/androiddev.sh diff --git a/buildscripts/androiddev.sh b/buildscripts/androiddev.sh old mode 100644 new mode 100755 index c035fea0..1634d4c0 --- a/buildscripts/androiddev.sh +++ b/buildscripts/androiddev.sh @@ -58,23 +58,14 @@ install_ndk() } # INSTALL SDK -function install_sdk() +install_sdk() { - if [[ "$get_python_version" -eq " 2 " ]]; - then - ANDROID_SDK_BUILD_TOOLS_VERSION="28.0.3" - elif [[ "$get_python_version" -eq " 3 " ]]; - then - ANDROID_SDK_BUILD_TOOLS_VERSION="29.0.2" - else - exit - fi + ANDROID_SDK_BUILD_TOOLS_VERSION="29.0.2" ANDROID_SDK_HOME="${ANDROID_HOME}/android-sdk" # get the latest version from https://developer.android.com/studio/index.html ANDROID_SDK_TOOLS_VERSION="4333796" ANDROID_SDK_TOOLS_ARCHIVE="sdk-tools-linux-${ANDROID_SDK_TOOLS_VERSION}.zip" ANDROID_SDK_TOOLS_DL_URL="https://dl.google.com/android/repository/${ANDROID_SDK_TOOLS_ARCHIVE}" - echo "Downloading sdk.........................................................................." wget -nc ${ANDROID_SDK_TOOLS_DL_URL} mkdir --parents "${ANDROID_SDK_HOME}" unzip -q "${ANDROID_SDK_TOOLS_ARCHIVE}" -d "${ANDROID_SDK_HOME}" @@ -84,7 +75,7 @@ function install_sdk() echo '### Sources for Android SDK Manager' > "${ANDROID_SDK_HOME}/.android/repositories.cfg" # accept Android licenses (JDK necessary!) apt -y update -qq - apt -y install -qq --no-install-recommends openjdk-8-jdk + apt -y install -qq --no-install-recommends openjdk-11-jdk apt -y autoremove yes | "${ANDROID_SDK_HOME}/tools/bin/sdkmanager" "build-tools;${ANDROID_SDK_BUILD_TOOLS_VERSION}" > /dev/null # download platforms, API, build tools @@ -98,23 +89,14 @@ function install_sdk() } # INSTALL APACHE-ANT -function install_ant() +install_ant() { - if [[ "$get_python_version" -eq " 2 " ]]; - then - APACHE_ANT_VERSION="1.9.4" - elif [[ "$get_python_version" -eq " 3 " ]]; - then - APACHE_ANT_VERSION="1.10.7" - else - exit - fi + APACHE_ANT_VERSION="1.10.12" APACHE_ANT_ARCHIVE="apache-ant-${APACHE_ANT_VERSION}-bin.tar.gz" APACHE_ANT_DL_URL="http://archive.apache.org/dist/ant/binaries/${APACHE_ANT_ARCHIVE}" APACHE_ANT_HOME="${ANDROID_HOME}/apache-ant" APACHE_ANT_HOME_V="${APACHE_ANT_HOME}-${APACHE_ANT_VERSION}" - echo "Downloading ant.........................................................................." wget -nc ${APACHE_ANT_DL_URL} tar -xf "${APACHE_ANT_ARCHIVE}" -C "${ANDROID_HOME}" ln -sfn "${APACHE_ANT_HOME_V}" "${APACHE_ANT_HOME}" From 97612b049e0453867d6d90aa628f8e7b007b4d85 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Tue, 11 Jan 2022 20:15:42 +0530 Subject: [PATCH 002/424] refactored state.py --- src/state.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/state.py b/src/state.py index be81992d..ee39e094 100644 --- a/src/state.py +++ b/src/state.py @@ -51,6 +51,10 @@ discoveredPeers = {} dandelion = 0 +kivy = False + +kivyapp = None + testmode = False clientHasReceivedIncomingConnections = False From 584ea0f6a040def99af2d0daace8345a4f6eec5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marius=20Kj=C3=A6rstad?= Date: Mon, 17 Jan 2022 18:11:23 +0100 Subject: [PATCH 003/424] Changed copyright year to 2022 --- COPYING | 2 +- LICENSE | 4 ++-- src/api.py | 2 +- src/bitmessagemain.py | 2 +- src/bitmessageqt/about.ui | 2 +- src/bitmessageqt/dialogs.py | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/COPYING b/COPYING index 078bf213..279cef2a 100644 --- a/COPYING +++ b/COPYING @@ -1,5 +1,5 @@ Copyright (c) 2012-2016 Jonathan Warren -Copyright (c) 2012-2020 The Bitmessage Developers +Copyright (c) 2012-2022 The Bitmessage Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/LICENSE b/LICENSE index c2eeff82..fd772201 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) Copyright (c) 2012-2016 Jonathan Warren -Copyright (c) 2012-2020 The Bitmessage Developers +Copyright (c) 2012-2022 The Bitmessage Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -91,4 +91,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/src/api.py b/src/api.py index de220cc4..9cab1cfe 100644 --- a/src/api.py +++ b/src/api.py @@ -1,5 +1,5 @@ # Copyright (c) 2012-2016 Jonathan Warren -# Copyright (c) 2012-2020 The Bitmessage developers +# Copyright (c) 2012-2022 The Bitmessage developers """ This is not what you run to start the Bitmessage API. diff --git a/src/bitmessagemain.py b/src/bitmessagemain.py index 84313ab9..e60813b3 100755 --- a/src/bitmessagemain.py +++ b/src/bitmessagemain.py @@ -3,7 +3,7 @@ The PyBitmessage startup script """ # Copyright (c) 2012-2016 Jonathan Warren -# Copyright (c) 2012-2020 The Bitmessage developers +# Copyright (c) 2012-2022 The Bitmessage developers # Distributed under the MIT/X11 software license. See the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. diff --git a/src/bitmessageqt/about.ui b/src/bitmessageqt/about.ui index 7073bbd1..49bd4eca 100644 --- a/src/bitmessageqt/about.ui +++ b/src/bitmessageqt/about.ui @@ -46,7 +46,7 @@ - <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2012-2020 The Bitmessage Developers</p></body></html> + <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2012-2022 The Bitmessage Developers</p></body></html> Qt::AlignLeft diff --git a/src/bitmessageqt/dialogs.py b/src/bitmessageqt/dialogs.py index e76dd84e..dc31e266 100644 --- a/src/bitmessageqt/dialogs.py +++ b/src/bitmessageqt/dialogs.py @@ -45,7 +45,7 @@ class AboutDialog(QtGui.QDialog): try: self.label_2.setText( self.label_2.text().replace( - '2020', str(last_commit.get('time').year) + '2022', str(last_commit.get('time').year) )) except AttributeError: pass From 21691de95d1755d4c0a139c09269ffb5809ffd24 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Thu, 6 Jan 2022 17:32:07 +0530 Subject: [PATCH 004/424] Updated queues --- src/queues.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/queues.py b/src/queues.py index 4a9b98d2..d86d36fa 100644 --- a/src/queues.py +++ b/src/queues.py @@ -8,7 +8,13 @@ from six.moves import queue try: from multiqueue import MultiQueue except ImportError: - from .multiqueue import MultiQueue + try: + from .multiqueue import MultiQueue + except ImportError: + import sys + if 'bitmessagemock' not in sys.modules: + raise + MultiQueue = queue.Queue class ObjectProcessorQueue(queue.Queue): From 9c872ef676cafdebf07244a9dbd6e00a29154bef Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Tue, 4 Jan 2022 21:04:00 +0530 Subject: [PATCH 005/424] Updated kivy dependency xclip --- .travis-kivy.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis-kivy.yml b/.travis-kivy.yml index 46ef7963..49c99f4d 100644 --- a/.travis-kivy.yml +++ b/.travis-kivy.yml @@ -10,6 +10,7 @@ addons: - libcap-dev - libmtdev-dev - xvfb + - xclip install: - pip3 install -r kivy-requirements.txt - python3 setup.py install From 153b4dc1b242cf2df6970dc74a42ee0587a8cf57 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Mon, 24 Jan 2022 12:52:34 +0530 Subject: [PATCH 006/424] KV directory added --- src/bitmessagekivy/kv/addressbook.kv | 26 + src/bitmessagekivy/kv/allmails.kv | 25 + src/bitmessagekivy/kv/chat_list.kv | 58 ++ src/bitmessagekivy/kv/chat_room.kv | 45 ++ src/bitmessagekivy/kv/common_widgets.kv | 62 ++ src/bitmessagekivy/kv/credits.kv | 28 + src/bitmessagekivy/kv/draft.kv | 23 + src/bitmessagekivy/kv/inbox.kv | 39 + src/bitmessagekivy/kv/login.kv | 264 +++++++ src/bitmessagekivy/kv/maildetail.kv | 87 +++ src/bitmessagekivy/kv/msg_composer.kv | 178 +++++ src/bitmessagekivy/kv/myaddress.kv | 33 + src/bitmessagekivy/kv/network.kv | 131 ++++ src/bitmessagekivy/kv/payment.kv | 253 +++++++ src/bitmessagekivy/kv/popup.kv | 333 ++++++++ src/bitmessagekivy/kv/qrcode.kv | 33 + src/bitmessagekivy/kv/scan_screen.kv | 2 + src/bitmessagekivy/kv/scanner.kv | 37 + src/bitmessagekivy/kv/sent.kv | 26 + src/bitmessagekivy/kv/settings.kv | 964 ++++++++++++++++++++++++ src/bitmessagekivy/kv/trash.kv | 25 + 21 files changed, 2672 insertions(+) create mode 100644 src/bitmessagekivy/kv/addressbook.kv create mode 100644 src/bitmessagekivy/kv/allmails.kv create mode 100644 src/bitmessagekivy/kv/chat_list.kv create mode 100644 src/bitmessagekivy/kv/chat_room.kv create mode 100644 src/bitmessagekivy/kv/common_widgets.kv create mode 100644 src/bitmessagekivy/kv/credits.kv create mode 100644 src/bitmessagekivy/kv/draft.kv create mode 100644 src/bitmessagekivy/kv/inbox.kv create mode 100644 src/bitmessagekivy/kv/login.kv create mode 100644 src/bitmessagekivy/kv/maildetail.kv create mode 100644 src/bitmessagekivy/kv/msg_composer.kv create mode 100644 src/bitmessagekivy/kv/myaddress.kv create mode 100644 src/bitmessagekivy/kv/network.kv create mode 100644 src/bitmessagekivy/kv/payment.kv create mode 100644 src/bitmessagekivy/kv/popup.kv create mode 100644 src/bitmessagekivy/kv/qrcode.kv create mode 100644 src/bitmessagekivy/kv/scan_screen.kv create mode 100644 src/bitmessagekivy/kv/scanner.kv create mode 100644 src/bitmessagekivy/kv/sent.kv create mode 100644 src/bitmessagekivy/kv/settings.kv create mode 100644 src/bitmessagekivy/kv/trash.kv diff --git a/src/bitmessagekivy/kv/addressbook.kv b/src/bitmessagekivy/kv/addressbook.kv new file mode 100644 index 00000000..73b4c1ef --- /dev/null +++ b/src/bitmessagekivy/kv/addressbook.kv @@ -0,0 +1,26 @@ +: + name: 'addressbook' + BoxLayout: + orientation: 'vertical' + spacing: dp(5) + SearchBar: + id: address_search + GridLayout: + id: identi_tag + padding: [20, 0, 0, 5] + cols: 1 + size_hint_y: None + height: self.minimum_height + MDLabel: + id: tag_label + text: '' + font_style: 'Subtitle2' + BoxLayout: + orientation:'vertical' + ScrollView: + id: scroll_y + do_scroll_x: False + MDList: + id: ml + Loader: + ComposerButton: \ No newline at end of file diff --git a/src/bitmessagekivy/kv/allmails.kv b/src/bitmessagekivy/kv/allmails.kv new file mode 100644 index 00000000..f1b9387e --- /dev/null +++ b/src/bitmessagekivy/kv/allmails.kv @@ -0,0 +1,25 @@ +: + name: 'allmails' + BoxLayout: + orientation: 'vertical' + spacing: dp(5) + GridLayout: + id: identi_tag + padding: [20, 20, 0, 5] + spacing: dp(5) + cols: 1 + size_hint_y: None + height: self.minimum_height + MDLabel: + id: tag_label + text: '' + font_style: 'Subtitle2' + BoxLayout: + orientation:'vertical' + ScrollView: + id: scroll_y + do_scroll_x: False + MDList: + id: ml + Loader: + ComposerButton: \ No newline at end of file diff --git a/src/bitmessagekivy/kv/chat_list.kv b/src/bitmessagekivy/kv/chat_list.kv new file mode 100644 index 00000000..e59c32d7 --- /dev/null +++ b/src/bitmessagekivy/kv/chat_list.kv @@ -0,0 +1,58 @@ +: + name: 'chlist' + canvas.before: + Color: + rgba: 1,1,1,1 + Rectangle: + pos: self.pos + size: self.size + MDTabs: + id: chat_panel + tab_display_mode:'text' + + Tab: + text: app.tr._("Chats") + BoxLayout: + id: chat_box + orientation: 'vertical' + ScrollView: + id: scroll_y + do_scroll_x: False + MDList: + id: ml + MDLabel: + font_style: 'Caption' + theme_text_color: 'Primary' + text: app.tr._('No Chat') + halign: 'center' + size_hint_y: None + bold: True + valign: 'top' + # OneLineAvatarListItem: + # text: "Single-line item with avatar" + # divider: None + # _no_ripple_effect: True + # ImageLeftWidget: + # source: './images/text_images/A.png' + # OneLineAvatarListItem: + # text: "Single-line item with avatar" + # divider: None + # _no_ripple_effect: True + # ImageLeftWidget: + # source: './images/text_images/B.png' + # OneLineAvatarListItem: + # text: "Single-line item with avatar" + # divider: None + # _no_ripple_effect: True + # ImageLeftWidget: + # source: './images/text_images/A.png' + Tab: + text: app.tr._("Contacts") + BoxLayout: + id: contact_box + orientation: 'vertical' + ScrollView: + id: scroll_y + do_scroll_x: False + MDList: + id: ml diff --git a/src/bitmessagekivy/kv/chat_room.kv b/src/bitmessagekivy/kv/chat_room.kv new file mode 100644 index 00000000..40843c47 --- /dev/null +++ b/src/bitmessagekivy/kv/chat_room.kv @@ -0,0 +1,45 @@ +#:import C kivy.utils.get_color_from_hex + +: + name: 'chroom' + BoxLayout: + orientation: 'vertical' + canvas.before: + Color: + rgba: 1,1,1,1 + Rectangle: + pos: self.pos + size: self.size + ScrollView: + Label: + id: chat_logs + text: '' + color: C('#101010') + text_size: (self.width, None) + halign: 'left' + valign: 'top' + padding: (0, 0) # fixed in Kivy 1.8.1 + size_hint: (1, None) + height: self.texture_size[1] + markup: True + font_size: sp(20) + BoxLayout: + height: 50 + orientation: 'horizontal' + padding: 0 + size_hint: (1, None) + + TextInput: + id: message + size_hint: (1, 1) + multiline: False + font_size: sp(20) + on_text_validate: root.send_msg() + + MDRaisedButton: + text: app.tr._("Send") + elevation_normal: 2 + opposite_colors: True + size_hint: (0.3, 1) + pos_hint: {"center_x": .5} + on_press: root.send_msg() diff --git a/src/bitmessagekivy/kv/common_widgets.kv b/src/bitmessagekivy/kv/common_widgets.kv new file mode 100644 index 00000000..792fef1e --- /dev/null +++ b/src/bitmessagekivy/kv/common_widgets.kv @@ -0,0 +1,62 @@ +: + source: app.image_path +('/down-arrow.png' if self.parent.is_open == True else '/right-arrow.png') + size: 15, 15 + x: self.parent.x + self.parent.width - self.width - 5 + y: self.parent.y + self.parent.height/2 - self.height + 5 + +: + # id: search_bar + size_hint_y: None + height: self.minimum_height + + MDIconButton: + icon: 'magnify' + + MDTextField: + id: search_field + hint_text: 'Search' + on_text: app.searchQuery(self) + canvas.before: + Color: + rgba: (0,0,0,1) + +: + id: spinner + size_hint: None, None + size: dp(46), dp(46) + pos_hint: {'center_x': 0.5, 'center_y': 0.5} + active: False + +: + size_hint_y: None + height: dp(56) + spacing: '10dp' + pos_hint: {'center_x':0.45, 'center_y': .1} + + Widget: + + MDFloatingActionButton: + icon: 'plus' + opposite_colors: True + elevation_normal: 8 + md_bg_color: [0.941, 0, 0,1] + on_press: app.root.ids.scr_mngr.current = 'create' + on_press: app.clear_composer() + + + +: + size_hint: None, None + size: dp(36), dp(48) + pos_hint: {'center_x': .95, 'center_y': .4} + on_active: app.root.ids.sc10.toggleAction(self) + +: + canvas: + Color: + id: set_clr + # rgba: 0.5, 0.5, 0.5, 0.5 + rgba: 0,0,0,0 + Rectangle: #woohoo!!! + size: self.size + pos: self.pos \ No newline at end of file diff --git a/src/bitmessagekivy/kv/credits.kv b/src/bitmessagekivy/kv/credits.kv new file mode 100644 index 00000000..b5eb3db7 --- /dev/null +++ b/src/bitmessagekivy/kv/credits.kv @@ -0,0 +1,28 @@ +: + 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' diff --git a/src/bitmessagekivy/kv/draft.kv b/src/bitmessagekivy/kv/draft.kv new file mode 100644 index 00000000..56682d2b --- /dev/null +++ b/src/bitmessagekivy/kv/draft.kv @@ -0,0 +1,23 @@ +: + name: 'draft' + BoxLayout: + orientation: 'vertical' + spacing: dp(5) + GridLayout: + id: identi_tag + padding: [20, 20, 0, 5] + cols: 1 + size_hint_y: None + height: self.minimum_height + MDLabel: + id: tag_label + text: '' + font_style: 'Subtitle2' + BoxLayout: + orientation:'vertical' + ScrollView: + id: scroll_y + do_scroll_x: False + MDList: + id: ml + ComposerButton: \ No newline at end of file diff --git a/src/bitmessagekivy/kv/inbox.kv b/src/bitmessagekivy/kv/inbox.kv new file mode 100644 index 00000000..b9cc8566 --- /dev/null +++ b/src/bitmessagekivy/kv/inbox.kv @@ -0,0 +1,39 @@ +: + name: 'inbox' + #transition: NoTransition() + BoxLayout: + orientation: 'vertical' + spacing: dp(5) + SearchBar: + id:inbox_search + GridLayout: + id: identi_tag + padding: [20, 0, 0, 5] + cols: 1 + size_hint_y: None + height: self.minimum_height + MDLabel: + id: tag_label + text: '' + font_style: 'Subtitle2' + #FloatLayout: + # MDScrollViewRefreshLayout: + # id: refresh_layout + # refresh_callback: root.refresh_callback + # root_layout: root.set_root_layout() + # MDList: + # id: ml + BoxLayout: + orientation:'vertical' + ScrollView: + id: scroll_y + do_scroll_x: False + MDList: + id: ml + Loader: + ComposerButton: + +: + size_hint:(None, None) + font_style: 'Caption' + halign: 'center' diff --git a/src/bitmessagekivy/kv/login.kv b/src/bitmessagekivy/kv/login.kv new file mode 100644 index 00000000..44e24c04 --- /dev/null +++ b/src/bitmessagekivy/kv/login.kv @@ -0,0 +1,264 @@ +#:import SlideTransition kivy.uix.screenmanager.SlideTransition +: + name:"login" + BoxLayout: + orientation: "vertical" + + #buttons-area-outer + BoxLayout: + size_hint_y: .53 + canvas: + Color: + rgba: 1,1,1,1 + Rectangle: + pos: self.pos + size: self.size + + ScreenManager: + id: check_screenmgr + Screen: + name: "check_screen" + BoxLayout: + orientation: "vertical" + padding: 0, dp(5), 0, dp(5) + spacing: dp(5) + + #label area + AnchorLayout: + size_hint_y: None + height: dp(50) + MDLabel: + text: app.tr._("Select method to make an address:") + bold: True + halign: "center" + theme_text_color: "Custom" + text_color: .4,.4,.4,1 + + #upper-checkbor-area + AnchorLayout: + size_hint_y: None + height: dp(40) + BoxLayout: + size_hint_x: None + width: self.minimum_width + + #check-container + AnchorLayout: + size_hint_x: None + width: dp(40) + Check: + active: True + + #text-container + AnchorLayout: + size_hint_x: None + width: dp(200) + MDLabel: + text: app.tr._("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.") + +: + name:"random" + ScrollView: + id:add_random_bx + +: + orientation: "vertical" + #buttons-area-outer + BoxLayout: + orientation: "vertical" + # padding: 0, dp(5), 0, dp(5) + # spacing: dp(5) + size_hint_y: .53 + canvas: + Color: + rgba: 1,1,1,1 + Rectangle: + pos: self.pos + size: self.size + + #label area + AnchorLayout: + size_hint_y: None + height: dp(50) + MDLabel: + text: app.tr._("Enter a label to generate address for:") + bold: True + halign: "center" + theme_text_color: "Custom" + text_color: .4,.4,.4,1 + + AnchorLayout: + size_hint_y: None + height: dp(40) + MDTextField: + id:lab + hint_text: "Label" + required: True + size_hint_x: None + width: dp(190) + helper_text_mode: "on_error" + # helper_text: "Please enter your label name" + on_text: app.root.ids.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.") + +: + group: 'group' + size_hint: None, None + size: dp(48), dp(48) + +: + section_name: "" + orientation: "vertical" + size_hint_y: None + height: dp(50) + padding: dp(20), 0, 0, 0 + Widget: + size_hint_y: None + height: dp(25) + MDLabel: + theme_text_color: "Custom" + text_color: .1,.1,.1,.9 + text: app.tr._(root.section_name) + bold: True + font_style: "Button" + +: + section_text: "" + size_hint_y: None + height: self.minimum_height + padding: dp(50), 0, dp(10), 0 + + MDLabel: + size_hint_y: None + height: self.texture_size[1]+dp(10) + theme_text_color: "Custom" + text_color: 0.3,0.3,0.3,1 + font_style: "Body1" + text: app.tr._(root.section_text) diff --git a/src/bitmessagekivy/kv/maildetail.kv b/src/bitmessagekivy/kv/maildetail.kv new file mode 100644 index 00000000..e98b8661 --- /dev/null +++ b/src/bitmessagekivy/kv/maildetail.kv @@ -0,0 +1,87 @@ +: + name: 'mailDetail' + ScrollView: + do_scroll_x: False + BoxLayout: + size_hint_y: None + orientation: 'vertical' + # height: dp(bod.height) + self.minimum_height + height: self.minimum_height + padding: dp(10) + # MDLabel: + # size_hint_y: None + # id: subj + # text: root.subject + # theme_text_color: 'Primary' + # halign: 'left' + # font_style: 'H5' + # height: dp(40) + # on_touch_down: root.allclick(self) + OneLineListTitle: + id: subj + text: app.tr._(root.subject) + divider: None + font_style: 'H5' + theme_text_color: 'Primary' + _no_ripple_effect: True + long_press_time: 1 + TwoLineAvatarIconListItem: + id: subaft + text: app.tr._(root.from_addr) + secondary_text: app.tr._('to ' + root.to_addr) + divider: None + on_press: root.detailedPopup() + BadgeText: + size_hint:(None, None) + size:[120, 140] if app.app_platform == 'android' else [64, 80] + text: app.tr._(root.time_tag) + halign:'center' + font_style:'Caption' + pos_hint: {'center_y': .8} + _txt_right_pad: dp(70) + font_size: '11sp' + MDChip: + size_hint: (.16 if app.app_platform == 'android' else .08 , None) + text: app.tr._(root.page_type) + icon: '' + text_color: (1,1,1,1) + pos_hint: {'center_x': .91 if app.app_platform == 'android' else .95, 'center_y': .3} + radius: [8] + height: self.parent.height/4 + AvatarSampleWidget: + source: root.avatarImg + MDLabel: + text: root.status + disabled: True + font_style: 'Body2' + halign:'left' + padding_x: 20 + # MDLabel: + # id: bod + # font_style: 'Subtitle2' + # theme_text_color: 'Primary' + # text: root.message + # halign: 'left' + # height: self.texture_size[1] + MyMDTextField: + id: bod + size_hint_y: None + font_style: 'Subtitle2' + text: root.message + multiline: True + readonly: True + line_color_normal: [0,0,0,0] + _current_line_color: [0,0,0,0] + line_color_focus: [0,0,0,0] + markup: True + font_size: '15sp' + canvas.before: + Color: + rgba: (0,0,0,1) + Loader: + + +: + canvas.before: + Color: + rgba: (0,0,0,1) diff --git a/src/bitmessagekivy/kv/msg_composer.kv b/src/bitmessagekivy/kv/msg_composer.kv new file mode 100644 index 00000000..82a2a8cb --- /dev/null +++ b/src/bitmessagekivy/kv/msg_composer.kv @@ -0,0 +1,178 @@ +: + name: 'create' + Loader: + + +: + ScrollView: + id: id_scroll + BoxLayout: + orientation: 'vertical' + size_hint_y: None + height: self.minimum_height + 3 * self.parent.height/5 + padding: dp(20) + spacing: 15 + BoxLayout: + orientation: 'vertical' + MDTextField: + id: ti + size_hint_y: None + hint_text: 'Type or Select sender address' + icon_right: 'account' + icon_right_color: app.theme_cls.primary_light + font_size: '15sp' + multiline: False + required: True + # height: 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 + +: + readonly: False + multiline: False + + +: + # Draw a background to indicate selection + color: 0,0,0,1 + canvas.before: + Color: + rgba: app.theme_cls.primary_dark if self.selected else (1, 1, 1, 0) + Rectangle: + pos: self.pos + size: self.size + +: + canvas: + Color: + rgba: 0,0,0,.2 + + Line: + rectangle: self.x +1 , self.y, self.width - 2, self.height -2 + bar_width: 10 + scroll_type:['bars'] + viewclass: 'SelectableLabel' + SelectableRecycleBoxLayout: + default_size: None, dp(20) + default_size_hint: 1, None + size_hint_y: None + height: self.minimum_height + orientation: 'vertical' + multiselect: False + + +: + canvas.before: + Color: + rgba: (0,0,0,1) + + + +: + font_size: '13.5sp' + #background_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 \ No newline at end of file diff --git a/src/bitmessagekivy/kv/myaddress.kv b/src/bitmessagekivy/kv/myaddress.kv new file mode 100644 index 00000000..93496eeb --- /dev/null +++ b/src/bitmessagekivy/kv/myaddress.kv @@ -0,0 +1,33 @@ +: + name: 'myaddress' + BoxLayout: + id: main_box + orientation: 'vertical' + spacing: dp(5) + SearchBar: + id: search_bar + GridLayout: + id: identi_tag + padding: [20, 0, 0, 5] + cols: 1 + size_hint_y: None + height: self.minimum_height + MDLabel: + id: tag_label + text: app.tr._('My Addresses') + font_style: 'Subtitle2' + FloatLayout: + MDScrollViewRefreshLayout: + id: refresh_layout + refresh_callback: root.refresh_callback + root_layout: root + MDList: + id: ml + Loader: + + +: + size_hint: None, None + size: dp(36), dp(48) + pos_hint: {'center_x': .95, 'center_y': .4} + on_active: app.root.ids.sc10.toggleAction(self) \ No newline at end of file diff --git a/src/bitmessagekivy/kv/network.kv b/src/bitmessagekivy/kv/network.kv new file mode 100644 index 00000000..b77dea26 --- /dev/null +++ b/src/bitmessagekivy/kv/network.kv @@ -0,0 +1,131 @@ +: + 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' diff --git a/src/bitmessagekivy/kv/payment.kv b/src/bitmessagekivy/kv/payment.kv new file mode 100644 index 00000000..542e8e0e --- /dev/null +++ b/src/bitmessagekivy/kv/payment.kv @@ -0,0 +1,253 @@ +#:import get_color_from_hex kivy.utils.get_color_from_hex + +: + 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" + +: + size_hint_y: None + height: self.minimum_height + category_text:"" + + orientation: "vertical" + spacing: 2 + + #category area + Category: + text_: root.category_text + +: + canvas: + Color: + rgba: 1,1,1,1 + Rectangle: + pos: self.pos + size: self.size + text_: "" + size_hint_y: None + height: dp(30) + Widget: + size_hint_x: None + width: dp(20) + MDLabel: + text: root.text_ + font_size: sp(15) + +: + heading_text: "" + price_text: "" + source: "" + description_text: "" + + product_id: "" + + canvas: + Color: + rgba: 1,1,1,1 + Rectangle: + pos: self.pos + size: self.size + + size_hint_y: None + height: dp(200) + orientation: "vertical" + + #heading area + BoxLayout: + size_hint_y: 0.3 + + #text heading + BoxLayout: + Widget: + size_hint_x: None + width: dp(20) + MDLabel: + text: root.heading_text + bold: True + + #price text + BoxLayout: + size_hint_x:.3 + MDLabel: + text: root.price_text + bold: True + halign: "right" + theme_text_color: "Custom" + text_color: 0,0,1,1 + Widget: + size_hint_x: None + width: dp(20) + + #details area + BoxLayout: + size_hint_y: 0.3 + Widget: + size_hint_x: None + width: dp(20) + + #image area + AnchorLayout: + size_hint_x: None + width: self.height + BoxLayout: + canvas: + Color: + rgba: 1,1,1,1 + Ellipse: + size: self.size + pos: self.pos + source: root.source + Widget: + size_hint_x: None + width: dp(10) + + #description text + BoxLayout: + #size_hint_x: 1 + MDLabel: + text: root.description_text + font_size: sp(15) + + #Button Area + BoxLayout: + size_hint_y: 0.4 + Widget: + + AnchorLayout: + anchor_x: "right" + MDRaisedButton: + elevation_normal: 5 + text: "BUY" + on_release: + #print(app) + app.open_payment_layout(root.product_id) + + Widget: + size_hint_x: None + width: dp(20) + +: + on_release: app.initiate_purchase(self.method_name) + recent: False + source: "" + method_name: "" + right_label_text: "Recent" if self.recent else "" + + ImageLeftWidget: + source: root.source + + RightLabel: + text: root.right_label_text + theme_text_color: "Custom" + text_color: 0,0,0,.4 + font_size: sp(12) + +: + orientation: "vertical" + size_hint_y: None + height: "200dp" + + BoxLayout: + size_hint_y: None + height: dp(40) + + Widget: + size_hint_x: None + width: dp(20) + MDLabel: + text: "Select Payment Method" + font_size: sp(14) + bold: True + theme_text_color: "Custom" + text_color: 0,0,0,.5 + + + ScrollView: + + GridLayout: + cols: 1 + size_hint_y:None + height:self.minimum_height + + ListItemWithLabel: + source: app.image_path + "/payment/gplay.png" + text: "Google Play" + method_name: "gplay" + recent: True + + ListItemWithLabel: + source: app.image_path + "/payment/btc.png" + text: "BTC" + 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" \ No newline at end of file diff --git a/src/bitmessagekivy/kv/popup.kv b/src/bitmessagekivy/kv/popup.kv new file mode 100644 index 00000000..217d9131 --- /dev/null +++ b/src/bitmessagekivy/kv/popup.kv @@ -0,0 +1,333 @@ +: + 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 + + +: + 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) + +: + id: addbook_popup_box + size_hint_y: None + height: 2.5*(add_label.height) + orientation: 'vertical' + spacing:dp(5) + MDLabel + font_style: 'Subtitle2' + theme_text_color: 'Primary' + text: app.tr._("Label") + font_size: '17sp' + halign: 'left' + MDTextField: + id: add_label + font_style: 'Body1' + font_size: '15sp' + halign: 'left' + text: app.tr._(root.address_label) + theme_text_color: 'Primary' + required: True + helper_text_mode: "on_error" + on_text: root.checkLabel_valid(self) + canvas.before: + Color: + rgba: (0,0,0,1) + MDLabel: + font_style: 'Subtitle2' + theme_text_color: 'Primary' + text: app.tr._("Address") + font_size: '17sp' + halign: 'left' + Widget: + size_hint_y: None + height: dp(1) + BoxLayout: + orientation: 'horizontal' + MDLabel: + id: address + font_style: 'Body1' + theme_text_color: 'Primary' + text: app.tr._(root.address) + font_size: '15sp' + halign: 'left' + IconRightSampleWidget: + pos_hint: {'center_x': 0, 'center_y': 1} + icon: 'content-copy' + on_press: app.copy_composer_text(root.address) + + +: + id: myadd_popup + size_hint_y: None + height: "130dp" + spacing:dp(25) + + #height: dp(1.5*(myaddr_label.height)) + orientation: 'vertical' + MDLabel: + id: myaddr_label + font_style: 'Subtitle2' + theme_text_color: 'Primary' + text: app.tr._("Label") + font_size: '17sp' + halign: 'left' + MDLabel: + font_style: 'Body1' + theme_text_color: 'Primary' + text: root.address_label + font_size: '15sp' + halign: 'left' + MDLabel: + font_style: 'Subtitle2' + theme_text_color: 'Primary' + text: app.tr._("Address") + font_size: '17sp' + halign: 'left' + BoxLayout: + orientation: 'horizontal' + MDLabel: + id: label_address + font_style: 'Body1' + theme_text_color: 'Primary' + text: app.tr._(root.address) + font_size: '15sp' + halign: 'left' + IconRightSampleWidget: + pos_hint: {'center_x': 0, 'center_y': 1} + icon: 'content-copy' + on_press: app.copy_composer_text(root.address) + BoxLayout: + id: my_add_btn + spacing:5 + orientation: 'horizontal' + size_hint_y: None + height: self.minimum_height + MDRaisedButton: + size_hint: 2, None + height: dp(40) + on_press: root.send_message_from() + MDLabel: + font_style: 'H6' + text: app.tr._('Send message from') + font_size: '13sp' + color: (1,1,1,1) + halign: 'center' + MDRaisedButton: + size_hint: 1.5, None + height: dp(40) + on_press: app.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' + +: + id: closing_popup + size_hint : (None,None) + height: 1.4*(popup_label.height+ my_add_btn.children[0].height) + width :app.window_size[0] - (app.window_size[0]/10 if app.app_platform == 'android' else app.window_size[0]/4) + background: app.image_path + '/popup.jpeg' + auto_dismiss: False + separator_height: 0 + BoxLayout: + id: myadd_popup_box + size_hint_y: None + spacing:dp(70) + orientation: 'vertical' + BoxLayout: + size_hint_y: None + orientation: 'vertical' + spacing:dp(25) + MDLabel: + id: popup_label + font_style: 'Subtitle2' + theme_text_color: 'Primary' + text: app.tr._("Bitmessage isn't connected to the network.\n If you quit now, it may cause delivery delays.\n Wait until connected and the synchronisation finishes?") + font_size: '17sp' + halign: 'center' + BoxLayout: + id: my_add_btn + spacing:5 + orientation: 'horizontal' + MDRaisedButton: + size_hint: 1.5, None + height: dp(40) + on_press: root.closingAction(self.children[0].text) + on_press: app.stop() + MDLabel: + font_style: 'H6' + text: app.tr._('Yes') + font_size: '13sp' + color: (1,1,1,1) + halign: 'center' + MDRaisedButton: + size_hint: 1.5, None + height: dp(40) + on_press: root.closingAction(self.children[0].text) + MDLabel: + font_style: 'H6' + text: app.tr._('No') + font_size: '13sp' + color: (1,1,1,1) + halign: 'center' + MDRaisedButton: + size_hint: 1.5, None + height: dp(40) + #on_press: root.dismiss() + on_press: root.closingAction(self.children[0].text) + MDLabel: + font_style: 'H6' + text: app.tr._('Cancel') + font_size: '13sp' + color: (1,1,1,1) + halign: 'center' + +: + id: myadd_popup + size_hint : (None,None) + # height: 2*(sd_label.height+ sd_btn.children[0].height) + width :app.window_size[0] - (app.window_size[0]/10 if app.app_platform == 'android' else app.window_size[0]/4) + background: app.image_path + '/popup.jpeg' + auto_dismiss: False + separator_height: 0 + BoxLayout: + id: myadd_popup_box + size_hint_y: None + orientation: 'vertical' + spacing:dp(8 if app.app_platform == 'android' else 3) + BoxLayout: + orientation: 'vertical' + MDLabel: + id: from_add_label + font_style: 'Subtitle2' + theme_text_color: 'Primary' + text: app.tr._("From :") + font_size: '15sp' + halign: 'left' + Widget: + size_hint_y: None + height: dp(1 if app.app_platform == 'android' else 0) + BoxLayout: + size_hint_y: None + height: 50 + orientation: 'horizontal' + MDLabel: + id: sd_label + font_style: 'Body2' + theme_text_color: 'Primary' + text: app.tr._("[b]" + root.from_addr + "[/b]") + font_size: '15sp' + halign: 'left' + markup: True + IconRightSampleWidget: + icon: 'content-copy' + on_press: app.copy_composer_text(root.from_addr) + Widget: + id: space_1 + size_hint_y: None + height: dp(2 if app.app_platform == 'android' else 0) + BoxLayout: + id: to_addtitle + Widget: + id:space_2 + size_hint_y: None + height: dp(1 if app.app_platform == 'android' else 0) + BoxLayout: + id: to_addId + BoxLayout: + size_hint_y: None + orientation: 'vertical' + height: 50 + MDLabel: + font_style: 'Body2' + theme_text_color: 'Primary' + text: app.tr._("Date : " + root.time_tag) + font_size: '15sp' + halign: 'left' + BoxLayout: + id: sd_btn + orientation: 'vertical' + MDRaisedButton: + id: dismiss_btn + on_press: root.dismiss() + size_hint: .2, 0 + pos_hint: {'x': 0.8, 'y': 0} + MDLabel: + font_style: 'H6' + text: app.tr._('Cancel') + font_size: '13sp' + color: (1,1,1,1) + halign: 'center' + +: + orientation: 'horizontal' + MDLabel: + font_style: 'Body2' + theme_text_color: 'Primary' + text: app.tr._(root.to_addr) + font_size: '15sp' + halign: 'left' + IconRightSampleWidget: + icon: 'content-copy' + on_press: app.copy_composer_text(root.to_addr) + +: + orientation: 'vertical' + MDLabel: + id: to_add_label + font_style: 'Subtitle2' + theme_text_color: 'Primary' + text: "To :" + font_size: '15sp' + halign: 'left' \ No newline at end of file diff --git a/src/bitmessagekivy/kv/qrcode.kv b/src/bitmessagekivy/kv/qrcode.kv new file mode 100644 index 00000000..cadaa996 --- /dev/null +++ b/src/bitmessagekivy/kv/qrcode.kv @@ -0,0 +1,33 @@ +: + name: 'showqrcode' + BoxLayout: + orientation: 'vertical' + size_hint: (None, None) + pos_hint:{'center_x': .5, 'top': 0.9} + size: (app.window_size[0]/1.8, app.window_size[0]/1.8) + id: qr + BoxLayout: + orientation: 'vertical' + MyMDTextField: + size_hint_y: None + font_style: 'Body1' + theme_text_color: 'Primary' + text: app.tr._(root.address) + multiline: True + readonly: True + line_color_normal: [0,0,0,0] + _current_line_color: [0,0,0,0] + line_color_focus: [0,0,0,0] + halign: 'center' + font_size: dp(15) + bold: True + canvas.before: + Color: + rgba: (0,0,0,1) + # MDLabel: + # size_hint_y: None + # font_style: 'Body1' + # theme_text_color: 'Primary' + # text: "[b]BM-2cV7Y8imvAevK6z6YmhYRcj2t7rghBtDSZ[/b]" + # markup: True + # pos_hint: {'x': .28, 'y': 0.6} \ No newline at end of file diff --git a/src/bitmessagekivy/kv/scan_screen.kv b/src/bitmessagekivy/kv/scan_screen.kv new file mode 100644 index 00000000..dbcff5a1 --- /dev/null +++ b/src/bitmessagekivy/kv/scan_screen.kv @@ -0,0 +1,2 @@ +: + name:'scanscreen' \ No newline at end of file diff --git a/src/bitmessagekivy/kv/scanner.kv b/src/bitmessagekivy/kv/scanner.kv new file mode 100644 index 00000000..1c56f6c2 --- /dev/null +++ b/src/bitmessagekivy/kv/scanner.kv @@ -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 + + \ No newline at end of file diff --git a/src/bitmessagekivy/kv/sent.kv b/src/bitmessagekivy/kv/sent.kv new file mode 100644 index 00000000..11477ed6 --- /dev/null +++ b/src/bitmessagekivy/kv/sent.kv @@ -0,0 +1,26 @@ +: + name: 'sent' + BoxLayout: + orientation: 'vertical' + spacing: dp(5) + SearchBar: + id: sent_search + GridLayout: + id: identi_tag + padding: [20, 0, 0, 5] + cols: 1 + size_hint_y: None + height: self.minimum_height + MDLabel: + id: tag_label + text: '' + font_style: 'Subtitle2' + BoxLayout: + orientation:'vertical' + ScrollView: + id: scroll_y + do_scroll_x: False + MDList: + id: ml + Loader: + ComposerButton: \ No newline at end of file diff --git a/src/bitmessagekivy/kv/settings.kv b/src/bitmessagekivy/kv/settings.kv new file mode 100644 index 00000000..609c8e80 --- /dev/null +++ b/src/bitmessagekivy/kv/settings.kv @@ -0,0 +1,964 @@ +: + 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: \ No newline at end of file diff --git a/src/bitmessagekivy/kv/trash.kv b/src/bitmessagekivy/kv/trash.kv new file mode 100644 index 00000000..97bcf7d7 --- /dev/null +++ b/src/bitmessagekivy/kv/trash.kv @@ -0,0 +1,25 @@ +: + 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: From ea16d6fefab2ddcf9835b766d648a0aebc465938 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Tue, 25 Jan 2022 13:04:18 +0530 Subject: [PATCH 007/424] Override the pylint checks --- src/bmconfigparser.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/bmconfigparser.py b/src/bmconfigparser.py index 4798dda4..8bc425c8 100644 --- a/src/bmconfigparser.py +++ b/src/bmconfigparser.py @@ -69,6 +69,7 @@ class BMConfigParser(SafeConfigParser): raise ValueError("Invalid value %s" % value) return SafeConfigParser.set(self, section, option, value) + # pylint: disable=redefined-builtin, too-many-return-statements def get(self, section, option, raw=False, vars=None): if sys.version_info[0] == 3: # pylint: disable=arguments-differ @@ -187,7 +188,7 @@ class BMConfigParser(SafeConfigParser): try: if not self.validate( section, option, - self[section][option] + self[section][option] # pylint: disable=unsubscriptable-object ): try: newVal = BMConfigDefaults[section][option] From 4ec5eaf016bc56843bdb0b2caefdcf3c66a65f20 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Thu, 6 Jan 2022 17:32:07 +0530 Subject: [PATCH 008/424] Kivy Mock backend added --- src/tests/mock/__init__.py | 0 src/tests/mock/bitmessagemock.py | 32 ++++++++ src/tests/mock/images | 1 + src/tests/mock/kivy_main.py | 8 ++ src/tests/mock/pybitmessage/__init__.py | 0 src/tests/mock/pybitmessage/addresses.py | 1 + src/tests/mock/pybitmessage/bmconfigparser.py | 1 + .../pybitmessage/class_addressGenerator.py | 80 +++++++++++++++++++ src/tests/mock/pybitmessage/inventory.py | 15 ++++ .../mock/pybitmessage/network/__init__.py | 0 .../mock/pybitmessage/network/threads.py | 1 + src/tests/mock/pybitmessage/queues.py | 1 + src/tests/mock/pybitmessage/shutdown.py | 11 +++ src/tests/mock/pybitmessage/singleton.py | 1 + src/tests/mock/pybitmessage/state.py | 1 + 15 files changed, 153 insertions(+) create mode 100644 src/tests/mock/__init__.py create mode 100644 src/tests/mock/bitmessagemock.py create mode 120000 src/tests/mock/images create mode 100644 src/tests/mock/kivy_main.py create mode 100644 src/tests/mock/pybitmessage/__init__.py create mode 120000 src/tests/mock/pybitmessage/addresses.py create mode 120000 src/tests/mock/pybitmessage/bmconfigparser.py create mode 100644 src/tests/mock/pybitmessage/class_addressGenerator.py create mode 100644 src/tests/mock/pybitmessage/inventory.py create mode 100644 src/tests/mock/pybitmessage/network/__init__.py create mode 120000 src/tests/mock/pybitmessage/network/threads.py create mode 120000 src/tests/mock/pybitmessage/queues.py create mode 100644 src/tests/mock/pybitmessage/shutdown.py create mode 120000 src/tests/mock/pybitmessage/singleton.py create mode 120000 src/tests/mock/pybitmessage/state.py diff --git a/src/tests/mock/__init__.py b/src/tests/mock/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/tests/mock/bitmessagemock.py b/src/tests/mock/bitmessagemock.py new file mode 100644 index 00000000..d9ee857b --- /dev/null +++ b/src/tests/mock/bitmessagemock.py @@ -0,0 +1,32 @@ +# pylint: disable=no-name-in-module, import-error + +""" +Bitmessage mock +""" + +from pybitmessage.class_addressGenerator import addressGenerator +from pybitmessage.inventory import Inventory +from pybitmessage.mpybit import NavigateApp +from pybitmessage import state + + +class MockMain(object): # pylint: disable=too-few-public-methods + """Mock main function""" + + def __init__(self): + """Start main application""" + addressGeneratorThread = addressGenerator() + # close the main program even if there are threads left + addressGeneratorThread.start() + Inventory() + state.kivyapp = NavigateApp() + state.kivyapp.run() + + +def main(): + """Triggers main module""" + MockMain() + + +if __name__ == "__main__": + main() diff --git a/src/tests/mock/images b/src/tests/mock/images new file mode 120000 index 00000000..847b03ed --- /dev/null +++ b/src/tests/mock/images @@ -0,0 +1 @@ +../../images/ \ No newline at end of file diff --git a/src/tests/mock/kivy_main.py b/src/tests/mock/kivy_main.py new file mode 100644 index 00000000..79bb413e --- /dev/null +++ b/src/tests/mock/kivy_main.py @@ -0,0 +1,8 @@ +"""Mock kivy app with mock threads.""" +from pybitmessage import state + +if __name__ == '__main__': + state.kivy = True + print("Kivy Loading......") + from bitmessagemock import main + main() diff --git a/src/tests/mock/pybitmessage/__init__.py b/src/tests/mock/pybitmessage/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/tests/mock/pybitmessage/addresses.py b/src/tests/mock/pybitmessage/addresses.py new file mode 120000 index 00000000..88fcee82 --- /dev/null +++ b/src/tests/mock/pybitmessage/addresses.py @@ -0,0 +1 @@ +../../../addresses.py \ No newline at end of file diff --git a/src/tests/mock/pybitmessage/bmconfigparser.py b/src/tests/mock/pybitmessage/bmconfigparser.py new file mode 120000 index 00000000..da05040e --- /dev/null +++ b/src/tests/mock/pybitmessage/bmconfigparser.py @@ -0,0 +1 @@ +../../../bmconfigparser.py \ No newline at end of file diff --git a/src/tests/mock/pybitmessage/class_addressGenerator.py b/src/tests/mock/pybitmessage/class_addressGenerator.py new file mode 100644 index 00000000..34258bbc --- /dev/null +++ b/src/tests/mock/pybitmessage/class_addressGenerator.py @@ -0,0 +1,80 @@ +""" +A thread for creating addresses +""" + +from six.moves import queue + +from pybitmessage import state +from pybitmessage import queues + +from pybitmessage.bmconfigparser import BMConfigParser + +from pybitmessage.network.threads import StoppableThread + + +fake_addresses = { + 'BM-2cUgQGcTLWAkC6dNsv2Bc8XB3Y1GEesVLV': { + 'privsigningkey': '5KWXwYq1oJMzghUSJaJoWPn8VdeBbhDN8zFot1cBd6ezKKReqBd', + 'privencryptionkey': '5JaeFJs8iPcQT3N8676r3gHKvJ5mTWXy1VLhGCEDqRs4vpvpxV8' + }, + 'BM-2cUd2dm8MVMokruMTcGhhteTpyRZCAMhnA': { + 'privsigningkey': '5JnJ79nkcwjo4Aj7iG8sFMkzYoQqWfpUjTcitTuFJZ1YKHZz98J', + 'privencryptionkey': '5JXgNzTRouFLqSRFJvuHMDHCYPBvTeMPBiHt4Jeb6smNjhUNTYq' + }, + 'BM-2cWyvL54WytfALrJHZqbsDHca5QkrtByAW': { + 'privsigningkey': '5KVE4gLmcfYVicLdgyD4GmnbBTFSnY7Yj2UCuytQqgBBsfwDhpi', + 'privencryptionkey': '5JTw48CGm5CP8fyJUJQMq8HQANQMHDHp2ETUe1dgm6EFpT1egD7' + }, + 'BM-2cTE65PK9Y4AQEkCZbazV86pcQACocnRXd': { + 'privsigningkey': '5KCuyReHx9MB4m5hhEyCWcLEXqc8rxhD1T2VWk8CicPFc8B6LaZ', + 'privencryptionkey': '5KBRpwXdX3n2tP7f583SbFgfzgs6Jemx7qfYqhdH7B1Vhe2jqY6' + }, + 'BM-2cX5z1EgmJ87f2oKAwXdv4VQtEVwr2V3BG': { + 'privsigningkey': '5K5UK7qED7F1uWCVsehudQrszLyMZxFVnP6vN2VDQAjtn5qnyRK', + 'privencryptionkey': '5J5coocoJBX6hy5DFTWKtyEgPmADpSwfQTazMpU7QPeART6oMAu' + } +} + + +class addressGenerator(StoppableThread): + """A thread for creating fake addresses""" + name = "addressGenerator" + address_list = list(fake_addresses.keys()) + + def stopThread(self): + """"To stop address generator thread""" + try: + queues.addressGeneratorQueue.put(("stopThread", "data")) + except queue.Full: + self.logger.warning('addressGeneratorQueue is Full') + super(addressGenerator, self).stopThread() # pylint: disable=super-with-arguments + + def run(self): + """ + Process the requests for addresses generation + from `.queues.addressGeneratorQueue` + """ + while state.shutdown == 0: + queueValue = queues.addressGeneratorQueue.get() + try: + address = self.address_list.pop(0) + except IndexError: + self.logger.error( + 'Program error: you can only create 5 fake addresses') + continue + + if len(queueValue) >= 3: + label = queueValue[3] + else: + label = '' + + BMConfigParser().add_section(address) + BMConfigParser().set(address, 'label', label) + BMConfigParser().set(address, 'enabled', 'true') + BMConfigParser().set( + address, 'privsigningkey', fake_addresses[address]['privsigningkey']) + BMConfigParser().set( + address, 'privencryptionkey', fake_addresses[address]['privencryptionkey']) + BMConfigParser().save() + + queues.addressGeneratorQueue.task_done() diff --git a/src/tests/mock/pybitmessage/inventory.py b/src/tests/mock/pybitmessage/inventory.py new file mode 100644 index 00000000..6173c3cd --- /dev/null +++ b/src/tests/mock/pybitmessage/inventory.py @@ -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 diff --git a/src/tests/mock/pybitmessage/network/__init__.py b/src/tests/mock/pybitmessage/network/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/tests/mock/pybitmessage/network/threads.py b/src/tests/mock/pybitmessage/network/threads.py new file mode 120000 index 00000000..c95b4c36 --- /dev/null +++ b/src/tests/mock/pybitmessage/network/threads.py @@ -0,0 +1 @@ +../../../../network/threads.py \ No newline at end of file diff --git a/src/tests/mock/pybitmessage/queues.py b/src/tests/mock/pybitmessage/queues.py new file mode 120000 index 00000000..8c556015 --- /dev/null +++ b/src/tests/mock/pybitmessage/queues.py @@ -0,0 +1 @@ +../../../queues.py \ No newline at end of file diff --git a/src/tests/mock/pybitmessage/shutdown.py b/src/tests/mock/pybitmessage/shutdown.py new file mode 100644 index 00000000..08c885d8 --- /dev/null +++ b/src/tests/mock/pybitmessage/shutdown.py @@ -0,0 +1,11 @@ +# pylint: disable=invalid-name +"""shutdown function""" + +from pybitmessage import state + + +def doCleanShutdown(): + """ + Used to exit Kivy UI. + """ + state.shutdown = 1 diff --git a/src/tests/mock/pybitmessage/singleton.py b/src/tests/mock/pybitmessage/singleton.py new file mode 120000 index 00000000..5e112567 --- /dev/null +++ b/src/tests/mock/pybitmessage/singleton.py @@ -0,0 +1 @@ +../../../singleton.py \ No newline at end of file diff --git a/src/tests/mock/pybitmessage/state.py b/src/tests/mock/pybitmessage/state.py new file mode 120000 index 00000000..117203f5 --- /dev/null +++ b/src/tests/mock/pybitmessage/state.py @@ -0,0 +1 @@ +../../../state.py \ No newline at end of file From a5773999fe1d6791bfc0cbb830527a5b29b84f5d Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Fri, 28 Jan 2022 18:25:23 +0530 Subject: [PATCH 009/424] Refactor BMConfigParser as a Module variable --- packages/pyinstaller/bitmessagemain.spec | 2 +- setup.py | 2 +- src/api.py | 28 ++--- src/bitmessagecli.py | 120 ++++++++++---------- src/bitmessagecurses/__init__.py | 62 +++++----- src/bitmessagemain.py | 9 +- src/bitmessageqt/__init__.py | 111 +++++++++--------- src/bitmessageqt/account.py | 28 ++--- src/bitmessageqt/bitmessageui.py | 8 +- src/bitmessageqt/blacklist.py | 28 ++--- src/bitmessageqt/foldertree.py | 24 ++-- src/bitmessageqt/languagebox.py | 4 +- src/bitmessageqt/settings.py | 82 +++++++------- src/bitmessageqt/support.py | 10 +- src/bitmessageqt/tests/settings.py | 6 +- src/bitmessageqt/utils.py | 8 +- src/bmconfigparser.py | 138 +++-------------------- src/build_osx.py | 2 +- src/class_addressGenerator.py | 50 ++++---- src/class_objectProcessor.py | 34 +++--- src/class_singleCleaner.py | 6 +- src/class_singleWorker.py | 66 +++++------ src/class_smtpDeliver.py | 8 +- src/class_smtpServer.py | 14 +-- src/class_sqlThread.py | 11 +- src/default.ini | 47 ++++++++ src/helper_addressbook.py | 4 +- src/helper_msgcoding.py | 6 +- src/helper_sent.py | 6 +- src/helper_startup.py | 38 ++----- src/highlevelcrypto.py | 4 +- src/inventory.py | 4 +- src/l10n.py | 6 +- src/mock/class_addressGenerator.py | 14 +-- src/namecoin.py | 33 +++--- src/network/announcethread.py | 4 +- src/network/bmproto.py | 8 +- src/network/connectionchooser.py | 6 +- src/network/connectionpool.py | 52 ++++----- src/network/knownnodes.py | 8 +- src/network/proxy.py | 8 +- src/network/tcp.py | 12 +- src/openclpow.py | 4 +- src/proofofwork.py | 4 +- src/protocol.py | 14 +-- src/shared.py | 8 +- src/tests/core.py | 28 ++--- src/tests/test_config.py | 51 +++++---- src/tests/test_config_process.py | 3 +- src/upnp.py | 14 +-- 50 files changed, 583 insertions(+), 664 deletions(-) create mode 100644 src/default.ini diff --git a/packages/pyinstaller/bitmessagemain.spec b/packages/pyinstaller/bitmessagemain.spec index d7d4d70d..8d38ec09 100644 --- a/packages/pyinstaller/bitmessagemain.spec +++ b/packages/pyinstaller/bitmessagemain.spec @@ -74,7 +74,7 @@ a.datas += [ # append the translations directory a.datas += addTranslations() - +a.datas += [('default.ini', os.path.join(srcPath, 'default.ini'), 'DATA')] excluded_binaries = [ 'QtOpenGL4.dll', diff --git a/setup.py b/setup.py index f43f3d58..fea2e58a 100644 --- a/setup.py +++ b/setup.py @@ -136,7 +136,7 @@ if __name__ == "__main__": packages=packages, package_data={'': [ 'bitmessageqt/*.ui', 'bitmsghash/*.cl', 'sslkeys/*.pem', - 'translations/*.ts', 'translations/*.qm', + 'translations/*.ts', 'translations/*.qm', 'default.ini', 'images/*.png', 'images/*.ico', 'images/*.icns' ]}, data_files=data_files, diff --git a/src/api.py b/src/api.py index 9cab1cfe..736ba024 100644 --- a/src/api.py +++ b/src/api.py @@ -87,7 +87,7 @@ from addresses import ( decodeVarint, varintDecodeError ) -from bmconfigparser import BMConfigParser +from bmconfigparser import config from debug import logger from helper_sql import SqlBulkExecute, sqlExecute, sqlQuery, sqlStoredProcedure, sql_ready from inventory import Inventory @@ -190,8 +190,8 @@ class singleAPI(StoppableThread): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: s.connect(( - BMConfigParser().get('bitmessagesettings', 'apiinterface'), - BMConfigParser().getint('bitmessagesettings', 'apiport') + config.get('bitmessagesettings', 'apiinterface'), + config.getint('bitmessagesettings', 'apiport') )) s.shutdown(socket.SHUT_RDWR) s.close() @@ -204,7 +204,7 @@ class singleAPI(StoppableThread): :class:`jsonrpclib.SimpleJSONRPCServer` is created and started here with `BMRPCDispatcher` dispatcher. """ - port = BMConfigParser().getint('bitmessagesettings', 'apiport') + port = config.getint('bitmessagesettings', 'apiport') try: getattr(errno, 'WSAEADDRINUSE') except AttributeError: @@ -212,7 +212,7 @@ class singleAPI(StoppableThread): RPCServerBase = SimpleXMLRPCServer ct = 'text/xml' - if BMConfigParser().safeGet( + if config.safeGet( 'bitmessagesettings', 'apivariant') == 'json': try: from jsonrpclib.SimpleJSONRPCServer import ( @@ -242,7 +242,7 @@ class singleAPI(StoppableThread): 'Failed to start API listener on port %s', port) port = random.randint(32767, 65535) se = StoppableRPCServer( - (BMConfigParser().get( + (config.get( 'bitmessagesettings', 'apiinterface'), port), BMXMLRPCRequestHandler, True, encoding='UTF-8') @@ -252,15 +252,15 @@ class singleAPI(StoppableThread): else: if attempt > 0: logger.warning('Setting apiport to %s', port) - BMConfigParser().set( + config.set( 'bitmessagesettings', 'apiport', str(port)) - BMConfigParser().save() + config.save() break se.register_instance(BMRPCDispatcher()) se.register_introspection_functions() - apiNotifyPath = BMConfigParser().safeGet( + apiNotifyPath = config.safeGet( 'bitmessagesettings', 'apinotifypath') if apiNotifyPath: @@ -271,7 +271,7 @@ class singleAPI(StoppableThread): logger.warning( 'Failed to call %s, removing apinotifypath setting', apiNotifyPath) - BMConfigParser().remove_option( + config.remove_option( 'bitmessagesettings', 'apinotifypath') se.serve_forever() @@ -286,7 +286,7 @@ class CommandHandler(type): # pylint: disable=protected-access result = super(CommandHandler, mcs).__new__( mcs, name, bases, namespace) - result.config = BMConfigParser() + result.config = config result._handlers = {} apivariant = result.config.safeGet('bitmessagesettings', 'apivariant') for func in namespace.values(): @@ -325,7 +325,7 @@ class command(object): # pylint: disable=too-few-public-methods def __call__(self, func): - if BMConfigParser().safeGet( + if config.safeGet( 'bitmessagesettings', 'apivariant') == 'legacy': def wrapper(*args): """ @@ -444,9 +444,9 @@ class BMXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): encstr = self.headers.get('Authorization').split()[1] emailid, password = encstr.decode('base64').split(':') return ( - emailid == BMConfigParser().get( + emailid == config.get( 'bitmessagesettings', 'apiusername' - ) and password == BMConfigParser().get( + ) and password == config.get( 'bitmessagesettings', 'apipassword')) else: logger.warning( diff --git a/src/bitmessagecli.py b/src/bitmessagecli.py index adcab8b1..84c618af 100644 --- a/src/bitmessagecli.py +++ b/src/bitmessagecli.py @@ -23,7 +23,7 @@ import sys import time import xmlrpclib -from bmconfigparser import BMConfigParser +from bmconfigparser import config api = '' @@ -86,15 +86,15 @@ def lookupAppdataFolder(): def configInit(): """Initialised the configuration""" - BMConfigParser().add_section('bitmessagesettings') + config.add_section('bitmessagesettings') # Sets the bitmessage port to stop the warning about the api not properly # being setup. This is in the event that the keys.dat is in a different # directory or is created locally to connect to a machine remotely. - BMConfigParser().set('bitmessagesettings', 'port', '8444') - BMConfigParser().set('bitmessagesettings', 'apienabled', 'true') # Sets apienabled to true in keys.dat + config.set('bitmessagesettings', 'port', '8444') + config.set('bitmessagesettings', 'apienabled', 'true') # Sets apienabled to true in keys.dat with open(keysName, 'wb') as configfile: - BMConfigParser().write(configfile) + config.write(configfile) print('\n ' + str(keysName) + ' Initalized in the same directory as daemon.py') print(' You will now need to configure the ' + str(keysName) + ' file.\n') @@ -104,15 +104,15 @@ def apiInit(apiEnabled): """Initialise the API""" global usrPrompt - BMConfigParser().read(keysPath) + config.read(keysPath) if apiEnabled is False: # API information there but the api is disabled. uInput = userInput("The API is not enabled. Would you like to do that now, (Y)es or (N)o?").lower() if uInput == "y": - BMConfigParser().set('bitmessagesettings', 'apienabled', 'true') # Sets apienabled to true in keys.dat + config.set('bitmessagesettings', 'apienabled', 'true') # Sets apienabled to true in keys.dat with open(keysPath, 'wb') as configfile: - BMConfigParser().write(configfile) + config.write(configfile) print('Done') restartBmNotify() @@ -158,15 +158,15 @@ def apiInit(apiEnabled): # sets the bitmessage port to stop the warning about the api not properly # being setup. This is in the event that the keys.dat is in a different # directory or is created locally to connect to a machine remotely. - BMConfigParser().set('bitmessagesettings', 'port', '8444') - BMConfigParser().set('bitmessagesettings', 'apienabled', 'true') - BMConfigParser().set('bitmessagesettings', 'apiport', apiPort) - BMConfigParser().set('bitmessagesettings', 'apiinterface', '127.0.0.1') - BMConfigParser().set('bitmessagesettings', 'apiusername', apiUsr) - BMConfigParser().set('bitmessagesettings', 'apipassword', apiPwd) - BMConfigParser().set('bitmessagesettings', 'daemon', daemon) + config.set('bitmessagesettings', 'port', '8444') + config.set('bitmessagesettings', 'apienabled', 'true') + config.set('bitmessagesettings', 'apiport', apiPort) + config.set('bitmessagesettings', 'apiinterface', '127.0.0.1') + config.set('bitmessagesettings', 'apiusername', apiUsr) + config.set('bitmessagesettings', 'apipassword', apiPwd) + config.set('bitmessagesettings', 'daemon', daemon) with open(keysPath, 'wb') as configfile: - BMConfigParser().write(configfile) + config.write(configfile) print('\n Finished configuring the keys.dat file with API information.\n') restartBmNotify() @@ -191,19 +191,19 @@ def apiData(): global keysPath global usrPrompt - BMConfigParser().read(keysPath) # First try to load the config file (the keys.dat file) from the program directory + config.read(keysPath) # First try to load the config file (the keys.dat file) from the program directory try: - BMConfigParser().get('bitmessagesettings', 'port') + config.get('bitmessagesettings', 'port') appDataFolder = '' except: # noqa:E722 # Could not load the keys.dat file in the program directory. Perhaps it is in the appdata directory. appDataFolder = lookupAppdataFolder() keysPath = appDataFolder + keysPath - BMConfigParser().read(keysPath) + config.read(keysPath) try: - BMConfigParser().get('bitmessagesettings', 'port') + config.get('bitmessagesettings', 'port') except: # noqa:E722 # keys.dat was not there either, something is wrong. print('\n ******************************************************************') @@ -230,24 +230,24 @@ def apiData(): main() try: # checks to make sure that everyting is configured correctly. Excluding apiEnabled, it is checked after - BMConfigParser().get('bitmessagesettings', 'apiport') - BMConfigParser().get('bitmessagesettings', 'apiinterface') - BMConfigParser().get('bitmessagesettings', 'apiusername') - BMConfigParser().get('bitmessagesettings', 'apipassword') + config.get('bitmessagesettings', 'apiport') + config.get('bitmessagesettings', 'apiinterface') + config.get('bitmessagesettings', 'apiusername') + config.get('bitmessagesettings', 'apipassword') except: # noqa:E722 apiInit("") # Initalize the keys.dat file with API information # keys.dat file was found or appropriately configured, allow information retrieval # apiEnabled = - # apiInit(BMConfigParser().safeGetBoolean('bitmessagesettings','apienabled')) + # apiInit(config.safeGetBoolean('bitmessagesettings','apienabled')) # #if false it will prompt the user, if true it will return true - BMConfigParser().read(keysPath) # read again since changes have been made - apiPort = int(BMConfigParser().get('bitmessagesettings', 'apiport')) - apiInterface = BMConfigParser().get('bitmessagesettings', 'apiinterface') - apiUsername = BMConfigParser().get('bitmessagesettings', 'apiusername') - apiPassword = BMConfigParser().get('bitmessagesettings', 'apipassword') + config.read(keysPath) # read again since changes have been made + apiPort = int(config.get('bitmessagesettings', 'apiport')) + apiInterface = config.get('bitmessagesettings', 'apiinterface') + apiUsername = config.get('bitmessagesettings', 'apiusername') + apiPassword = config.get('bitmessagesettings', 'apipassword') print('\n API data successfully imported.\n') @@ -277,28 +277,28 @@ def bmSettings(): keysPath = 'keys.dat' - BMConfigParser().read(keysPath) # Read the keys.dat + config.read(keysPath) # Read the keys.dat try: - port = BMConfigParser().get('bitmessagesettings', 'port') + port = config.get('bitmessagesettings', 'port') except: # noqa:E722 print('\n File not found.\n') usrPrompt = 0 main() - startonlogon = BMConfigParser().safeGetBoolean('bitmessagesettings', 'startonlogon') - minimizetotray = BMConfigParser().safeGetBoolean('bitmessagesettings', 'minimizetotray') - showtraynotifications = BMConfigParser().safeGetBoolean('bitmessagesettings', 'showtraynotifications') - startintray = BMConfigParser().safeGetBoolean('bitmessagesettings', 'startintray') - defaultnoncetrialsperbyte = BMConfigParser().get('bitmessagesettings', 'defaultnoncetrialsperbyte') - defaultpayloadlengthextrabytes = BMConfigParser().get('bitmessagesettings', 'defaultpayloadlengthextrabytes') - daemon = BMConfigParser().safeGetBoolean('bitmessagesettings', 'daemon') + startonlogon = config.safeGetBoolean('bitmessagesettings', 'startonlogon') + minimizetotray = config.safeGetBoolean('bitmessagesettings', 'minimizetotray') + showtraynotifications = config.safeGetBoolean('bitmessagesettings', 'showtraynotifications') + startintray = config.safeGetBoolean('bitmessagesettings', 'startintray') + defaultnoncetrialsperbyte = config.get('bitmessagesettings', 'defaultnoncetrialsperbyte') + defaultpayloadlengthextrabytes = config.get('bitmessagesettings', 'defaultpayloadlengthextrabytes') + daemon = config.safeGetBoolean('bitmessagesettings', 'daemon') - socksproxytype = BMConfigParser().get('bitmessagesettings', 'socksproxytype') - sockshostname = BMConfigParser().get('bitmessagesettings', 'sockshostname') - socksport = BMConfigParser().get('bitmessagesettings', 'socksport') - socksauthentication = BMConfigParser().safeGetBoolean('bitmessagesettings', 'socksauthentication') - socksusername = BMConfigParser().get('bitmessagesettings', 'socksusername') - sockspassword = BMConfigParser().get('bitmessagesettings', 'sockspassword') + socksproxytype = config.get('bitmessagesettings', 'socksproxytype') + sockshostname = config.get('bitmessagesettings', 'sockshostname') + socksport = config.get('bitmessagesettings', 'socksport') + socksauthentication = config.safeGetBoolean('bitmessagesettings', 'socksauthentication') + socksusername = config.get('bitmessagesettings', 'socksusername') + sockspassword = config.get('bitmessagesettings', 'sockspassword') print('\n -----------------------------------') print(' | Current Bitmessage Settings |') @@ -333,60 +333,60 @@ def bmSettings(): if uInput == "port": print(' Current port number: ' + port) uInput = userInput("Enter the new port number.") - BMConfigParser().set('bitmessagesettings', 'port', str(uInput)) + config.set('bitmessagesettings', 'port', str(uInput)) elif uInput == "startonlogon": print(' Current status: ' + str(startonlogon)) uInput = userInput("Enter the new status.") - BMConfigParser().set('bitmessagesettings', 'startonlogon', str(uInput)) + config.set('bitmessagesettings', 'startonlogon', str(uInput)) elif uInput == "minimizetotray": print(' Current status: ' + str(minimizetotray)) uInput = userInput("Enter the new status.") - BMConfigParser().set('bitmessagesettings', 'minimizetotray', str(uInput)) + config.set('bitmessagesettings', 'minimizetotray', str(uInput)) elif uInput == "showtraynotifications": print(' Current status: ' + str(showtraynotifications)) uInput = userInput("Enter the new status.") - BMConfigParser().set('bitmessagesettings', 'showtraynotifications', str(uInput)) + config.set('bitmessagesettings', 'showtraynotifications', str(uInput)) elif uInput == "startintray": print(' Current status: ' + str(startintray)) uInput = userInput("Enter the new status.") - BMConfigParser().set('bitmessagesettings', 'startintray', str(uInput)) + config.set('bitmessagesettings', 'startintray', str(uInput)) elif uInput == "defaultnoncetrialsperbyte": print(' Current default nonce trials per byte: ' + defaultnoncetrialsperbyte) uInput = userInput("Enter the new defaultnoncetrialsperbyte.") - BMConfigParser().set('bitmessagesettings', 'defaultnoncetrialsperbyte', str(uInput)) + config.set('bitmessagesettings', 'defaultnoncetrialsperbyte', str(uInput)) elif uInput == "defaultpayloadlengthextrabytes": print(' Current default payload length extra bytes: ' + defaultpayloadlengthextrabytes) uInput = userInput("Enter the new defaultpayloadlengthextrabytes.") - BMConfigParser().set('bitmessagesettings', 'defaultpayloadlengthextrabytes', str(uInput)) + config.set('bitmessagesettings', 'defaultpayloadlengthextrabytes', str(uInput)) elif uInput == "daemon": print(' Current status: ' + str(daemon)) uInput = userInput("Enter the new status.").lower() - BMConfigParser().set('bitmessagesettings', 'daemon', str(uInput)) + config.set('bitmessagesettings', 'daemon', str(uInput)) elif uInput == "socksproxytype": print(' Current socks proxy type: ' + socksproxytype) print("Possibilities: 'none', 'SOCKS4a', 'SOCKS5'.") uInput = userInput("Enter the new socksproxytype.") - BMConfigParser().set('bitmessagesettings', 'socksproxytype', str(uInput)) + config.set('bitmessagesettings', 'socksproxytype', str(uInput)) elif uInput == "sockshostname": print(' Current socks host name: ' + sockshostname) uInput = userInput("Enter the new sockshostname.") - BMConfigParser().set('bitmessagesettings', 'sockshostname', str(uInput)) + config.set('bitmessagesettings', 'sockshostname', str(uInput)) elif uInput == "socksport": print(' Current socks port number: ' + socksport) uInput = userInput("Enter the new socksport.") - BMConfigParser().set('bitmessagesettings', 'socksport', str(uInput)) + config.set('bitmessagesettings', 'socksport', str(uInput)) elif uInput == "socksauthentication": print(' Current status: ' + str(socksauthentication)) uInput = userInput("Enter the new status.") - BMConfigParser().set('bitmessagesettings', 'socksauthentication', str(uInput)) + config.set('bitmessagesettings', 'socksauthentication', str(uInput)) elif uInput == "socksusername": print(' Current socks username: ' + socksusername) uInput = userInput("Enter the new socksusername.") - BMConfigParser().set('bitmessagesettings', 'socksusername', str(uInput)) + config.set('bitmessagesettings', 'socksusername', str(uInput)) elif uInput == "sockspassword": print(' Current socks password: ' + sockspassword) uInput = userInput("Enter the new password.") - BMConfigParser().set('bitmessagesettings', 'sockspassword', str(uInput)) + config.set('bitmessagesettings', 'sockspassword', str(uInput)) else: print("\n Invalid input. Please try again.\n") invalidInput = True @@ -397,7 +397,7 @@ def bmSettings(): if uInput != "y": print('\n Changes Made.\n') with open(keysPath, 'wb') as configfile: - BMConfigParser().write(configfile) + config.write(configfile) restartBmNotify() break diff --git a/src/bitmessagecurses/__init__.py b/src/bitmessagecurses/__init__.py index 7b52457d..542b8e53 100644 --- a/src/bitmessagecurses/__init__.py +++ b/src/bitmessagecurses/__init__.py @@ -28,7 +28,7 @@ import shutdown import state from addresses import addBMIfNotPresent, decodeAddress -from bmconfigparser import BMConfigParser +from bmconfigparser import config from helper_sql import sqlExecute, sqlQuery from inventory import Inventory @@ -618,19 +618,19 @@ def handlech(c, stdscr): r, t = d.inputbox("New address label", init=label) if r == d.DIALOG_OK: label = t - BMConfigParser().set(a, "label", label) + config.set(a, "label", label) # Write config - BMConfigParser().save() + config.save() addresses[addrcur][0] = label elif t == "4": # Enable address a = addresses[addrcur][2] - BMConfigParser().set(a, "enabled", "true") # Set config + config.set(a, "enabled", "true") # Set config # Write config - BMConfigParser().save() + config.save() # Change color - if BMConfigParser().safeGetBoolean(a, 'chan'): + if config.safeGetBoolean(a, 'chan'): addresses[addrcur][3] = 9 # orange - elif BMConfigParser().safeGetBoolean(a, 'mailinglist'): + elif config.safeGetBoolean(a, 'mailinglist'): addresses[addrcur][3] = 5 # magenta else: addresses[addrcur][3] = 0 # black @@ -638,26 +638,26 @@ def handlech(c, stdscr): shared.reloadMyAddressHashes() # Reload address hashes elif t == "5": # Disable address a = addresses[addrcur][2] - BMConfigParser().set(a, "enabled", "false") # Set config + config.set(a, "enabled", "false") # Set config addresses[addrcur][3] = 8 # Set color to gray # Write config - BMConfigParser().save() + config.save() addresses[addrcur][1] = False shared.reloadMyAddressHashes() # Reload address hashes elif t == "6": # Delete address r, t = d.inputbox("Type in \"I want to delete this address\"", width=50) if r == d.DIALOG_OK and t == "I want to delete this address": - BMConfigParser().remove_section(addresses[addrcur][2]) - BMConfigParser().save() + config.remove_section(addresses[addrcur][2]) + config.save() del addresses[addrcur] elif t == "7": # Special address behavior a = addresses[addrcur][2] set_background_title(d, "Special address behavior") - if BMConfigParser().safeGetBoolean(a, "chan"): + if config.safeGetBoolean(a, "chan"): scrollbox(d, unicode( "This is a chan address. You cannot use it as a pseudo-mailing list.")) else: - m = BMConfigParser().safeGetBoolean(a, "mailinglist") + m = config.safeGetBoolean(a, "mailinglist") r, t = d.radiolist( "Select address behavior", choices=[ @@ -665,24 +665,24 @@ def handlech(c, stdscr): ("2", "Behave as a pseudo-mailing-list address", m)]) if r == d.DIALOG_OK: if t == "1" and m: - BMConfigParser().set(a, "mailinglist", "false") + config.set(a, "mailinglist", "false") if addresses[addrcur][1]: addresses[addrcur][3] = 0 # Set color to black else: addresses[addrcur][3] = 8 # Set color to gray elif t == "2" and m is False: try: - mn = BMConfigParser().get(a, "mailinglistname") + mn = config.get(a, "mailinglistname") except ConfigParser.NoOptionError: mn = "" r, t = d.inputbox("Mailing list name", init=mn) if r == d.DIALOG_OK: mn = t - BMConfigParser().set(a, "mailinglist", "true") - BMConfigParser().set(a, "mailinglistname", mn) + config.set(a, "mailinglist", "true") + config.set(a, "mailinglistname", mn) addresses[addrcur][3] = 6 # Set color to magenta # Write config - BMConfigParser().save() + config.save() elif menutab == 5: set_background_title(d, "Subscriptions Dialog Box") if len(subscriptions) <= subcur: @@ -1002,7 +1002,7 @@ def loadInbox(): if toaddr == BROADCAST_STR: tolabel = BROADCAST_STR else: - tolabel = BMConfigParser().get(toaddr, "label") + tolabel = config.get(toaddr, "label") except: # noqa:E722 tolabel = "" if tolabel == "": @@ -1011,8 +1011,8 @@ def loadInbox(): # Set label for from address fromlabel = "" - if BMConfigParser().has_section(fromaddr): - fromlabel = BMConfigParser().get(fromaddr, "label") + if config.has_section(fromaddr): + fromlabel = config.get(fromaddr, "label") if fromlabel == "": # Check Address Book qr = sqlQuery("SELECT label FROM addressbook WHERE address=?", fromaddr) if qr != []: @@ -1062,15 +1062,15 @@ def loadSent(): for r in qr: tolabel, = r if tolabel == "": - if BMConfigParser().has_section(toaddr): - tolabel = BMConfigParser().get(toaddr, "label") + if config.has_section(toaddr): + tolabel = config.get(toaddr, "label") if tolabel == "": tolabel = toaddr # Set label for from address fromlabel = "" - if BMConfigParser().has_section(fromaddr): - fromlabel = BMConfigParser().get(fromaddr, "label") + if config.has_section(fromaddr): + fromlabel = config.get(fromaddr, "label") if fromlabel == "": fromlabel = fromaddr @@ -1146,7 +1146,7 @@ def loadSubscriptions(): def loadBlackWhiteList(): """load black/white list""" global bwtype - bwtype = BMConfigParser().get("bitmessagesettings", "blackwhitelist") + bwtype = config.get("bitmessagesettings", "blackwhitelist") if bwtype == "black": ret = sqlQuery("SELECT label, address, enabled FROM blacklist") else: @@ -1205,16 +1205,16 @@ def run(stdscr): curses.init_pair(9, curses.COLOR_YELLOW, curses.COLOR_BLACK) # orangish # Init list of address in 'Your Identities' tab - configSections = BMConfigParser().addresses() + configSections = config.addresses() for addressInKeysFile in configSections: - isEnabled = BMConfigParser().getboolean(addressInKeysFile, "enabled") - addresses.append([BMConfigParser().get(addressInKeysFile, "label"), isEnabled, addressInKeysFile]) + isEnabled = config.getboolean(addressInKeysFile, "enabled") + addresses.append([config.get(addressInKeysFile, "label"), isEnabled, addressInKeysFile]) # Set address color if not isEnabled: addresses[len(addresses) - 1].append(8) # gray - elif BMConfigParser().safeGetBoolean(addressInKeysFile, 'chan'): + elif config.safeGetBoolean(addressInKeysFile, 'chan'): addresses[len(addresses) - 1].append(9) # orange - elif BMConfigParser().safeGetBoolean(addressInKeysFile, 'mailinglist'): + elif config.safeGetBoolean(addressInKeysFile, 'mailinglist'): addresses[len(addresses) - 1].append(5) # magenta else: addresses[len(addresses) - 1].append(0) # black diff --git a/src/bitmessagemain.py b/src/bitmessagemain.py index e60813b3..4118926b 100755 --- a/src/bitmessagemain.py +++ b/src/bitmessagemain.py @@ -35,7 +35,7 @@ import shutdown import state from testmode_init import populate_api_test_data -from bmconfigparser import BMConfigParser +from bmconfigparser import config from debug import logger # this should go before any threads from helper_startup import ( adjustHalfOpenConnectionsLimit, fixSocket, start_proxyconfig) @@ -92,7 +92,6 @@ class Main(object): fixSocket() adjustHalfOpenConnectionsLimit() - config = BMConfigParser() daemon = config.safeGetBoolean('bitmessagesettings', 'daemon') try: @@ -408,11 +407,11 @@ All parameters are optional. @staticmethod def getApiAddress(): """This function returns API address and port""" - if not BMConfigParser().safeGetBoolean( + if not config.safeGetBoolean( 'bitmessagesettings', 'apienabled'): return None - address = BMConfigParser().get('bitmessagesettings', 'apiinterface') - port = BMConfigParser().getint('bitmessagesettings', 'apiport') + address = config.get('bitmessagesettings', 'apiinterface') + port = config.getint('bitmessagesettings', 'apiport') return {'address': address, 'port': port} diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 6a692f38..98964a62 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -24,7 +24,7 @@ from debug import logger from tr import _translate from addresses import decodeAddress, addBMIfNotPresent from bitmessageui import Ui_MainWindow -from bmconfigparser import BMConfigParser +from bmconfigparser import config import namecoin from messageview import MessageView from migrationwizard import Ui_MigrationWizard @@ -516,11 +516,11 @@ class MyForm(settingsmixin.SMainWindow): enabled = {} for toAddress in getSortedAccounts(): - isEnabled = BMConfigParser().getboolean( + isEnabled = config.getboolean( toAddress, 'enabled') - isChan = BMConfigParser().safeGetBoolean( + isChan = config.safeGetBoolean( toAddress, 'chan') - isMaillinglist = BMConfigParser().safeGetBoolean( + isMaillinglist = config.safeGetBoolean( toAddress, 'mailinglist') if treeWidget == self.ui.treeWidgetYourIdentities: @@ -639,8 +639,8 @@ class MyForm(settingsmixin.SMainWindow): reply = QtGui.QMessageBox.question( self, 'Message', displayMsg, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) if reply == QtGui.QMessageBox.Yes: - BMConfigParser().remove_section(addressInKeysFile) - BMConfigParser().save() + config.remove_section(addressInKeysFile) + config.save() self.change_translation() @@ -810,7 +810,7 @@ class MyForm(settingsmixin.SMainWindow): self.rerenderComboBoxSendFromBroadcast() # Put the TTL slider in the correct spot - TTL = BMConfigParser().getint('bitmessagesettings', 'ttl') + TTL = config.getint('bitmessagesettings', 'ttl') if TTL < 3600: # an hour TTL = 3600 elif TTL > 28*24*60*60: # 28 days @@ -830,7 +830,7 @@ class MyForm(settingsmixin.SMainWindow): self.ui.updateNetworkSwitchMenuLabel() - self._firstrun = BMConfigParser().safeGetBoolean( + self._firstrun = config.safeGetBoolean( 'bitmessagesettings', 'dontconnect') self._contact_selected = None @@ -849,7 +849,7 @@ class MyForm(settingsmixin.SMainWindow): Configure Bitmessage to start on startup (or remove the configuration) based on the setting in the keys.dat file """ - startonlogon = BMConfigParser().safeGetBoolean( + startonlogon = config.safeGetBoolean( 'bitmessagesettings', 'startonlogon') if sys.platform.startswith('win'): # Auto-startup for Windows RUN_PATH = "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run" @@ -871,8 +871,8 @@ class MyForm(settingsmixin.SMainWindow): def updateTTL(self, sliderPosition): TTL = int(sliderPosition ** 3.199 + 3600) self.updateHumanFriendlyTTLDescription(TTL) - BMConfigParser().set('bitmessagesettings', 'ttl', str(TTL)) - BMConfigParser().save() + config.set('bitmessagesettings', 'ttl', str(TTL)) + config.save() def updateHumanFriendlyTTLDescription(self, TTL): numberOfHours = int(round(TTL / (60*60))) @@ -923,7 +923,7 @@ class MyForm(settingsmixin.SMainWindow): self.appIndicatorShowOrHideWindow() def appIndicatorSwitchQuietMode(self): - BMConfigParser().set( + config.set( 'bitmessagesettings', 'showtraynotifications', str(not self.actionQuiet.isChecked()) ) @@ -1294,7 +1294,7 @@ class MyForm(settingsmixin.SMainWindow): # show bitmessage self.actionShow = QtGui.QAction(_translate( "MainWindow", "Show Bitmessage"), m, checkable=True) - self.actionShow.setChecked(not BMConfigParser().getboolean( + self.actionShow.setChecked(not config.getboolean( 'bitmessagesettings', 'startintray')) self.actionShow.triggered.connect(self.appIndicatorShowOrHideWindow) if not sys.platform[0:3] == 'win': @@ -1303,7 +1303,7 @@ class MyForm(settingsmixin.SMainWindow): # quiet mode self.actionQuiet = QtGui.QAction(_translate( "MainWindow", "Quiet Mode"), m, checkable=True) - self.actionQuiet.setChecked(not BMConfigParser().getboolean( + self.actionQuiet.setChecked(not config.getboolean( 'bitmessagesettings', 'showtraynotifications')) self.actionQuiet.triggered.connect(self.appIndicatorSwitchQuietMode) m.addAction(self.actionQuiet) @@ -1647,9 +1647,9 @@ class MyForm(settingsmixin.SMainWindow): if dialog.exec_(): if dialog.radioButtonConnectNow.isChecked(): self.ui.updateNetworkSwitchMenuLabel(False) - BMConfigParser().remove_option( + config.remove_option( 'bitmessagesettings', 'dontconnect') - BMConfigParser().save() + config.save() elif dialog.radioButtonConfigureNetwork.isChecked(): self.click_actionSettings() else: @@ -1674,7 +1674,7 @@ class MyForm(settingsmixin.SMainWindow): self.ui.blackwhitelist.init_blacklist_popup_menu(False) if event.type() == QtCore.QEvent.WindowStateChange: if self.windowState() & QtCore.Qt.WindowMinimized: - if BMConfigParser().getboolean('bitmessagesettings', 'minimizetotray') and not 'darwin' in sys.platform: + if config.getboolean('bitmessagesettings', 'minimizetotray') and not 'darwin' in sys.platform: QtCore.QTimer.singleShot(0, self.appIndicatorHide) elif event.oldState() & QtCore.Qt.WindowMinimized: # The window state has just been changed to @@ -1691,7 +1691,7 @@ class MyForm(settingsmixin.SMainWindow): def setStatusIcon(self, color): # print 'setting status icon color' - _notifications_enabled = not BMConfigParser().getboolean( + _notifications_enabled = not config.getboolean( 'bitmessagesettings', 'hidetrayconnectionnotifications') if color == 'red': self.pushButtonStatusIcon.setIcon( @@ -1703,8 +1703,8 @@ class MyForm(settingsmixin.SMainWindow): 'Bitmessage', _translate("MainWindow", "Connection lost"), sound.SOUND_DISCONNECTED) - if not BMConfigParser().safeGetBoolean('bitmessagesettings', 'upnp') and \ - BMConfigParser().get('bitmessagesettings', 'socksproxytype') == "none": + if not config.safeGetBoolean('bitmessagesettings', 'upnp') and \ + config.get('bitmessagesettings', 'socksproxytype') == "none": self.updateStatusBar( _translate( "MainWindow", @@ -1947,7 +1947,7 @@ class MyForm(settingsmixin.SMainWindow): addresses = getSortedAccounts() for address in addresses: account = accountClass(address) - if (account.type == AccountMixin.CHAN and BMConfigParser().safeGetBoolean(address, 'enabled')): + if (account.type == AccountMixin.CHAN and config.safeGetBoolean(address, 'enabled')): newRows[address] = [account.getLabel(), AccountMixin.CHAN] # normal accounts queryreturn = sqlQuery('SELECT * FROM addressbook') @@ -2065,9 +2065,9 @@ class MyForm(settingsmixin.SMainWindow): email = ''.join(random.SystemRandom().choice(string.ascii_lowercase) for _ in range(12)) + "@mailchuck.com" acct = MailchuckAccount(fromAddress) acct.register(email) - BMConfigParser().set(fromAddress, 'label', email) - BMConfigParser().set(fromAddress, 'gateway', 'mailchuck') - BMConfigParser().save() + config.set(fromAddress, 'label', email) + config.set(fromAddress, 'gateway', 'mailchuck') + config.save() self.updateStatusBar(_translate( "MainWindow", "Error: Your account wasn't registered at" @@ -2259,18 +2259,18 @@ class MyForm(settingsmixin.SMainWindow): self.ui.tabWidgetSend.setCurrentIndex( self.ui.tabWidgetSend.indexOf( self.ui.sendBroadcast - if BMConfigParser().safeGetBoolean(str(address), 'mailinglist') + if config.safeGetBoolean(str(address), 'mailinglist') else self.ui.sendDirect )) def rerenderComboBoxSendFrom(self): self.ui.comboBoxSendFrom.clear() for addressInKeysFile in getSortedAccounts(): - isEnabled = BMConfigParser().getboolean( + isEnabled = config.getboolean( addressInKeysFile, 'enabled') # I realize that this is poor programming practice but I don't care. It's easier for others to read. - isMaillinglist = BMConfigParser().safeGetBoolean(addressInKeysFile, 'mailinglist') + isMaillinglist = config.safeGetBoolean(addressInKeysFile, 'mailinglist') if isEnabled and not isMaillinglist: - label = unicode(BMConfigParser().get(addressInKeysFile, 'label'), 'utf-8', 'ignore').strip() + label = unicode(config.get(addressInKeysFile, 'label'), 'utf-8', 'ignore').strip() if label == "": label = addressInKeysFile self.ui.comboBoxSendFrom.addItem(avatarize(addressInKeysFile), label, addressInKeysFile) @@ -2290,11 +2290,11 @@ class MyForm(settingsmixin.SMainWindow): def rerenderComboBoxSendFromBroadcast(self): self.ui.comboBoxSendFromBroadcast.clear() for addressInKeysFile in getSortedAccounts(): - isEnabled = BMConfigParser().getboolean( + isEnabled = config.getboolean( addressInKeysFile, 'enabled') # I realize that this is poor programming practice but I don't care. It's easier for others to read. - isChan = BMConfigParser().safeGetBoolean(addressInKeysFile, 'chan') + isChan = config.safeGetBoolean(addressInKeysFile, 'chan') if isEnabled and not isChan: - label = unicode(BMConfigParser().get(addressInKeysFile, 'label'), 'utf-8', 'ignore').strip() + label = unicode(config.get(addressInKeysFile, 'label'), 'utf-8', 'ignore').strip() if label == "": label = addressInKeysFile self.ui.comboBoxSendFromBroadcast.addItem(avatarize(addressInKeysFile), label, addressInKeysFile) @@ -2395,7 +2395,7 @@ class MyForm(settingsmixin.SMainWindow): else: acct = ret self.propagateUnreadCount(widget=treeWidget if ret else None) - if BMConfigParser().safeGetBoolean( + if config.safeGetBoolean( 'bitmessagesettings', 'showtraynotifications'): self.notifierShow( _translate("MainWindow", "New Message"), @@ -2415,7 +2415,7 @@ class MyForm(settingsmixin.SMainWindow): if acct.feedback != GatewayAccount.ALL_OK: if acct.feedback == GatewayAccount.REGISTRATION_DENIED: dialogs.EmailGatewayDialog( - self, BMConfigParser(), acct).exec_() + self, config, acct).exec_() # possible other branches? except AttributeError: pass @@ -2497,7 +2497,7 @@ class MyForm(settingsmixin.SMainWindow): )) def click_pushButtonStatusIcon(self): - dialogs.IconGlossaryDialog(self, config=BMConfigParser()).exec_() + dialogs.IconGlossaryDialog(self, config=config).exec_() def click_actionHelp(self): dialogs.HelpDialog(self).exec_() @@ -2524,10 +2524,10 @@ class MyForm(settingsmixin.SMainWindow): def on_action_SpecialAddressBehaviorDialog(self): """Show SpecialAddressBehaviorDialog""" - dialogs.SpecialAddressBehaviorDialog(self, BMConfigParser()) + dialogs.SpecialAddressBehaviorDialog(self, config) def on_action_EmailGatewayDialog(self): - dialog = dialogs.EmailGatewayDialog(self, config=BMConfigParser()) + dialog = dialogs.EmailGatewayDialog(self, config=config) # For Modal dialogs dialog.exec_() try: @@ -2589,7 +2589,7 @@ class MyForm(settingsmixin.SMainWindow): dialogs.NewAddressDialog(self) def network_switch(self): - dontconnect_option = not BMConfigParser().safeGetBoolean( + dontconnect_option = not config.safeGetBoolean( 'bitmessagesettings', 'dontconnect') reply = QtGui.QMessageBox.question( self, _translate("MainWindow", "Disconnecting") @@ -2604,9 +2604,9 @@ class MyForm(settingsmixin.SMainWindow): QtGui.QMessageBox.Cancel) if reply != QtGui.QMessageBox.Yes: return - BMConfigParser().set( + config.set( 'bitmessagesettings', 'dontconnect', str(dontconnect_option)) - BMConfigParser().save() + config.save() self.ui.updateNetworkSwitchMenuLabel(dontconnect_option) self.ui.pushButtonFetchNamecoinID.setHidden( @@ -2668,7 +2668,7 @@ class MyForm(settingsmixin.SMainWindow): elif reply == QtGui.QMessageBox.Cancel: return - if state.statusIconColor == 'red' and not BMConfigParser().safeGetBoolean( + if state.statusIconColor == 'red' and not config.safeGetBoolean( 'bitmessagesettings', 'dontconnect'): reply = QtGui.QMessageBox.question( self, _translate("MainWindow", "Not connected"), @@ -2804,7 +2804,7 @@ class MyForm(settingsmixin.SMainWindow): def closeEvent(self, event): """window close event""" event.ignore() - trayonclose = BMConfigParser().safeGetBoolean( + trayonclose = config.safeGetBoolean( 'bitmessagesettings', 'trayonclose') if trayonclose: self.appIndicatorHide() @@ -2873,7 +2873,7 @@ class MyForm(settingsmixin.SMainWindow): # Format predefined text on message reply. def quoted_text(self, message): - if not BMConfigParser().safeGetBoolean('bitmessagesettings', 'replybelow'): + if not config.safeGetBoolean('bitmessagesettings', 'replybelow'): return '\n\n------------------------------------------------------\n' + message quoteWrapper = textwrap.TextWrapper( @@ -2960,7 +2960,7 @@ class MyForm(settingsmixin.SMainWindow): self.ui.tabWidgetSend.indexOf(self.ui.sendDirect) ) # toAddressAtCurrentInboxRow = fromAddressAtCurrentInboxRow - elif not BMConfigParser().has_section(toAddressAtCurrentInboxRow): + elif not config.has_section(toAddressAtCurrentInboxRow): QtGui.QMessageBox.information( self, _translate("MainWindow", "Address is gone"), _translate( @@ -2968,7 +2968,7 @@ class MyForm(settingsmixin.SMainWindow): "Bitmessage cannot find your address %1. Perhaps you" " removed it?" ).arg(toAddressAtCurrentInboxRow), QtGui.QMessageBox.Ok) - elif not BMConfigParser().getboolean( + elif not config.getboolean( toAddressAtCurrentInboxRow, 'enabled'): QtGui.QMessageBox.information( self, _translate("MainWindow", "Address disabled"), @@ -3058,7 +3058,8 @@ class MyForm(settingsmixin.SMainWindow): queryreturn = sqlQuery('''select * from blacklist where address=?''', addressAtCurrentInboxRow) if queryreturn == []: - label = "\"" + tableWidget.item(currentInboxRow, 2).subject + "\" in " + BMConfigParser().get(recipientAddress, "label") + label = "\"" + tableWidget.item(currentInboxRow, 2).subject + "\" in " + config.get( + recipientAddress, "label") sqlExecute('''INSERT INTO blacklist VALUES (?,?, ?)''', label, addressAtCurrentInboxRow, True) @@ -3575,12 +3576,12 @@ class MyForm(settingsmixin.SMainWindow): " delete the channel?" ), QtGui.QMessageBox.Yes | QtGui.QMessageBox.No ) == QtGui.QMessageBox.Yes: - BMConfigParser().remove_section(str(account.address)) + config.remove_section(str(account.address)) else: return else: return - BMConfigParser().save() + config.save() shared.reloadMyAddressHashes() self.rerenderAddressBook() self.rerenderComboBoxSendFrom() @@ -3596,8 +3597,8 @@ class MyForm(settingsmixin.SMainWindow): account.setEnabled(True) def enableIdentity(self, address): - BMConfigParser().set(address, 'enabled', 'true') - BMConfigParser().save() + config.set(address, 'enabled', 'true') + config.save() shared.reloadMyAddressHashes() self.rerenderAddressBook() @@ -3608,8 +3609,8 @@ class MyForm(settingsmixin.SMainWindow): account.setEnabled(False) def disableIdentity(self, address): - BMConfigParser().set(str(address), 'enabled', 'false') - BMConfigParser().save() + config.set(str(address), 'enabled', 'false') + config.save() shared.reloadMyAddressHashes() self.rerenderAddressBook() @@ -4092,7 +4093,7 @@ class MyForm(settingsmixin.SMainWindow): # Check to see whether we can connect to namecoin. # Hide the 'Fetch Namecoin ID' button if we can't. - if BMConfigParser().safeGetBoolean( + if config.safeGetBoolean( 'bitmessagesettings', 'dontconnect' ) or self.namecoin.test()[0] == 'failed': logger.warning( @@ -4191,13 +4192,13 @@ def run(): myapp.showConnectDialog() # ask the user if we may connect # try: -# if BMConfigParser().get('bitmessagesettings', 'mailchuck') < 1: -# myapp.showMigrationWizard(BMConfigParser().get('bitmessagesettings', 'mailchuck')) +# if config.get('bitmessagesettings', 'mailchuck') < 1: +# myapp.showMigrationWizard(config.get('bitmessagesettings', 'mailchuck')) # except: # myapp.showMigrationWizard(0) # only show after wizards and connect dialogs have completed - if not BMConfigParser().getboolean('bitmessagesettings', 'startintray'): + if not config.getboolean('bitmessagesettings', 'startintray'): myapp.show() app.exec_() diff --git a/src/bitmessageqt/account.py b/src/bitmessageqt/account.py index 50ea3548..092b4d45 100644 --- a/src/bitmessageqt/account.py +++ b/src/bitmessageqt/account.py @@ -18,7 +18,7 @@ from PyQt4 import QtGui import queues from addresses import decodeAddress -from bmconfigparser import BMConfigParser +from bmconfigparser import config from helper_ackPayload import genAckPayload from helper_sql import sqlQuery, sqlExecute from .foldertree import AccountMixin @@ -28,16 +28,16 @@ from .utils import str_broadcast_subscribers def getSortedAccounts(): """Get a sorted list of configSections""" - configSections = BMConfigParser().addresses() + configSections = config.addresses() configSections.sort( cmp=lambda x, y: cmp( unicode( - BMConfigParser().get( + config.get( x, 'label'), 'utf-8').lower(), unicode( - BMConfigParser().get( + config.get( y, 'label'), 'utf-8').lower())) @@ -80,7 +80,7 @@ def getSortedSubscriptions(count=False): def accountClass(address): """Return a BMAccount for the address""" - if not BMConfigParser().has_section(address): + if not config.has_section(address): # .. todo:: This BROADCAST section makes no sense if address == str_broadcast_subscribers: subscription = BroadcastAccount(address) @@ -93,7 +93,7 @@ def accountClass(address): return NoAccount(address) return subscription try: - gateway = BMConfigParser().get(address, "gateway") + gateway = config.get(address, "gateway") for _, cls in inspect.getmembers(sys.modules[__name__], inspect.isclass): if issubclass(cls, GatewayAccount) and cls.gatewayName == gateway: return cls(address) @@ -114,9 +114,9 @@ class AccountColor(AccountMixin): # pylint: disable=too-few-public-methods if address_type is None: if address is None: self.type = AccountMixin.ALL - elif BMConfigParser().safeGetBoolean(self.address, 'mailinglist'): + elif config.safeGetBoolean(self.address, 'mailinglist'): self.type = AccountMixin.MAILINGLIST - elif BMConfigParser().safeGetBoolean(self.address, 'chan'): + elif config.safeGetBoolean(self.address, 'chan'): self.type = AccountMixin.CHAN elif sqlQuery( '''select label from subscriptions where address=?''', self.address): @@ -133,10 +133,10 @@ class BMAccount(object): def __init__(self, address=None): self.address = address self.type = AccountMixin.NORMAL - if BMConfigParser().has_section(address): - if BMConfigParser().safeGetBoolean(self.address, 'chan'): + if config.has_section(address): + if config.safeGetBoolean(self.address, 'chan'): self.type = AccountMixin.CHAN - elif BMConfigParser().safeGetBoolean(self.address, 'mailinglist'): + elif config.safeGetBoolean(self.address, 'mailinglist'): self.type = AccountMixin.MAILINGLIST elif self.address == str_broadcast_subscribers: self.type = AccountMixin.BROADCAST @@ -150,7 +150,7 @@ class BMAccount(object): """Get a label for this bitmessage account""" if address is None: address = self.address - label = BMConfigParser().safeGet(address, 'label', address) + label = config.safeGet(address, 'label', address) queryreturn = sqlQuery( '''select label from addressbook where address=?''', address) if queryreturn != []: @@ -216,7 +216,7 @@ class GatewayAccount(BMAccount): # pylint: disable=unused-variable status, addressVersionNumber, streamNumber, ripe = decodeAddress(self.toAddress) - stealthLevel = BMConfigParser().safeGetInt('bitmessagesettings', 'ackstealthlevel') + stealthLevel = config.safeGetInt('bitmessagesettings', 'ackstealthlevel') ackdata = genAckPayload(streamNumber, stealthLevel) sqlExecute( '''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''', @@ -235,7 +235,7 @@ class GatewayAccount(BMAccount): 'sent', # folder 2, # encodingtype # not necessary to have a TTL higher than 2 days - min(BMConfigParser().getint('bitmessagesettings', 'ttl'), 86400 * 2) + min(config.getint('bitmessagesettings', 'ttl'), 86400 * 2) ) queues.workerQueue.put(('sendmessage', self.toAddress)) diff --git a/src/bitmessageqt/bitmessageui.py b/src/bitmessageqt/bitmessageui.py index 7f2c8c91..f5d61d1c 100644 --- a/src/bitmessageqt/bitmessageui.py +++ b/src/bitmessageqt/bitmessageui.py @@ -8,7 +8,7 @@ # WARNING! All changes made in this file will be lost! from PyQt4 import QtCore, QtGui -from bmconfigparser import BMConfigParser +from bmconfigparser import config from foldertree import AddressBookCompleter from messageview import MessageView from messagecompose import MessageCompose @@ -561,7 +561,7 @@ class Ui_MainWindow(object): self.blackwhitelist = Blacklist() self.tabWidget.addTab(self.blackwhitelist, QtGui.QIcon(":/newPrefix/images/blacklist.png"), "") # Initialize the Blacklist or Whitelist - if BMConfigParser().get('bitmessagesettings', 'blackwhitelist') == 'white': + if config.get('bitmessagesettings', 'blackwhitelist') == 'white': self.blackwhitelist.radioButtonWhitelist.click() self.blackwhitelist.rerenderBlackWhiteList() @@ -663,7 +663,7 @@ class Ui_MainWindow(object): def updateNetworkSwitchMenuLabel(self, dontconnect=None): if dontconnect is None: - dontconnect = BMConfigParser().safeGetBoolean( + dontconnect = config.safeGetBoolean( 'bitmessagesettings', 'dontconnect') self.actionNetworkSwitch.setText( _translate("MainWindow", "Go online", None) @@ -710,7 +710,7 @@ class Ui_MainWindow(object): self.pushButtonTTL.setText(_translate("MainWindow", "TTL:", None)) hours = 48 try: - hours = int(BMConfigParser().getint('bitmessagesettings', 'ttl')/60/60) + hours = int(config.getint('bitmessagesettings', 'ttl')/60/60) except: pass self.labelHumanFriendlyTTLDescription.setText(_translate("MainWindow", "%n hour(s)", None, QtCore.QCoreApplication.CodecForTr, hours)) diff --git a/src/bitmessageqt/blacklist.py b/src/bitmessageqt/blacklist.py index 3897bddc..093f23d8 100644 --- a/src/bitmessageqt/blacklist.py +++ b/src/bitmessageqt/blacklist.py @@ -2,7 +2,7 @@ from PyQt4 import QtCore, QtGui import widgets from addresses import addBMIfNotPresent -from bmconfigparser import BMConfigParser +from bmconfigparser import config from dialogs import AddAddressDialog from helper_sql import sqlExecute, sqlQuery from queues import UISignalQueue @@ -39,17 +39,17 @@ class Blacklist(QtGui.QWidget, RetranslateMixin): "rerenderBlackWhiteList()"), self.rerenderBlackWhiteList) def click_radioButtonBlacklist(self): - if BMConfigParser().get('bitmessagesettings', 'blackwhitelist') == 'white': - BMConfigParser().set('bitmessagesettings', 'blackwhitelist', 'black') - BMConfigParser().save() + if config.get('bitmessagesettings', 'blackwhitelist') == 'white': + config.set('bitmessagesettings', 'blackwhitelist', 'black') + config.save() # self.tableWidgetBlacklist.clearContents() self.tableWidgetBlacklist.setRowCount(0) self.rerenderBlackWhiteList() def click_radioButtonWhitelist(self): - if BMConfigParser().get('bitmessagesettings', 'blackwhitelist') == 'black': - BMConfigParser().set('bitmessagesettings', 'blackwhitelist', 'white') - BMConfigParser().save() + if config.get('bitmessagesettings', 'blackwhitelist') == 'black': + config.set('bitmessagesettings', 'blackwhitelist', 'white') + config.save() # self.tableWidgetBlacklist.clearContents() self.tableWidgetBlacklist.setRowCount(0) self.rerenderBlackWhiteList() @@ -65,7 +65,7 @@ class Blacklist(QtGui.QWidget, RetranslateMixin): # address book. The user cannot add it again or else it will # cause problems when updating and deleting the entry. t = (address,) - if BMConfigParser().get('bitmessagesettings', 'blackwhitelist') == 'black': + if config.get('bitmessagesettings', 'blackwhitelist') == 'black': sql = '''select * from blacklist where address=?''' else: sql = '''select * from whitelist where address=?''' @@ -83,7 +83,7 @@ class Blacklist(QtGui.QWidget, RetranslateMixin): self.tableWidgetBlacklist.setItem(0, 1, newItem) self.tableWidgetBlacklist.setSortingEnabled(True) t = (str(self.NewBlacklistDialogInstance.lineEditLabel.text().toUtf8()), address, True) - if BMConfigParser().get('bitmessagesettings', 'blackwhitelist') == 'black': + if config.get('bitmessagesettings', 'blackwhitelist') == 'black': sql = '''INSERT INTO blacklist VALUES (?,?,?)''' else: sql = '''INSERT INTO whitelist VALUES (?,?,?)''' @@ -158,12 +158,12 @@ class Blacklist(QtGui.QWidget, RetranslateMixin): def rerenderBlackWhiteList(self): tabs = self.parent().parent() - if BMConfigParser().get('bitmessagesettings', 'blackwhitelist') == 'black': + if config.get('bitmessagesettings', 'blackwhitelist') == 'black': tabs.setTabText(tabs.indexOf(self), _translate('blacklist', 'Blacklist')) else: tabs.setTabText(tabs.indexOf(self), _translate('blacklist', 'Whitelist')) self.tableWidgetBlacklist.setRowCount(0) - listType = BMConfigParser().get('bitmessagesettings', 'blackwhitelist') + listType = config.get('bitmessagesettings', 'blackwhitelist') if listType == 'black': queryreturn = sqlQuery('''SELECT label, address, enabled FROM blacklist''') else: @@ -195,7 +195,7 @@ class Blacklist(QtGui.QWidget, RetranslateMixin): currentRow, 0).text().toUtf8() addressAtCurrentRow = self.tableWidgetBlacklist.item( currentRow, 1).text() - if BMConfigParser().get('bitmessagesettings', 'blackwhitelist') == 'black': + if config.get('bitmessagesettings', 'blackwhitelist') == 'black': sqlExecute( '''DELETE FROM blacklist WHERE label=? AND address=?''', str(labelAtCurrentRow), str(addressAtCurrentRow)) @@ -224,7 +224,7 @@ class Blacklist(QtGui.QWidget, RetranslateMixin): currentRow, 0).setTextColor(QtGui.QApplication.palette().text().color()) self.tableWidgetBlacklist.item( currentRow, 1).setTextColor(QtGui.QApplication.palette().text().color()) - if BMConfigParser().get('bitmessagesettings', 'blackwhitelist') == 'black': + if config.get('bitmessagesettings', 'blackwhitelist') == 'black': sqlExecute( '''UPDATE blacklist SET enabled=1 WHERE address=?''', str(addressAtCurrentRow)) @@ -241,7 +241,7 @@ class Blacklist(QtGui.QWidget, RetranslateMixin): currentRow, 0).setTextColor(QtGui.QColor(128, 128, 128)) self.tableWidgetBlacklist.item( currentRow, 1).setTextColor(QtGui.QColor(128, 128, 128)) - if BMConfigParser().get('bitmessagesettings', 'blackwhitelist') == 'black': + if config.get('bitmessagesettings', 'blackwhitelist') == 'black': sqlExecute( '''UPDATE blacklist SET enabled=0 WHERE address=?''', str(addressAtCurrentRow)) else: diff --git a/src/bitmessageqt/foldertree.py b/src/bitmessageqt/foldertree.py index e6d64427..c50b7d3d 100644 --- a/src/bitmessageqt/foldertree.py +++ b/src/bitmessageqt/foldertree.py @@ -8,7 +8,7 @@ from cgi import escape from PyQt4 import QtCore, QtGui -from bmconfigparser import BMConfigParser +from bmconfigparser import config from helper_sql import sqlExecute, sqlQuery from settingsmixin import SettingsMixin from tr import _translate @@ -106,9 +106,9 @@ class AccountMixin(object): if self.address is None: self.type = self.ALL self.setFlags(self.flags() & ~QtCore.Qt.ItemIsEditable) - elif BMConfigParser().safeGetBoolean(self.address, 'chan'): + elif config.safeGetBoolean(self.address, 'chan'): self.type = self.CHAN - elif BMConfigParser().safeGetBoolean(self.address, 'mailinglist'): + elif config.safeGetBoolean(self.address, 'mailinglist'): self.type = self.MAILINGLIST elif sqlQuery( '''select label from subscriptions where address=?''', self.address): @@ -125,7 +125,7 @@ class AccountMixin(object): AccountMixin.CHAN, AccountMixin.MAILINGLIST): try: retval = unicode( - BMConfigParser().get(self.address, 'label'), 'utf-8') + config.get(self.address, 'label'), 'utf-8') except Exception: queryreturn = sqlQuery( '''select label from addressbook where address=?''', self.address) @@ -237,7 +237,7 @@ class Ui_AddressWidget(BMTreeWidgetItem, SettingsMixin): else: try: return unicode( - BMConfigParser().get(self.address, 'label'), + config.get(self.address, 'label'), 'utf-8', 'ignore') except: return unicode(self.address, 'utf-8') @@ -263,13 +263,13 @@ class Ui_AddressWidget(BMTreeWidgetItem, SettingsMixin): """Save account label (if you edit in the the UI, this will be triggered and will save it to keys.dat)""" if role == QtCore.Qt.EditRole \ and self.type != AccountMixin.SUBSCRIPTION: - BMConfigParser().set( + config.set( str(self.address), 'label', str(value.toString().toUtf8()) if isinstance(value, QtCore.QVariant) else value.encode('utf-8') ) - BMConfigParser().save() + config.save() return super(Ui_AddressWidget, self).setData(column, role, value) def setAddress(self, address): @@ -382,7 +382,7 @@ class BMAddressWidget(BMTableWidgetItem, AccountMixin): if role == QtCore.Qt.ToolTipRole: return self.label + " (" + self.address + ")" elif role == QtCore.Qt.DecorationRole: - if BMConfigParser().safeGetBoolean( + if config.safeGetBoolean( 'bitmessagesettings', 'useidenticons'): return avatarize(self.address or self.label) elif role == QtCore.Qt.ForegroundRole: @@ -408,7 +408,7 @@ class MessageList_AddressWidget(BMAddressWidget): AccountMixin.CHAN, AccountMixin.MAILINGLIST): try: newLabel = unicode( - BMConfigParser().get(self.address, 'label'), + config.get(self.address, 'label'), 'utf-8', 'ignore') except: queryreturn = sqlQuery( @@ -521,9 +521,9 @@ class Ui_AddressBookWidgetItem(BMAddressWidget): AccountMixin.NORMAL, AccountMixin.MAILINGLIST, AccountMixin.CHAN): try: - BMConfigParser().get(self.address, 'label') - BMConfigParser().set(self.address, 'label', self.label) - BMConfigParser().save() + config.get(self.address, 'label') + config.set(self.address, 'label', self.label) + config.save() except: sqlExecute('''UPDATE addressbook set label=? WHERE address=?''', self.label, self.address) elif self.type == AccountMixin.SUBSCRIPTION: diff --git a/src/bitmessageqt/languagebox.py b/src/bitmessageqt/languagebox.py index 9ff78990..34f96b02 100644 --- a/src/bitmessageqt/languagebox.py +++ b/src/bitmessageqt/languagebox.py @@ -6,7 +6,7 @@ import os from PyQt4 import QtCore, QtGui import paths -from bmconfigparser import BMConfigParser +from bmconfigparser import config class LanguageBox(QtGui.QComboBox): @@ -41,7 +41,7 @@ class LanguageBox(QtGui.QComboBox): self.addItem( locale.nativeLanguageName() or localeShort, localeShort) - configuredLocale = BMConfigParser().safeGet( + configuredLocale = config.safeGet( 'bitmessagesettings', 'userlocale', "system") for i in range(self.count()): if self.itemData(i) == configuredLocale: diff --git a/src/bitmessageqt/settings.py b/src/bitmessageqt/settings.py index 4173ebd2..501b2114 100644 --- a/src/bitmessageqt/settings.py +++ b/src/bitmessageqt/settings.py @@ -16,7 +16,7 @@ import paths import queues import state import widgets -from bmconfigparser import BMConfigParser +from bmconfigparser import config from helper_sql import sqlExecute, sqlStoredProcedure from helper_startup import start_proxyconfig from network import knownnodes, AnnounceThread @@ -24,11 +24,11 @@ from network.asyncore_pollchoose import set_rates from tr import _translate -def getSOCKSProxyType(config): +def getSOCKSProxyType(config_): """Get user socksproxytype setting from *config*""" try: result = ConfigParser.SafeConfigParser.get( - config, 'bitmessagesettings', 'socksproxytype') + config_, 'bitmessagesettings', 'socksproxytype') except (ConfigParser.NoSectionError, ConfigParser.NoOptionError): return None else: @@ -45,7 +45,7 @@ class SettingsDialog(QtGui.QDialog): self.parent = parent self.firstrun = firstrun - self.config = BMConfigParser() + self.config = config self.net_restart_needed = False self.timer = QtCore.QTimer() @@ -80,7 +80,7 @@ class SettingsDialog(QtGui.QDialog): ) QtGui.QWidget.resize(self, QtGui.QWidget.sizeHint(self)) - def adjust_from_config(self, config): + def adjust_from_config(self, config_): """Adjust all widgets state according to config settings""" # pylint: disable=too-many-branches,too-many-statements if not self.parent.tray.isSystemTrayAvailable(): @@ -89,31 +89,31 @@ class SettingsDialog(QtGui.QDialog): "MainWindow", "Tray (not available in your system)")) for setting in ( 'minimizetotray', 'trayonclose', 'startintray'): - config.set('bitmessagesettings', setting, 'false') + config_.set('bitmessagesettings', setting, 'false') else: self.checkBoxMinimizeToTray.setChecked( - config.getboolean('bitmessagesettings', 'minimizetotray')) + config_.getboolean('bitmessagesettings', 'minimizetotray')) self.checkBoxTrayOnClose.setChecked( - config.safeGetBoolean('bitmessagesettings', 'trayonclose')) + config_.safeGetBoolean('bitmessagesettings', 'trayonclose')) self.checkBoxStartInTray.setChecked( - config.getboolean('bitmessagesettings', 'startintray')) + config_.getboolean('bitmessagesettings', 'startintray')) self.checkBoxHideTrayConnectionNotifications.setChecked( - config.getboolean( + config_.getboolean( 'bitmessagesettings', 'hidetrayconnectionnotifications')) self.checkBoxShowTrayNotifications.setChecked( - config.getboolean('bitmessagesettings', 'showtraynotifications')) + config_.getboolean('bitmessagesettings', 'showtraynotifications')) self.checkBoxStartOnLogon.setChecked( - config.getboolean('bitmessagesettings', 'startonlogon')) + config_.getboolean('bitmessagesettings', 'startonlogon')) self.checkBoxWillinglySendToMobile.setChecked( - config.safeGetBoolean( + config_.safeGetBoolean( 'bitmessagesettings', 'willinglysendtomobile')) self.checkBoxUseIdenticons.setChecked( - config.safeGetBoolean('bitmessagesettings', 'useidenticons')) + config_.safeGetBoolean('bitmessagesettings', 'useidenticons')) self.checkBoxReplyBelow.setChecked( - config.safeGetBoolean('bitmessagesettings', 'replybelow')) + config_.safeGetBoolean('bitmessagesettings', 'replybelow')) if state.appdata == paths.lookupExeFolder(): self.checkBoxPortableMode.setChecked(True) @@ -142,57 +142,57 @@ class SettingsDialog(QtGui.QDialog): # On the Network settings tab: self.lineEditTCPPort.setText(str( - config.get('bitmessagesettings', 'port'))) + config_.get('bitmessagesettings', 'port'))) self.checkBoxUPnP.setChecked( - config.safeGetBoolean('bitmessagesettings', 'upnp')) + config_.safeGetBoolean('bitmessagesettings', 'upnp')) self.checkBoxUDP.setChecked( - config.safeGetBoolean('bitmessagesettings', 'udp')) + config_.safeGetBoolean('bitmessagesettings', 'udp')) self.checkBoxAuthentication.setChecked( - config.getboolean('bitmessagesettings', 'socksauthentication')) + config_.getboolean('bitmessagesettings', 'socksauthentication')) self.checkBoxSocksListen.setChecked( - config.getboolean('bitmessagesettings', 'sockslisten')) + config_.getboolean('bitmessagesettings', 'sockslisten')) self.checkBoxOnionOnly.setChecked( - config.safeGetBoolean('bitmessagesettings', 'onionservicesonly')) + config_.safeGetBoolean('bitmessagesettings', 'onionservicesonly')) - self._proxy_type = getSOCKSProxyType(config) + self._proxy_type = getSOCKSProxyType(config_) self.comboBoxProxyType.setCurrentIndex( 0 if not self._proxy_type else self.comboBoxProxyType.findText(self._proxy_type)) self.comboBoxProxyTypeChanged(self.comboBoxProxyType.currentIndex()) self.lineEditSocksHostname.setText( - config.get('bitmessagesettings', 'sockshostname')) + config_.get('bitmessagesettings', 'sockshostname')) self.lineEditSocksPort.setText(str( - config.get('bitmessagesettings', 'socksport'))) + config_.get('bitmessagesettings', 'socksport'))) self.lineEditSocksUsername.setText( - config.get('bitmessagesettings', 'socksusername')) + config_.get('bitmessagesettings', 'socksusername')) self.lineEditSocksPassword.setText( - config.get('bitmessagesettings', 'sockspassword')) + config_.get('bitmessagesettings', 'sockspassword')) self.lineEditMaxDownloadRate.setText(str( - config.get('bitmessagesettings', 'maxdownloadrate'))) + config_.get('bitmessagesettings', 'maxdownloadrate'))) self.lineEditMaxUploadRate.setText(str( - config.get('bitmessagesettings', 'maxuploadrate'))) + config_.get('bitmessagesettings', 'maxuploadrate'))) self.lineEditMaxOutboundConnections.setText(str( - config.get('bitmessagesettings', 'maxoutboundconnections'))) + config_.get('bitmessagesettings', 'maxoutboundconnections'))) # Demanded difficulty tab self.lineEditTotalDifficulty.setText(str((float( - config.getint( + config_.getint( 'bitmessagesettings', 'defaultnoncetrialsperbyte') ) / defaults.networkDefaultProofOfWorkNonceTrialsPerByte))) self.lineEditSmallMessageDifficulty.setText(str((float( - config.getint( + config_.getint( 'bitmessagesettings', 'defaultpayloadlengthextrabytes') ) / defaults.networkDefaultPayloadLengthExtraBytes))) # Max acceptable difficulty tab self.lineEditMaxAcceptableTotalDifficulty.setText(str((float( - config.getint( + config_.getint( 'bitmessagesettings', 'maxacceptablenoncetrialsperbyte') ) / defaults.networkDefaultProofOfWorkNonceTrialsPerByte))) self.lineEditMaxAcceptableSmallMessageDifficulty.setText(str((float( - config.getint( + config_.getint( 'bitmessagesettings', 'maxacceptablepayloadlengthextrabytes') ) / defaults.networkDefaultPayloadLengthExtraBytes))) @@ -203,21 +203,21 @@ class SettingsDialog(QtGui.QDialog): self.comboBoxOpenCL.addItems(openclpow.vendors) self.comboBoxOpenCL.setCurrentIndex(0) for i in range(self.comboBoxOpenCL.count()): - if self.comboBoxOpenCL.itemText(i) == config.safeGet( + if self.comboBoxOpenCL.itemText(i) == config_.safeGet( 'bitmessagesettings', 'opencl'): self.comboBoxOpenCL.setCurrentIndex(i) break # Namecoin integration tab - nmctype = config.get('bitmessagesettings', 'namecoinrpctype') + nmctype = config_.get('bitmessagesettings', 'namecoinrpctype') self.lineEditNamecoinHost.setText( - config.get('bitmessagesettings', 'namecoinrpchost')) + config_.get('bitmessagesettings', 'namecoinrpchost')) self.lineEditNamecoinPort.setText(str( - config.get('bitmessagesettings', 'namecoinrpcport'))) + config_.get('bitmessagesettings', 'namecoinrpcport'))) self.lineEditNamecoinUser.setText( - config.get('bitmessagesettings', 'namecoinrpcuser')) + config_.get('bitmessagesettings', 'namecoinrpcuser')) self.lineEditNamecoinPassword.setText( - config.get('bitmessagesettings', 'namecoinrpcpassword')) + config_.get('bitmessagesettings', 'namecoinrpcpassword')) if nmctype == "namecoind": self.radioButtonNamecoinNamecoind.setChecked(True) @@ -232,9 +232,9 @@ class SettingsDialog(QtGui.QDialog): # Message Resend tab self.lineEditDays.setText(str( - config.get('bitmessagesettings', 'stopresendingafterxdays'))) + config_.get('bitmessagesettings', 'stopresendingafterxdays'))) self.lineEditMonths.setText(str( - config.get('bitmessagesettings', 'stopresendingafterxmonths'))) + config_.get('bitmessagesettings', 'stopresendingafterxmonths'))) def comboBoxProxyTypeChanged(self, comboBoxIndex): """A callback for currentIndexChanged event of comboBoxProxyType""" diff --git a/src/bitmessageqt/support.py b/src/bitmessageqt/support.py index ac02e2ca..865d79c4 100644 --- a/src/bitmessageqt/support.py +++ b/src/bitmessageqt/support.py @@ -15,7 +15,7 @@ import paths import proofofwork import queues import state -from bmconfigparser import BMConfigParser +from bmconfigparser import config from foldertree import AccountMixin from helper_sql import sqlExecute, sqlQuery from l10n import getTranslationLanguage @@ -79,7 +79,7 @@ def checkAddressBook(myapp): def checkHasNormalAddress(): for address in account.getSortedAccounts(): acct = account.accountClass(address) - if acct.type == AccountMixin.NORMAL and BMConfigParser().safeGetBoolean(address, 'enabled'): + if acct.type == AccountMixin.NORMAL and config.safeGetBoolean(address, 'enabled'): return address return False @@ -142,11 +142,11 @@ def createSupportMessage(myapp): portablemode = "True" if state.appdata == paths.lookupExeFolder() else "False" cpow = "True" if proofofwork.bmpow else "False" openclpow = str( - BMConfigParser().safeGet('bitmessagesettings', 'opencl') + config.safeGet('bitmessagesettings', 'opencl') ) if openclEnabled() else "None" locale = getTranslationLanguage() - socks = getSOCKSProxyType(BMConfigParser()) or "N/A" - upnp = BMConfigParser().safeGet('bitmessagesettings', 'upnp', "N/A") + socks = getSOCKSProxyType(config) or "N/A" + upnp = config.safeGet('bitmessagesettings', 'upnp', "N/A") connectedhosts = len(network.stats.connectedHostsList()) myapp.ui.textEditMessage.setText(unicode(SUPPORT_MESSAGE, 'utf-8').format( diff --git a/src/bitmessageqt/tests/settings.py b/src/bitmessageqt/tests/settings.py index c0708b5c..0dcf8cf3 100644 --- a/src/bitmessageqt/tests/settings.py +++ b/src/bitmessageqt/tests/settings.py @@ -2,7 +2,7 @@ import threading import time from main import TestBase -from bmconfigparser import BMConfigParser +from bmconfigparser import config from bitmessageqt import settings @@ -14,14 +14,14 @@ class TestSettings(TestBase): def test_udp(self): """Test the effect of checkBoxUDP""" - udp_setting = BMConfigParser().safeGetBoolean( + udp_setting = config.safeGetBoolean( 'bitmessagesettings', 'udp') self.assertEqual(udp_setting, self.dialog.checkBoxUDP.isChecked()) self.dialog.checkBoxUDP.setChecked(not udp_setting) self.dialog.accept() self.assertEqual( not udp_setting, - BMConfigParser().safeGetBoolean('bitmessagesettings', 'udp')) + config.safeGetBoolean('bitmessagesettings', 'udp')) time.sleep(5) for thread in threading.enumerate(): if thread.name == 'Announcer': # find Announcer thread diff --git a/src/bitmessageqt/utils.py b/src/bitmessageqt/utils.py index e118f487..9f849b3b 100644 --- a/src/bitmessageqt/utils.py +++ b/src/bitmessageqt/utils.py @@ -5,7 +5,7 @@ from PyQt4 import QtGui import state from addresses import addBMIfNotPresent -from bmconfigparser import BMConfigParser +from bmconfigparser import config str_broadcast_subscribers = '[Broadcast subscribers]' str_chan = '[chan]' @@ -14,14 +14,14 @@ str_chan = '[chan]' def identiconize(address): size = 48 - if not BMConfigParser().getboolean('bitmessagesettings', 'useidenticons'): + if not config.getboolean('bitmessagesettings', 'useidenticons'): return QtGui.QIcon() # If you include another identicon library, please generate an # example identicon with the following md5 hash: # 3fd4bf901b9d4ea1394f0fb358725b28 - identicon_lib = BMConfigParser().safeGet( + identicon_lib = config.safeGet( 'bitmessagesettings', 'identiconlib', 'qidenticon_two_x') # As an 'identiconsuffix' you could put "@bitmessge.ch" or "@bm.addr" @@ -30,7 +30,7 @@ def identiconize(address): # It can be used as a pseudo-password to salt the generation of # the identicons to decrease the risk of attacks where someone creates # an address to mimic someone else's identicon. - identiconsuffix = BMConfigParser().get('bitmessagesettings', 'identiconsuffix') + identiconsuffix = config.get('bitmessagesettings', 'identiconsuffix') if identicon_lib[:len('qidenticon')] == 'qidenticon': # originally by: # :Author:Shin Adachi diff --git a/src/bmconfigparser.py b/src/bmconfigparser.py index 8bc425c8..312a6f97 100644 --- a/src/bmconfigparser.py +++ b/src/bmconfigparser.py @@ -5,6 +5,7 @@ BMConfigParser class definition and default configuration settings import os import shutil import sys # FIXME: bad style! write more generally +from threading import Event from datetime import datetime from six import string_types @@ -12,47 +13,13 @@ 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 +config_ready = Event() -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` @@ -69,49 +36,6 @@ class BMConfigParser(SafeConfigParser): raise ValueError("Invalid value %s" % value) return SafeConfigParser.set(self, section, option, value) - # pylint: disable=redefined-builtin, 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: @@ -173,33 +97,18 @@ class BMConfigParser(SafeConfigParser): for x in sections: self.remove_section(x) + def read(self, filenames=None): + self._reset() + SafeConfigParser.read(self, os.path.join(os.path.dirname(__file__), 'default.ini')) + if filenames: + SafeConfigParser.read(self, filenames) + 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: - if not self.validate( - section, option, - self[section][option] # pylint: disable=unsubscriptable-object - ): - try: - newVal = BMConfigDefaults[section][option] - except configparser.NoSectionError: - continue - except KeyError: - continue - SafeConfigParser.set( - self, section, option, newVal) - except configparser.InterpolationError: - continue + return [x for x in config.sections() if x.startswith('BM-') and ( + hidden or not config.safeGetBoolean(x, 'hidden'))] def readfp(self, fp, filename=None): # pylint: disable=no-member @@ -209,27 +118,7 @@ class BMConfigParser(SafeConfigParser): 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 + x for x in config.sections() if x.startswith('BM-')] def save(self): """Save the runtime config onto the filesystem""" @@ -242,7 +131,7 @@ class BMConfigParser(SafeConfigParser): shutil.copyfile(fileName, fileNameBak) # The backup succeeded. fileNameExisted = True - except (IOError, Exception): + except(IOError, Exception): # The backup failed. This can happen if the file # didn't exist before. fileNameExisted = False @@ -270,3 +159,6 @@ class BMConfigParser(SafeConfigParser): if value < 0 or value > 8: return False return True + + +config = BMConfigParser() diff --git a/src/build_osx.py b/src/build_osx.py index 83d2f280..3fcefbfb 100644 --- a/src/build_osx.py +++ b/src/build_osx.py @@ -9,7 +9,7 @@ version = os.getenv("PYBITMESSAGEVERSION", "custom") mainscript = ["bitmessagemain.py"] DATA_FILES = [ - ('', ['sslkeys', 'images']), + ('', ['sslkeys', 'images', 'default.ini']), ('bitmsghash', ['bitmsghash/bitmsghash.cl', 'bitmsghash/bitmsghash.so']), ('translations', glob('translations/*.qm')), ('ui', glob('bitmessageqt/*.ui')), diff --git a/src/class_addressGenerator.py b/src/class_addressGenerator.py index 25b0c5df..a308a52e 100644 --- a/src/class_addressGenerator.py +++ b/src/class_addressGenerator.py @@ -12,7 +12,7 @@ import shared import state import tr from addresses import decodeAddress, encodeAddress, encodeVarint -from bmconfigparser import BMConfigParser +from bmconfigparser import config from fallback import RIPEMD160Hash from network import StoppableThread from pyelliptic import arithmetic @@ -72,7 +72,7 @@ class addressGenerator(StoppableThread): eighteenByteRipe = queueValue numberOfNullBytesDemandedOnFrontOfRipeHash = \ - BMConfigParser().safeGetInt( + config.safeGetInt( 'bitmessagesettings', 'numberofnullbytesonaddress', 2 if eighteenByteRipe else 1 @@ -84,7 +84,7 @@ class addressGenerator(StoppableThread): payloadLengthExtraBytes = queueValue numberOfNullBytesDemandedOnFrontOfRipeHash = \ - BMConfigParser().safeGetInt( + config.safeGetInt( 'bitmessagesettings', 'numberofnullbytesonaddress', 2 if eighteenByteRipe else 1 @@ -103,14 +103,14 @@ class addressGenerator(StoppableThread): ' one version %s address which it cannot do.\n', addressVersionNumber) if nonceTrialsPerByte == 0: - nonceTrialsPerByte = BMConfigParser().getint( + nonceTrialsPerByte = config.getint( 'bitmessagesettings', 'defaultnoncetrialsperbyte') if nonceTrialsPerByte < \ defaults.networkDefaultProofOfWorkNonceTrialsPerByte: nonceTrialsPerByte = \ defaults.networkDefaultProofOfWorkNonceTrialsPerByte if payloadLengthExtraBytes == 0: - payloadLengthExtraBytes = BMConfigParser().getint( + payloadLengthExtraBytes = config.getint( 'bitmessagesettings', 'defaultpayloadlengthextrabytes') if payloadLengthExtraBytes < \ defaults.networkDefaultPayloadLengthExtraBytes: @@ -178,19 +178,19 @@ class addressGenerator(StoppableThread): privEncryptionKeyWIF = arithmetic.changebase( privEncryptionKey + checksum, 256, 58) - BMConfigParser().add_section(address) - BMConfigParser().set(address, 'label', label) - BMConfigParser().set(address, 'enabled', 'true') - BMConfigParser().set(address, 'decoy', 'false') - BMConfigParser().set(address, 'noncetrialsperbyte', str( + config.add_section(address) + config.set(address, 'label', label) + config.set(address, 'enabled', 'true') + config.set(address, 'decoy', 'false') + config.set(address, 'noncetrialsperbyte', str( nonceTrialsPerByte)) - BMConfigParser().set(address, 'payloadlengthextrabytes', str( + config.set(address, 'payloadlengthextrabytes', str( payloadLengthExtraBytes)) - BMConfigParser().set( + config.set( address, 'privsigningkey', privSigningKeyWIF) - BMConfigParser().set( + config.set( address, 'privencryptionkey', privEncryptionKeyWIF) - BMConfigParser().save() + config.save() # The API and the join and create Chan functionality # both need information back from the address generator. @@ -317,7 +317,7 @@ class addressGenerator(StoppableThread): privEncryptionKey + checksum, 256, 58) try: - BMConfigParser().add_section(address) + config.add_section(address) addressAlreadyExists = False except configparser.DuplicateSectionError: addressAlreadyExists = True @@ -337,25 +337,25 @@ class addressGenerator(StoppableThread): )) else: self.logger.debug('label: %s', label) - BMConfigParser().set(address, 'label', label) - BMConfigParser().set(address, 'enabled', 'true') - BMConfigParser().set(address, 'decoy', 'false') + config.set(address, 'label', label) + config.set(address, 'enabled', 'true') + config.set(address, 'decoy', 'false') if command == 'joinChan' \ or command == 'createChan': - BMConfigParser().set(address, 'chan', 'true') - BMConfigParser().set( + config.set(address, 'chan', 'true') + config.set( address, 'noncetrialsperbyte', str(nonceTrialsPerByte)) - BMConfigParser().set( + config.set( address, 'payloadlengthextrabytes', str(payloadLengthExtraBytes)) - BMConfigParser().set( + config.set( address, 'privSigningKey', privSigningKeyWIF) - BMConfigParser().set( + config.set( address, 'privEncryptionKey', privEncryptionKeyWIF) - BMConfigParser().save() + config.save() queues.UISignalQueue.put(( 'writeNewAddressToTable', @@ -387,7 +387,7 @@ class addressGenerator(StoppableThread): "MainWindow", "Done generating address") )) elif saveAddressToDisk and not live \ - and not BMConfigParser().has_section(address): + and not config.has_section(address): listOfNewAddressesToSendOutThroughTheAPI.append( address) diff --git a/src/class_objectProcessor.py b/src/class_objectProcessor.py index 1bacf639..bbf622e4 100644 --- a/src/class_objectProcessor.py +++ b/src/class_objectProcessor.py @@ -26,7 +26,7 @@ from addresses import ( calculateInventoryHash, decodeAddress, decodeVarint, encodeAddress, encodeVarint, varintDecodeError ) -from bmconfigparser import BMConfigParser +from bmconfigparser import config from fallback import RIPEMD160Hash from helper_sql import sql_ready, SqlBulkExecute, sqlExecute, sqlQuery from network import bmproto, knownnodes @@ -98,7 +98,7 @@ class objectProcessor(threading.Thread): 'The object is too big after decompression (stopped' ' decompressing at %ib, your configured limit %ib).' ' Ignoring', - e.size, BMConfigParser().safeGetInt('zlib', 'maxsize')) + e.size, config.safeGetInt('zlib', 'maxsize')) except varintDecodeError as e: logger.debug( 'There was a problem with a varint while processing an' @@ -242,12 +242,12 @@ class objectProcessor(threading.Thread): ' one of my pubkeys but the stream number on which we' ' heard this getpubkey object doesn\'t match this' ' address\' stream number. Ignoring.') - if BMConfigParser().safeGetBoolean(myAddress, 'chan'): + if config.safeGetBoolean(myAddress, 'chan'): return logger.info( 'Ignoring getpubkey request because it is for one of my' ' chan addresses. The other party should already have' ' the pubkey.') - lastPubkeySendTime = BMConfigParser().safeGetInt( + lastPubkeySendTime = config.safeGetInt( myAddress, 'lastpubkeysendtime') # If the last time we sent our pubkey was more recent than # 28 days ago... @@ -613,13 +613,13 @@ class objectProcessor(threading.Thread): # If the toAddress version number is 3 or higher and not one of # my chan addresses: if decodeAddress(toAddress)[1] >= 3 \ - and not BMConfigParser().safeGetBoolean(toAddress, 'chan'): + and not config.safeGetBoolean(toAddress, 'chan'): # If I'm not friendly with this person: if not shared.isAddressInMyAddressBookSubscriptionsListOrWhitelist( fromAddress): - requiredNonceTrialsPerByte = BMConfigParser().getint( + requiredNonceTrialsPerByte = config.getint( toAddress, 'noncetrialsperbyte') - requiredPayloadLengthExtraBytes = BMConfigParser().getint( + requiredPayloadLengthExtraBytes = config.getint( toAddress, 'payloadlengthextrabytes') if not protocol.isProofOfWorkSufficient( data, requiredNonceTrialsPerByte, @@ -631,7 +631,7 @@ class objectProcessor(threading.Thread): # to black or white lists. blockMessage = False # If we are using a blacklist - if BMConfigParser().get( + if config.get( 'bitmessagesettings', 'blackwhitelist') == 'black': queryreturn = sqlQuery( "SELECT label FROM blacklist where address=? and enabled='1'", @@ -649,7 +649,7 @@ class objectProcessor(threading.Thread): 'Message ignored because address not in whitelist.') blockMessage = True - # toLabel = BMConfigParser().safeGet(toAddress, 'label', toAddress) + # toLabel = config.safeGet(toAddress, 'label', toAddress) try: decodedMessage = helper_msgcoding.MsgDecode( messageEncodingType, message) @@ -675,18 +675,18 @@ class objectProcessor(threading.Thread): # If we are behaving as an API then we might need to run an # outside command to let some program know that a new message # has arrived. - if BMConfigParser().safeGetBoolean( + if config.safeGetBoolean( 'bitmessagesettings', 'apienabled'): - apiNotifyPath = BMConfigParser().safeGet( + apiNotifyPath = config.safeGet( 'bitmessagesettings', 'apinotifypath') if apiNotifyPath: subprocess.call([apiNotifyPath, "newMessage"]) # Let us now check and see whether our receiving address is # behaving as a mailing list - if BMConfigParser().safeGetBoolean(toAddress, 'mailinglist') \ + if config.safeGetBoolean(toAddress, 'mailinglist') \ and messageEncodingType != 0: - mailingListName = BMConfigParser().safeGet( + mailingListName = config.safeGet( toAddress, 'mailinglistname', '') # Let us send out this message as a broadcast subject = self.addMailingListNameToSubject( @@ -724,8 +724,8 @@ class objectProcessor(threading.Thread): if ( self.ackDataHasAValidHeader(ackData) and not blockMessage and messageEncodingType != 0 - and not BMConfigParser().safeGetBoolean(toAddress, 'dontsendack') - and not BMConfigParser().safeGetBoolean(toAddress, 'chan') + and not config.safeGetBoolean(toAddress, 'dontsendack') + and not config.safeGetBoolean(toAddress, 'chan') ): self._ack_obj.send_data(ackData[24:]) @@ -960,8 +960,8 @@ class objectProcessor(threading.Thread): # If we are behaving as an API then we might need to run an # outside command to let some program know that a new message # has arrived. - if BMConfigParser().safeGetBoolean('bitmessagesettings', 'apienabled'): - apiNotifyPath = BMConfigParser().safeGet( + if config.safeGetBoolean('bitmessagesettings', 'apienabled'): + apiNotifyPath = config.safeGet( 'bitmessagesettings', 'apinotifypath') if apiNotifyPath: subprocess.call([apiNotifyPath, "newBroadcast"]) diff --git a/src/class_singleCleaner.py b/src/class_singleCleaner.py index 3f3f8ec0..dd7e6bc1 100644 --- a/src/class_singleCleaner.py +++ b/src/class_singleCleaner.py @@ -25,7 +25,7 @@ import time import queues import state -from bmconfigparser import BMConfigParser +from bmconfigparser import config from helper_sql import sqlExecute, sqlQuery from inventory import Inventory from network import BMConnectionPool, knownnodes, StoppableThread @@ -50,11 +50,11 @@ class singleCleaner(StoppableThread): timeWeLastClearedInventoryAndPubkeysTables = 0 try: state.maximumLengthOfTimeToBotherResendingMessages = ( - BMConfigParser().getfloat( + config.getfloat( 'bitmessagesettings', 'stopresendingafterxdays') * 24 * 60 * 60 ) + ( - BMConfigParser().getfloat( + config.getfloat( 'bitmessagesettings', 'stopresendingafterxmonths') * (60 * 60 * 24 * 365) / 12) except: # noqa:E722 diff --git a/src/class_singleWorker.py b/src/class_singleWorker.py index fea842ea..5b97c536 100644 --- a/src/class_singleWorker.py +++ b/src/class_singleWorker.py @@ -28,7 +28,7 @@ import tr from addresses import ( calculateInventoryHash, decodeAddress, decodeVarint, encodeVarint ) -from bmconfigparser import BMConfigParser +from bmconfigparser import config from helper_sql import sqlExecute, sqlQuery from inventory import Inventory from network import knownnodes, StoppableThread @@ -195,9 +195,9 @@ class singleWorker(StoppableThread): self.logger.info("Quitting...") def _getKeysForAddress(self, address): - privSigningKeyBase58 = BMConfigParser().get( + privSigningKeyBase58 = config.get( address, 'privsigningkey') - privEncryptionKeyBase58 = BMConfigParser().get( + privEncryptionKeyBase58 = config.get( address, 'privencryptionkey') privSigningKeyHex = hexlify(shared.decodeWalletImportFormat( @@ -299,15 +299,15 @@ class singleWorker(StoppableThread): queues.invQueue.put((streamNumber, inventoryHash)) queues.UISignalQueue.put(('updateStatusBar', '')) try: - BMConfigParser().set( + config.set( myAddress, 'lastpubkeysendtime', str(int(time.time()))) - BMConfigParser().save() + config.save() except configparser.NoSectionError: # The user deleted the address out of the keys.dat file # before this finished. pass except: # noqa:E722 - self.logger.warning("BMConfigParser().set didn't work") + self.logger.warning("config.set didn't work") def sendOutOrStoreMyV3Pubkey(self, adressHash): """ @@ -321,7 +321,7 @@ class singleWorker(StoppableThread): # The address has been deleted. self.logger.warning("Can't find %s in myAddressByHash", hexlify(adressHash)) return - if BMConfigParser().safeGetBoolean(myAddress, 'chan'): + if config.safeGetBoolean(myAddress, 'chan'): self.logger.info('This is a chan address. Not sending pubkey.') return _, addressVersionNumber, streamNumber, adressHash = decodeAddress( @@ -363,9 +363,9 @@ class singleWorker(StoppableThread): payload += pubSigningKey + pubEncryptionKey - payload += encodeVarint(BMConfigParser().getint( + payload += encodeVarint(config.getint( myAddress, 'noncetrialsperbyte')) - payload += encodeVarint(BMConfigParser().getint( + payload += encodeVarint(config.getint( myAddress, 'payloadlengthextrabytes')) signature = highlevelcrypto.sign(payload, privSigningKeyHex) @@ -387,9 +387,9 @@ class singleWorker(StoppableThread): queues.invQueue.put((streamNumber, inventoryHash)) queues.UISignalQueue.put(('updateStatusBar', '')) try: - BMConfigParser().set( + config.set( myAddress, 'lastpubkeysendtime', str(int(time.time()))) - BMConfigParser().save() + config.save() except configparser.NoSectionError: # The user deleted the address out of the keys.dat file # before this finished. @@ -403,10 +403,10 @@ class singleWorker(StoppableThread): whereas in the past it directly appended it to the outgoing buffer, I think. Same with all the other methods in this class. """ - if not BMConfigParser().has_section(myAddress): + if not config.has_section(myAddress): # The address has been deleted. return - if shared.BMConfigParser().safeGetBoolean(myAddress, 'chan'): + if config.safeGetBoolean(myAddress, 'chan'): self.logger.info('This is a chan address. Not sending pubkey.') return _, addressVersionNumber, streamNumber, addressHash = decodeAddress( @@ -437,9 +437,9 @@ class singleWorker(StoppableThread): dataToEncrypt += pubSigningKey + pubEncryptionKey - dataToEncrypt += encodeVarint(BMConfigParser().getint( + dataToEncrypt += encodeVarint(config.getint( myAddress, 'noncetrialsperbyte')) - dataToEncrypt += encodeVarint(BMConfigParser().getint( + dataToEncrypt += encodeVarint(config.getint( myAddress, 'payloadlengthextrabytes')) # When we encrypt, we'll use a hash of the data @@ -482,9 +482,9 @@ class singleWorker(StoppableThread): queues.invQueue.put((streamNumber, inventoryHash)) queues.UISignalQueue.put(('updateStatusBar', '')) try: - BMConfigParser().set( + config.set( myAddress, 'lastpubkeysendtime', str(int(time.time()))) - BMConfigParser().save() + config.save() except Exception as err: self.logger.error( 'Error: Couldn\'t add the lastpubkeysendtime' @@ -628,9 +628,9 @@ class singleWorker(StoppableThread): dataToEncrypt += protocol.getBitfield(fromaddress) dataToEncrypt += pubSigningKey + pubEncryptionKey if addressVersionNumber >= 3: - dataToEncrypt += encodeVarint(BMConfigParser().getint( + dataToEncrypt += encodeVarint(config.getint( fromaddress, 'noncetrialsperbyte')) - dataToEncrypt += encodeVarint(BMConfigParser().getint( + dataToEncrypt += encodeVarint(config.getint( fromaddress, 'payloadlengthextrabytes')) # message encoding type dataToEncrypt += encodeVarint(encoding) @@ -756,7 +756,7 @@ class singleWorker(StoppableThread): # then we won't need an entry in the pubkeys table; # we can calculate the needed pubkey using the private keys # in our keys.dat file. - elif BMConfigParser().has_section(toaddress): + elif config.has_section(toaddress): if not sqlExecute( '''UPDATE sent SET status='doingmsgpow' ''' ''' WHERE toaddress=? AND status='msgqueued' AND folder='sent' ''', @@ -905,7 +905,7 @@ class singleWorker(StoppableThread): embeddedTime = int(time.time() + TTL) # if we aren't sending this to ourselves or a chan - if not BMConfigParser().has_section(toaddress): + if not config.has_section(toaddress): state.ackdataForWhichImWatching[ackdata] = 0 queues.UISignalQueue.put(( 'updateSentItemStatusByAckdata', ( @@ -956,7 +956,7 @@ class singleWorker(StoppableThread): if protocol.isBitSetWithinBitfield(behaviorBitfield, 30): # if we are Not willing to include the receiver's # RIPE hash on the message.. - if not shared.BMConfigParser().safeGetBoolean( + if not config.safeGetBoolean( 'bitmessagesettings', 'willinglysendtomobile' ): self.logger.info( @@ -1058,9 +1058,9 @@ class singleWorker(StoppableThread): ) if status != 'forcepow': - maxacceptablenoncetrialsperbyte = BMConfigParser().getint( + maxacceptablenoncetrialsperbyte = config.getint( 'bitmessagesettings', 'maxacceptablenoncetrialsperbyte') - maxacceptablepayloadlengthextrabytes = BMConfigParser().getint( + maxacceptablepayloadlengthextrabytes = config.getint( 'bitmessagesettings', 'maxacceptablepayloadlengthextrabytes') cond1 = maxacceptablenoncetrialsperbyte and \ requiredAverageProofOfWorkNonceTrialsPerByte > maxacceptablenoncetrialsperbyte @@ -1096,7 +1096,7 @@ class singleWorker(StoppableThread): behaviorBitfield = protocol.getBitfield(fromaddress) try: - privEncryptionKeyBase58 = BMConfigParser().get( + privEncryptionKeyBase58 = config.get( toaddress, 'privencryptionkey') except (configparser.NoSectionError, configparser.NoOptionError) as err: queues.UISignalQueue.put(( @@ -1185,9 +1185,9 @@ class singleWorker(StoppableThread): payload += encodeVarint( defaults.networkDefaultPayloadLengthExtraBytes) else: - payload += encodeVarint(BMConfigParser().getint( + payload += encodeVarint(config.getint( fromaddress, 'noncetrialsperbyte')) - payload += encodeVarint(BMConfigParser().getint( + payload += encodeVarint(config.getint( fromaddress, 'payloadlengthextrabytes')) # This hash will be checked by the receiver of the message @@ -1200,7 +1200,7 @@ class singleWorker(StoppableThread): ) payload += encodeVarint(encodedMessage.length) payload += encodedMessage.data - if BMConfigParser().has_section(toaddress): + if config.has_section(toaddress): self.logger.info( 'Not bothering to include ackdata because we are' ' sending to ourselves or a chan.' @@ -1305,7 +1305,7 @@ class singleWorker(StoppableThread): objectType = 2 Inventory()[inventoryHash] = ( objectType, toStreamNumber, encryptedPayload, embeddedTime, '') - if BMConfigParser().has_section(toaddress) or \ + if config.has_section(toaddress) or \ not protocol.checkBitfield(behaviorBitfield, protocol.BITFIELD_DOESACK): queues.UISignalQueue.put(( 'updateSentItemStatusByAckdata', ( @@ -1333,7 +1333,7 @@ class singleWorker(StoppableThread): # Update the sent message in the sent table with the # necessary information. - if BMConfigParser().has_section(toaddress) or \ + if config.has_section(toaddress) or \ not protocol.checkBitfield(behaviorBitfield, protocol.BITFIELD_DOESACK): newStatus = 'msgsentnoackexpected' else: @@ -1349,7 +1349,7 @@ class singleWorker(StoppableThread): # If we are sending to ourselves or a chan, let's put # the message in our own inbox. - if BMConfigParser().has_section(toaddress): + if config.has_section(toaddress): # Used to detect and ignore duplicate messages in our inbox sigHash = hashlib.sha512(hashlib.sha512( signature).digest()).digest()[32:] @@ -1363,10 +1363,10 @@ class singleWorker(StoppableThread): # If we are behaving as an API then we might need to run an # outside command to let some program know that a new message # has arrived. - if BMConfigParser().safeGetBoolean( + if config.safeGetBoolean( 'bitmessagesettings', 'apienabled'): - apiNotifyPath = BMConfigParser().safeGet( + apiNotifyPath = config.safeGet( 'bitmessagesettings', 'apinotifypath') if apiNotifyPath: diff --git a/src/class_smtpDeliver.py b/src/class_smtpDeliver.py index 08cb35ab..ad509bbf 100644 --- a/src/class_smtpDeliver.py +++ b/src/class_smtpDeliver.py @@ -10,7 +10,7 @@ from email.mime.text import MIMEText import queues import state -from bmconfigparser import BMConfigParser +from bmconfigparser import config from network.threads import StoppableThread SMTPDOMAIN = "bmaddr.lan" @@ -51,7 +51,7 @@ class smtpDeliver(StoppableThread): ackData, message = data elif command == 'displayNewInboxMessage': inventoryHash, toAddress, fromAddress, subject, body = data - dest = BMConfigParser().safeGet("bitmessagesettings", "smtpdeliver", '') + dest = config.safeGet("bitmessagesettings", "smtpdeliver", '') if dest == '': continue try: @@ -62,9 +62,9 @@ class smtpDeliver(StoppableThread): msg['Subject'] = Header(subject, 'utf-8') msg['From'] = fromAddress + '@' + SMTPDOMAIN toLabel = map( - lambda y: BMConfigParser().safeGet(y, "label"), + lambda y: config.safeGet(y, "label"), filter( - lambda x: x == toAddress, BMConfigParser().addresses()) + lambda x: x == toAddress, config.addresses()) ) if toLabel: msg['To'] = "\"%s\" <%s>" % (Header(toLabel[0], 'utf-8'), toAddress + '@' + SMTPDOMAIN) diff --git a/src/class_smtpServer.py b/src/class_smtpServer.py index f5b63c2e..44ea7c9c 100644 --- a/src/class_smtpServer.py +++ b/src/class_smtpServer.py @@ -15,7 +15,7 @@ from email.parser import Parser import queues from addresses import decodeAddress -from bmconfigparser import BMConfigParser +from bmconfigparser import config from helper_ackPayload import genAckPayload from helper_sql import sqlExecute from network.threads import StoppableThread @@ -51,8 +51,8 @@ class smtpServerChannel(smtpd.SMTPChannel): authstring = arg[6:] try: decoded = base64.b64decode(authstring) - correctauth = "\x00" + BMConfigParser().safeGet( - "bitmessagesettings", "smtpdusername", "") + "\x00" + BMConfigParser().safeGet( + correctauth = "\x00" + config.safeGet( + "bitmessagesettings", "smtpdusername", "") + "\x00" + config.safeGet( "bitmessagesettings", "smtpdpassword", "") logger.debug('authstring: %s / %s', correctauth, decoded) if correctauth == decoded: @@ -84,7 +84,7 @@ class smtpServerPyBitmessage(smtpd.SMTPServer): """Send a bitmessage""" # pylint: disable=arguments-differ streamNumber, ripe = decodeAddress(toAddress)[2:] - stealthLevel = BMConfigParser().safeGetInt('bitmessagesettings', 'ackstealthlevel') + stealthLevel = config.safeGetInt('bitmessagesettings', 'ackstealthlevel') ackdata = genAckPayload(streamNumber, stealthLevel) sqlExecute( '''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''', @@ -103,7 +103,7 @@ class smtpServerPyBitmessage(smtpd.SMTPServer): 'sent', # folder 2, # encodingtype # not necessary to have a TTL higher than 2 days - min(BMConfigParser().getint('bitmessagesettings', 'ttl'), 86400 * 2) + min(config.getint('bitmessagesettings', 'ttl'), 86400 * 2) ) queues.workerQueue.put(('sendmessage', toAddress)) @@ -136,7 +136,7 @@ class smtpServerPyBitmessage(smtpd.SMTPServer): sender, domain = p.sub(r'\1', mailfrom).split("@") if domain != SMTPDOMAIN: raise Exception("Bad domain %s" % domain) - if sender not in BMConfigParser().addresses(): + if sender not in config.addresses(): raise Exception("Nonexisting user %s" % sender) except Exception as err: logger.debug('Bad envelope from %s: %r', mailfrom, err) @@ -146,7 +146,7 @@ class smtpServerPyBitmessage(smtpd.SMTPServer): sender, domain = msg_from.split("@") if domain != SMTPDOMAIN: raise Exception("Bad domain %s" % domain) - if sender not in BMConfigParser().addresses(): + if sender not in config.addresses(): raise Exception("Nonexisting user %s" % sender) except Exception as err: logger.error('Bad headers from %s: %r', msg_from, err) diff --git a/src/class_sqlThread.py b/src/class_sqlThread.py index d22ffadb..7df9e253 100644 --- a/src/class_sqlThread.py +++ b/src/class_sqlThread.py @@ -16,13 +16,13 @@ try: import queues import state from addresses import encodeAddress - from bmconfigparser import BMConfigParser + from bmconfigparser import config, config_ready from debug import logger from tr import _translate except ImportError: from . import helper_sql, helper_startup, paths, queues, state from .addresses import encodeAddress - from .bmconfigparser import BMConfigParser + from .bmconfigparser import config, config_ready from .debug import logger from .tr import _translate @@ -36,6 +36,7 @@ class sqlThread(threading.Thread): def run(self): # pylint: disable=too-many-locals, too-many-branches, too-many-statements """Process SQL queries from `.helper_sql.sqlSubmitQueue`""" helper_sql.sql_available = True + config_ready.wait() self.conn = sqlite3.connect(state.appdata + 'messages.dat') self.conn.text_factory = str self.cur = self.conn.cursor() @@ -93,7 +94,7 @@ class sqlThread(threading.Thread): # If the settings version is equal to 2 or 3 then the # sqlThread will modify the pubkeys table and change # the settings version to 4. - settingsversion = BMConfigParser().getint( + settingsversion = config.getint( 'bitmessagesettings', 'settingsversion') # People running earlier versions of PyBitmessage do not have the @@ -125,9 +126,9 @@ class sqlThread(threading.Thread): settingsversion = 4 - BMConfigParser().set( + config.set( 'bitmessagesettings', 'settingsversion', str(settingsversion)) - BMConfigParser().save() + config.save() helper_startup.updateConfig() diff --git a/src/default.ini b/src/default.ini new file mode 100644 index 00000000..fbf731f8 --- /dev/null +++ b/src/default.ini @@ -0,0 +1,47 @@ +[bitmessagesettings] +maxaddrperstreamsend = 500 +maxbootstrapconnections = 20 +maxdownloadrate = 0 +maxoutboundconnections = 8 +maxtotalconnections = 200 +maxuploadrate = 0 +apiinterface = 127.0.0.1 +apiport = 8442 +udp = True +port = 8444 +timeformat = %%c +blackwhitelist = black +startonlogon = False +showtraynotifications = True +startintray = False +socksproxytype = none +sockshostname = localhost +socksport = 9050 +socksauthentication = False +socksusername = +sockspassword = +keysencrypted = False +messagesencrypted = False +minimizeonclose = False +dontconnect = True +replybelow = False +stopresendingafterxdays = +stopresendingafterxmonths = +opencl = + +[threads] +receive = 3 + +[network] +bind = +dandelion = 90 + +[inventory] +storage = sqlite +acceptmismatch = False + +[knownnodes] +maxnodes = 20000 + +[zlib] +maxsize = 1048576 diff --git a/src/helper_addressbook.py b/src/helper_addressbook.py index fb572150..6d354113 100644 --- a/src/helper_addressbook.py +++ b/src/helper_addressbook.py @@ -2,13 +2,13 @@ Insert value into addressbook """ -from bmconfigparser import BMConfigParser +from bmconfigparser import config from helper_sql import sqlExecute def insert(address, label): """perform insert into addressbook""" - if address not in BMConfigParser().addresses(): + if address not in config.addresses(): return sqlExecute('''INSERT INTO addressbook VALUES (?,?)''', label, address) == 1 return False diff --git a/src/helper_msgcoding.py b/src/helper_msgcoding.py index 28f92288..05fa1c1b 100644 --- a/src/helper_msgcoding.py +++ b/src/helper_msgcoding.py @@ -6,7 +6,7 @@ import string import zlib import messagetypes -from bmconfigparser import BMConfigParser +from bmconfigparser import config from debug import logger from tr import _translate @@ -100,10 +100,10 @@ class MsgDecode(object): """Handle extended encoding""" dc = zlib.decompressobj() tmp = "" - while len(tmp) <= BMConfigParser().safeGetInt("zlib", "maxsize"): + while len(tmp) <= config.safeGetInt("zlib", "maxsize"): try: got = dc.decompress( - data, BMConfigParser().safeGetInt("zlib", "maxsize") + data, config.safeGetInt("zlib", "maxsize") + 1 - len(tmp)) # EOF if got == "": diff --git a/src/helper_sent.py b/src/helper_sent.py index d83afce6..1fc98de2 100644 --- a/src/helper_sent.py +++ b/src/helper_sent.py @@ -5,7 +5,7 @@ Insert values into sent table import time import uuid from addresses import decodeAddress -from bmconfigparser import BMConfigParser +from bmconfigparser import config from helper_ackPayload import genAckPayload from helper_sql import sqlExecute @@ -27,7 +27,7 @@ def insert(msgid=None, toAddress='[Broadcast subscribers]', fromAddress=None, su ripe = new_ripe if not ackdata: - stealthLevel = BMConfigParser().safeGetInt( + stealthLevel = config.safeGetInt( 'bitmessagesettings', 'ackstealthlevel') new_ackdata = genAckPayload(streamNumber, stealthLevel) ackdata = new_ackdata @@ -36,7 +36,7 @@ def insert(msgid=None, toAddress='[Broadcast subscribers]', fromAddress=None, su sentTime = sentTime if sentTime else int(time.time()) # sentTime (this doesn't change) lastActionTime = lastActionTime if lastActionTime else int(time.time()) - ttl = ttl if ttl else BMConfigParser().getint('bitmessagesettings', 'ttl') + ttl = ttl if ttl else config.getint('bitmessagesettings', 'ttl') t = (msgid, toAddress, ripe, fromAddress, subject, message, ackdata, sentTime, lastActionTime, sleeptill, status, retryNumber, folder, diff --git a/src/helper_startup.py b/src/helper_startup.py index b4951668..c8a56c09 100644 --- a/src/helper_startup.py +++ b/src/helper_startup.py @@ -18,10 +18,11 @@ try: import helper_random import paths import state - from bmconfigparser import BMConfigParser + from bmconfigparser import config, config_ready + except ImportError: from . import defaults, helper_random, paths, state - from .bmconfigparser import BMConfigParser + from bmconfigparser import config, config_ready try: from plugins.plugin import get_plugin @@ -38,7 +39,6 @@ StoreConfigFilesInSameDirectoryAsProgramByDefault = False def loadConfig(): """Load the config""" - config = BMConfigParser() if state.appdata: config.read(state.appdata + 'keys.dat') # state.appdata must have been specified as a startup option. @@ -70,12 +70,9 @@ def loadConfig(): # This appears to be the first time running the program; there is # no config file (or it cannot be accessed). Create config file. - config.add_section('bitmessagesettings') + # config.add_section('bitmessagesettings') + config.read() config.set('bitmessagesettings', 'settingsversion', '10') - config.set('bitmessagesettings', 'port', '8444') - config.set('bitmessagesettings', 'timeformat', '%%c') - config.set('bitmessagesettings', 'blackwhitelist', 'black') - config.set('bitmessagesettings', 'startonlogon', 'false') if 'linux' in sys.platform: config.set('bitmessagesettings', 'minimizetotray', 'false') # This isn't implimented yet and when True on @@ -83,31 +80,16 @@ def loadConfig(): # running when minimized. else: config.set('bitmessagesettings', 'minimizetotray', 'true') - config.set('bitmessagesettings', 'showtraynotifications', 'true') - config.set('bitmessagesettings', 'startintray', 'false') - config.set('bitmessagesettings', 'socksproxytype', 'none') - config.set('bitmessagesettings', 'sockshostname', 'localhost') - config.set('bitmessagesettings', 'socksport', '9050') - config.set('bitmessagesettings', 'socksauthentication', 'false') - config.set('bitmessagesettings', 'socksusername', '') - config.set('bitmessagesettings', 'sockspassword', '') - config.set('bitmessagesettings', 'keysencrypted', 'false') - config.set('bitmessagesettings', 'messagesencrypted', 'false') config.set( 'bitmessagesettings', 'defaultnoncetrialsperbyte', str(defaults.networkDefaultProofOfWorkNonceTrialsPerByte)) config.set( 'bitmessagesettings', 'defaultpayloadlengthextrabytes', str(defaults.networkDefaultPayloadLengthExtraBytes)) - config.set('bitmessagesettings', 'minimizeonclose', 'false') - config.set('bitmessagesettings', 'dontconnect', 'true') - config.set('bitmessagesettings', 'replybelow', 'False') - config.set('bitmessagesettings', 'maxdownloadrate', '0') - config.set('bitmessagesettings', 'maxuploadrate', '0') # UI setting to stop trying to send messages after X days/months - config.set('bitmessagesettings', 'stopresendingafterxdays', '') - config.set('bitmessagesettings', 'stopresendingafterxmonths', '') + # config.set('bitmessagesettings', 'stopresendingafterxdays', '') + # config.set('bitmessagesettings', 'stopresendingafterxmonths', '') # Are you hoping to add a new option to the keys.dat file? You're in # the right place for adding it to users who install the software for @@ -130,11 +112,11 @@ def loadConfig(): config.save() else: updateConfig() + config_ready.set() def updateConfig(): """Save the config""" - config = BMConfigParser() settingsversion = config.getint('bitmessagesettings', 'settingsversion') if settingsversion == 1: config.set('bitmessagesettings', 'socksproxytype', 'none') @@ -286,7 +268,7 @@ def updateConfig(): def adjustHalfOpenConnectionsLimit(): """Check and satisfy half-open connections limit (mainly XP and Vista)""" - if BMConfigParser().safeGet( + if config.safeGet( 'bitmessagesettings', 'socksproxytype', 'none') != 'none': state.maximumNumberOfHalfOpenConnections = 4 return @@ -373,7 +355,7 @@ def start_proxyconfig(): """Check socksproxytype and start any proxy configuration plugin""" if not get_plugin: return - config = BMConfigParser() + config_ready.wait() proxy_type = config.safeGet('bitmessagesettings', 'socksproxytype') if proxy_type and proxy_type not in ('none', 'SOCKS4a', 'SOCKS5'): try: diff --git a/src/highlevelcrypto.py b/src/highlevelcrypto.py index 82743acf..356cded7 100644 --- a/src/highlevelcrypto.py +++ b/src/highlevelcrypto.py @@ -13,7 +13,7 @@ import pyelliptic from pyelliptic import OpenSSL from pyelliptic import arithmetic as a -from bmconfigparser import BMConfigParser +from bmconfigparser import config __all__ = ['encrypt', 'makeCryptor', 'pointMult', 'privToPub', 'sign', 'verify'] @@ -72,7 +72,7 @@ def sign(msg, hexPrivkey): Signs with hex private key using SHA1 or SHA256 depending on "digestalg" setting """ - digestAlg = BMConfigParser().safeGet( + digestAlg = config.safeGet( 'bitmessagesettings', 'digestalg', 'sha256') if digestAlg == "sha1": # SHA1, this will eventually be deprecated diff --git a/src/inventory.py b/src/inventory.py index fc06e455..985f1382 100644 --- a/src/inventory.py +++ b/src/inventory.py @@ -3,7 +3,7 @@ # TODO make this dynamic, and watch out for frozen, like with messagetypes import storage.filesystem import storage.sqlite -from bmconfigparser import BMConfigParser +from bmconfigparser import config from singleton import Singleton @@ -14,7 +14,7 @@ class Inventory(): to manage the inventory. """ def __init__(self): - self._moduleName = BMConfigParser().safeGet("inventory", "storage") + self._moduleName = config.safeGet("inventory", "storage") self._inventoryClass = getattr( getattr(storage, self._moduleName), "{}Inventory".format(self._moduleName.title()) diff --git a/src/l10n.py b/src/l10n.py index 3b16f0b6..fe02d3f4 100644 --- a/src/l10n.py +++ b/src/l10n.py @@ -8,7 +8,7 @@ import time from six.moves import range -from bmconfigparser import BMConfigParser +from bmconfigparser import config logger = logging.getLogger('default') @@ -53,7 +53,7 @@ windowsLanguageMap = { } -time_format = BMConfigParser().safeGet( +time_format = config.safeGet( 'bitmessagesettings', 'timeformat', DEFAULT_TIME_FORMAT) if not re.search(r'\d', time.strftime(time_format)): @@ -125,7 +125,7 @@ def formatTimestamp(timestamp=None): def getTranslationLanguage(): """Return the user's language choice""" - userlocale = BMConfigParser().safeGet( + userlocale = config.safeGet( 'bitmessagesettings', 'userlocale', 'system') return userlocale if userlocale and userlocale != 'system' else language diff --git a/src/mock/class_addressGenerator.py b/src/mock/class_addressGenerator.py index fbb34710..c84b92d5 100644 --- a/src/mock/class_addressGenerator.py +++ b/src/mock/class_addressGenerator.py @@ -11,7 +11,7 @@ from six.moves import queue from pybitmessage import state from pybitmessage import queues -from pybitmessage.bmconfigparser import BMConfigParser +from pybitmessage.bmconfigparser import config # from network.threads import StoppableThread @@ -83,14 +83,14 @@ class FakeAddressGenerator(StoppableThread): 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( + config.add_section(address) + config.set(address, 'label', label) + config.set(address, 'enabled', 'true') + config.set( address, 'privsigningkey', fake_addresses[address]['privsigningkey']) - BMConfigParser().set( + config.set( address, 'privencryptionkey', fake_addresses[address]['privencryptionkey']) - BMConfigParser().save() + config.save() queues.addressGeneratorQueue.task_done() except IndexError: diff --git a/src/namecoin.py b/src/namecoin.py index 33d39070..a16cb3d7 100644 --- a/src/namecoin.py +++ b/src/namecoin.py @@ -12,7 +12,7 @@ import sys import defaults from addresses import decodeAddress -from bmconfigparser import BMConfigParser +from bmconfigparser import config from debug import logger from tr import _translate # translate @@ -52,15 +52,15 @@ class namecoinConnection(object): actually changing the values (yet). """ if options is None: - self.nmctype = BMConfigParser().get( + self.nmctype = config.get( configSection, "namecoinrpctype") - self.host = BMConfigParser().get( + self.host = config.get( configSection, "namecoinrpchost") - self.port = int(BMConfigParser().get( + self.port = int(config.get( configSection, "namecoinrpcport")) - self.user = BMConfigParser().get( + self.user = config.get( configSection, "namecoinrpcuser") - self.password = BMConfigParser().get( + self.password = config.get( configSection, "namecoinrpcpassword") else: self.nmctype = options["type"] @@ -321,14 +321,14 @@ def ensureNamecoinOptions(): that aren't there. """ - if not BMConfigParser().has_option(configSection, "namecoinrpctype"): - BMConfigParser().set(configSection, "namecoinrpctype", "namecoind") - if not BMConfigParser().has_option(configSection, "namecoinrpchost"): - BMConfigParser().set(configSection, "namecoinrpchost", "localhost") + if not config.has_option(configSection, "namecoinrpctype"): + config.set(configSection, "namecoinrpctype", "namecoind") + if not config.has_option(configSection, "namecoinrpchost"): + config.set(configSection, "namecoinrpchost", "localhost") - hasUser = BMConfigParser().has_option(configSection, "namecoinrpcuser") - hasPass = BMConfigParser().has_option(configSection, "namecoinrpcpassword") - hasPort = BMConfigParser().has_option(configSection, "namecoinrpcport") + hasUser = config.has_option(configSection, "namecoinrpcuser") + hasPass = config.has_option(configSection, "namecoinrpcpassword") + hasPort = config.has_option(configSection, "namecoinrpcport") # Try to read user/password from .namecoin configuration file. defaultUser = "" @@ -364,11 +364,10 @@ def ensureNamecoinOptions(): # If still nothing found, set empty at least. if not hasUser: - BMConfigParser().set(configSection, "namecoinrpcuser", defaultUser) + config.set(configSection, "namecoinrpcuser", defaultUser) if not hasPass: - BMConfigParser().set(configSection, "namecoinrpcpassword", defaultPass) + config.set(configSection, "namecoinrpcpassword", defaultPass) # Set default port now, possibly to found value. if not hasPort: - BMConfigParser().set(configSection, "namecoinrpcport", - defaults.namecoinDefaultRpcPort) + config.set(configSection, "namecoinrpcport", defaults.namecoinDefaultRpcPort) diff --git a/src/network/announcethread.py b/src/network/announcethread.py index e34ed963..6e18e661 100644 --- a/src/network/announcethread.py +++ b/src/network/announcethread.py @@ -4,7 +4,7 @@ Announce myself (node address) import time import state -from bmconfigparser import BMConfigParser +from bmconfigparser import config from network.assemble import assemble_addr from network.connectionpool import BMConnectionPool from node import Peer @@ -37,7 +37,7 @@ class AnnounceThread(StoppableThread): stream, Peer( '127.0.0.1', - BMConfigParser().safeGetInt( + config.safeGetInt( 'bitmessagesettings', 'port')), time.time()) connection.append_write_buf(assemble_addr([addr])) diff --git a/src/network/bmproto.py b/src/network/bmproto.py index 008eadb0..1295bd34 100644 --- a/src/network/bmproto.py +++ b/src/network/bmproto.py @@ -16,7 +16,7 @@ import connectionpool import knownnodes import protocol import state -from bmconfigparser import BMConfigParser +from bmconfigparser import config from inventory import Inventory from network.advanceddispatcher import AdvancedDispatcher from network.bmobject import ( @@ -403,7 +403,7 @@ class BMProto(AdvancedDispatcher, ObjectTracker): try: self.object.checkStream() except BMObjectUnwantedStreamError: - acceptmismatch = BMConfigParser().get( + acceptmismatch = config.get( "inventory", "acceptmismatch") BMProto.stopDownloadingObject( self.object.inventoryHash, acceptmismatch) @@ -619,9 +619,9 @@ class BMProto(AdvancedDispatcher, ObjectTracker): Peer(self.destination.host, self.peerNode.port) in connectionpool.BMConnectionPool().inboundConnections or len(connectionpool.BMConnectionPool()) - > BMConfigParser().safeGetInt( + > config.safeGetInt( 'bitmessagesettings', 'maxtotalconnections') - + BMConfigParser().safeGetInt( + + config.safeGetInt( 'bitmessagesettings', 'maxbootstrapconnections') ): self.append_write_buf(protocol.assembleErrorMessage( diff --git a/src/network/connectionchooser.py b/src/network/connectionchooser.py index c31bbb6a..db6f0ff8 100644 --- a/src/network/connectionchooser.py +++ b/src/network/connectionchooser.py @@ -8,7 +8,7 @@ import random # nosec import knownnodes import protocol import state -from bmconfigparser import BMConfigParser +from bmconfigparser import config from queues import queue, portCheckerQueue logger = logging.getLogger('default') @@ -29,9 +29,9 @@ def getDiscoveredPeer(): def chooseConnection(stream): """Returns an appropriate connection""" - haveOnion = BMConfigParser().safeGet( + haveOnion = config.safeGet( "bitmessagesettings", "socksproxytype")[0:5] == 'SOCKS' - onionOnly = BMConfigParser().safeGetBoolean( + onionOnly = config.safeGetBoolean( "bitmessagesettings", "onionservicesonly") try: retval = portCheckerQueue.get(False) diff --git a/src/network/connectionpool.py b/src/network/connectionpool.py index fffc0bc3..78132cb7 100644 --- a/src/network/connectionpool.py +++ b/src/network/connectionpool.py @@ -13,7 +13,7 @@ import helper_random import knownnodes import protocol import state -from bmconfigparser import BMConfigParser +from bmconfigparser import config from connectionchooser import chooseConnection from node import Peer from proxy import Proxy @@ -46,9 +46,9 @@ class BMConnectionPool(object): def __init__(self): asyncore.set_rates( - BMConfigParser().safeGetInt( + config.safeGetInt( "bitmessagesettings", "maxdownloadrate"), - BMConfigParser().safeGetInt( + config.safeGetInt( "bitmessagesettings", "maxuploadrate") ) self.outboundConnections = {} @@ -60,7 +60,7 @@ class BMConnectionPool(object): self._spawnWait = 2 self._bootstrapped = False - trustedPeer = BMConfigParser().safeGet( + trustedPeer = config.safeGet( 'bitmessagesettings', 'trustedpeer') try: if trustedPeer: @@ -163,27 +163,27 @@ class BMConnectionPool(object): @staticmethod def getListeningIP(): """What IP are we supposed to be listening on?""" - if BMConfigParser().safeGet( + if config.safeGet( "bitmessagesettings", "onionhostname").endswith(".onion"): - host = BMConfigParser().safeGet( + host = config.safeGet( "bitmessagesettings", "onionbindip") else: host = '127.0.0.1' if ( - BMConfigParser().safeGetBoolean("bitmessagesettings", "sockslisten") - or BMConfigParser().safeGet("bitmessagesettings", "socksproxytype") + config.safeGetBoolean("bitmessagesettings", "sockslisten") + or config.safeGet("bitmessagesettings", "socksproxytype") == "none" ): # python doesn't like bind + INADDR_ANY? # host = socket.INADDR_ANY - host = BMConfigParser().get("network", "bind") + host = config.get("network", "bind") return host def startListening(self, bind=None): """Open a listening socket and start accepting connections on it""" if bind is None: bind = self.getListeningIP() - port = BMConfigParser().safeGetInt("bitmessagesettings", "port") + port = config.safeGetInt("bitmessagesettings", "port") # correct port even if it changed ls = TCPServer(host=bind, port=port) self.listeningSockets[ls.destination] = ls @@ -205,7 +205,7 @@ class BMConnectionPool(object): def startBootstrappers(self): """Run the process of resolving bootstrap hostnames""" - proxy_type = BMConfigParser().safeGet( + proxy_type = config.safeGet( 'bitmessagesettings', 'socksproxytype') # A plugins may be added here hostname = None @@ -237,21 +237,21 @@ class BMConnectionPool(object): # defaults to empty loop if outbound connections are maxed spawnConnections = False acceptConnections = True - if BMConfigParser().safeGetBoolean( + if config.safeGetBoolean( 'bitmessagesettings', 'dontconnect'): acceptConnections = False - elif BMConfigParser().safeGetBoolean( + elif config.safeGetBoolean( 'bitmessagesettings', 'sendoutgoingconnections'): spawnConnections = True - socksproxytype = BMConfigParser().safeGet( + socksproxytype = config.safeGet( 'bitmessagesettings', 'socksproxytype', '') - onionsocksproxytype = BMConfigParser().safeGet( + onionsocksproxytype = config.safeGet( 'bitmessagesettings', 'onionsocksproxytype', '') if ( socksproxytype[:5] == 'SOCKS' - and not BMConfigParser().safeGetBoolean( + and not config.safeGetBoolean( 'bitmessagesettings', 'sockslisten') - and '.onion' not in BMConfigParser().safeGet( + and '.onion' not in config.safeGet( 'bitmessagesettings', 'onionhostname', '') ): acceptConnections = False @@ -264,9 +264,9 @@ class BMConnectionPool(object): if not self._bootstrapped: self._bootstrapped = True Proxy.proxy = ( - BMConfigParser().safeGet( + config.safeGet( 'bitmessagesettings', 'sockshostname'), - BMConfigParser().safeGetInt( + config.safeGetInt( 'bitmessagesettings', 'socksport') ) # TODO AUTH @@ -275,9 +275,9 @@ class BMConnectionPool(object): if not onionsocksproxytype.startswith("SOCKS"): raise ValueError Proxy.onion_proxy = ( - BMConfigParser().safeGet( + config.safeGet( 'network', 'onionsockshostname', None), - BMConfigParser().safeGet( + config.safeGet( 'network', 'onionsocksport', None) ) except ValueError: @@ -286,7 +286,7 @@ class BMConnectionPool(object): 1 for c in self.outboundConnections.values() if (c.connected and c.fullyEstablished)) pending = len(self.outboundConnections) - established - if established < BMConfigParser().safeGetInt( + if established < config.safeGetInt( 'bitmessagesettings', 'maxoutboundconnections'): for i in range( state.maximumNumberOfHalfOpenConnections - pending): @@ -340,22 +340,22 @@ class BMConnectionPool(object): if acceptConnections: if not self.listeningSockets: - if BMConfigParser().safeGet('network', 'bind') == '': + if config.safeGet('network', 'bind') == '': self.startListening() else: for bind in re.sub( r'[^\w.]+', ' ', - BMConfigParser().safeGet('network', 'bind') + config.safeGet('network', 'bind') ).split(): self.startListening(bind) logger.info('Listening for incoming connections.') if not self.udpSockets: - if BMConfigParser().safeGet('network', 'bind') == '': + if config.safeGet('network', 'bind') == '': self.startUDPSocket() else: for bind in re.sub( r'[^\w.]+', ' ', - BMConfigParser().safeGet('network', 'bind') + config.safeGet('network', 'bind') ).split(): self.startUDPSocket(bind) self.startUDPSocket(False) diff --git a/src/network/knownnodes.py b/src/network/knownnodes.py index 4840aad9..79912a67 100644 --- a/src/network/knownnodes.py +++ b/src/network/knownnodes.py @@ -16,7 +16,7 @@ except ImportError: from collections import Iterable import state -from bmconfigparser import BMConfigParser +from bmconfigparser import config from network.node import Peer state.Peer = Peer @@ -130,7 +130,7 @@ def addKnownNode(stream, peer, lastseen=None, is_self=False): return if not is_self: - if len(knownNodes[stream]) > BMConfigParser().safeGetInt( + if len(knownNodes[stream]) > config.safeGetInt( "knownnodes", "maxnodes"): return @@ -165,8 +165,6 @@ def readKnownNodes(): 'Failed to read nodes from knownnodes.dat', exc_info=True) createDefaultKnownNodes() - config = BMConfigParser() - # your own onion address, if setup onionhostname = config.safeGet('bitmessagesettings', 'onionhostname') if onionhostname and ".onion" in onionhostname: @@ -210,7 +208,7 @@ def decreaseRating(peer): def trimKnownNodes(recAddrStream=1): """Triming Knownnodes""" if len(knownNodes[recAddrStream]) < \ - BMConfigParser().safeGetInt("knownnodes", "maxnodes"): + config.safeGetInt("knownnodes", "maxnodes"): return with knownNodesLock: oldestList = sorted( diff --git a/src/network/proxy.py b/src/network/proxy.py index 3bd3cc66..ed1af127 100644 --- a/src/network/proxy.py +++ b/src/network/proxy.py @@ -8,7 +8,7 @@ import time import asyncore_pollchoose as asyncore from advanceddispatcher import AdvancedDispatcher -from bmconfigparser import BMConfigParser +from bmconfigparser import config from node import Peer logger = logging.getLogger('default') @@ -114,12 +114,12 @@ class Proxy(AdvancedDispatcher): self.isOutbound = True self.fullyEstablished = False self.create_socket(socket.AF_INET, socket.SOCK_STREAM) - if BMConfigParser().safeGetBoolean( + if config.safeGetBoolean( "bitmessagesettings", "socksauthentication"): self.auth = ( - BMConfigParser().safeGet( + config.safeGet( "bitmessagesettings", "socksusername"), - BMConfigParser().safeGet( + config.safeGet( "bitmessagesettings", "sockspassword")) else: self.auth = None diff --git a/src/network/tcp.py b/src/network/tcp.py index ff778378..14fc72f0 100644 --- a/src/network/tcp.py +++ b/src/network/tcp.py @@ -16,7 +16,7 @@ import helper_random import knownnodes import protocol import state -from bmconfigparser import BMConfigParser +from bmconfigparser import config from helper_random import randomBytes from inventory import Inventory from network.advanceddispatcher import AdvancedDispatcher @@ -177,7 +177,7 @@ class TCPConnection(BMProto, TLSDispatcher): # 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( + maxAddrCount = config.safeGetInt( "bitmessagesettings", "maxaddrperstreamsend", 500) templist = [] @@ -406,9 +406,9 @@ class TCPServer(AdvancedDispatcher): else: if attempt > 0: logger.warning('Setting port to %s', port) - BMConfigParser().set( + config.set( 'bitmessagesettings', 'port', str(port)) - BMConfigParser().save() + config.save() break self.destination = Peer(host, port) self.bound = True @@ -431,9 +431,9 @@ class TCPServer(AdvancedDispatcher): state.ownAddresses[Peer(*sock.getsockname())] = True if ( len(connectionpool.BMConnectionPool()) - > BMConfigParser().safeGetInt( + > config.safeGetInt( 'bitmessagesettings', 'maxtotalconnections') - + BMConfigParser().safeGetInt( + + config.safeGetInt( 'bitmessagesettings', 'maxbootstrapconnections') + 10 ): # 10 is a sort of buffer, in between it will go through diff --git a/src/openclpow.py b/src/openclpow.py index 1091f555..938ffc81 100644 --- a/src/openclpow.py +++ b/src/openclpow.py @@ -6,7 +6,7 @@ import os from struct import pack import paths -from bmconfigparser import BMConfigParser +from bmconfigparser import config from state import shutdown try: @@ -42,7 +42,7 @@ def initCL(): try: for platform in cl.get_platforms(): gpus.extend(platform.get_devices(device_type=cl.device_type.GPU)) - if BMConfigParser().safeGet("bitmessagesettings", "opencl") == platform.vendor: + if config.safeGet("bitmessagesettings", "opencl") == platform.vendor: enabledGpus.extend(platform.get_devices( device_type=cl.device_type.GPU)) if platform.vendor not in vendors: diff --git a/src/proofofwork.py b/src/proofofwork.py index 148d6734..5c9448f1 100644 --- a/src/proofofwork.py +++ b/src/proofofwork.py @@ -17,7 +17,7 @@ import paths import queues import state import tr -from bmconfigparser import BMConfigParser +from bmconfigparser import config from debug import logger bitmsglib = 'bitmsghash.so' @@ -119,7 +119,7 @@ def _doFastPoW(target, initialHash): except: # noqa:E722 pool_size = 4 try: - maxCores = BMConfigParser().getint('bitmessagesettings', 'maxcores') + maxCores = config.getint('bitmessagesettings', 'maxcores') except: # noqa:E722 maxCores = 99999 if pool_size > maxCores: diff --git a/src/protocol.py b/src/protocol.py index 1934d9cc..6ee35d53 100644 --- a/src/protocol.py +++ b/src/protocol.py @@ -18,7 +18,7 @@ import highlevelcrypto import state from addresses import ( encodeVarint, decodeVarint, decodeAddress, varintDecodeError) -from bmconfigparser import BMConfigParser +from bmconfigparser import config from debug import logger from fallback import RIPEMD160Hash from helper_sql import sqlExecute @@ -72,7 +72,7 @@ def getBitfield(address): # bitfield of features supported by me (see the wiki). bitfield = 0 # send ack - if not BMConfigParser().safeGetBoolean(address, 'dontsendack'): + if not config.safeGetBoolean(address, 'dontsendack'): bitfield |= BITFIELD_DOESACK return pack('>I', bitfield) @@ -238,7 +238,7 @@ def haveSSL(server=False): def checkSocksIP(host): """Predicate to check if we're using a SOCKS proxy""" - sockshostname = BMConfigParser().safeGet( + sockshostname = config.safeGet( 'bitmessagesettings', 'sockshostname') try: if not state.socksIP: @@ -341,19 +341,19 @@ def assembleVersionMessage( '>L', 2130706433) # we have a separate extPort and incoming over clearnet # or outgoing through clearnet - extport = BMConfigParser().safeGetInt('bitmessagesettings', 'extport') + extport = config.safeGetInt('bitmessagesettings', 'extport') if ( extport and ((server and not checkSocksIP(remoteHost)) or ( - BMConfigParser().get('bitmessagesettings', 'socksproxytype') + config.get('bitmessagesettings', 'socksproxytype') == 'none' and not server)) ): payload += pack('>H', extport) elif checkSocksIP(remoteHost) and server: # incoming connection over Tor payload += pack( - '>H', BMConfigParser().getint('bitmessagesettings', 'onionport')) + '>H', config.getint('bitmessagesettings', 'onionport')) else: # no extport and not incoming over Tor payload += pack( - '>H', BMConfigParser().getint('bitmessagesettings', 'port')) + '>H', config.getint('bitmessagesettings', 'port')) if nodeid is not None: payload += nodeid[0:8] diff --git a/src/shared.py b/src/shared.py index 4a654932..d9c1ca13 100644 --- a/src/shared.py +++ b/src/shared.py @@ -19,7 +19,7 @@ from binascii import hexlify import highlevelcrypto import state from addresses import decodeAddress, encodeVarint -from bmconfigparser import BMConfigParser +from bmconfigparser import config from debug import logger from helper_sql import sqlQuery @@ -116,8 +116,8 @@ def reloadMyAddressHashes(): keyfileSecure = checkSensitiveFilePermissions(os.path.join( state.appdata, 'keys.dat')) hasEnabledKeys = False - for addressInKeysFile in BMConfigParser().addresses(): - isEnabled = BMConfigParser().getboolean(addressInKeysFile, 'enabled') + for addressInKeysFile in config.addresses(): + isEnabled = config.getboolean(addressInKeysFile, 'enabled') if isEnabled: hasEnabledKeys = True # status @@ -126,7 +126,7 @@ def reloadMyAddressHashes(): # 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'))) + config.get(addressInKeysFile, 'privencryptionkey'))) # It is 32 bytes encoded as 64 hex characters if len(privEncryptionKey) == 64: myECCryptorObjects[hashobj] = \ diff --git a/src/tests/core.py b/src/tests/core.py index 52cea234..1dca92c0 100644 --- a/src/tests/core.py +++ b/src/tests/core.py @@ -21,7 +21,7 @@ import state import helper_sent import helper_addressbook -from bmconfigparser import BMConfigParser +from bmconfigparser import config from helper_msgcoding import MsgEncode, MsgDecode from helper_sql import sqlQuery from network import asyncore_pollchoose as asyncore, knownnodes @@ -66,9 +66,9 @@ class TestCore(unittest.TestCase): def tearDown(self): """Reset possible unexpected settings after test""" knownnodes.addKnownNode(1, Peer('127.0.0.1', 8444), is_self=True) - BMConfigParser().remove_option('bitmessagesettings', 'dontconnect') - BMConfigParser().remove_option('bitmessagesettings', 'onionservicesonly') - BMConfigParser().set('bitmessagesettings', 'socksproxytype', 'none') + config.remove_option('bitmessagesettings', 'dontconnect') + config.remove_option('bitmessagesettings', 'onionservicesonly') + config.set('bitmessagesettings', 'socksproxytype', 'none') def test_msgcoding(self): """test encoding and decoding (originally from helper_msgcoding)""" @@ -110,7 +110,7 @@ class TestCore(unittest.TestCase): @unittest.skip('Bad environment for asyncore.loop') def test_tcpconnection(self): """initial fill script from network.tcp""" - BMConfigParser().set('bitmessagesettings', 'dontconnect', 'true') + config.set('bitmessagesettings', 'dontconnect', 'true') try: for peer in (Peer("127.0.0.1", 8448),): direct = TCPConnection(peer) @@ -175,7 +175,7 @@ class TestCore(unittest.TestCase): self.fail("IndexError because of empty knownNodes!") def _initiate_bootstrap(self): - BMConfigParser().set('bitmessagesettings', 'dontconnect', 'true') + config.set('bitmessagesettings', 'dontconnect', 'true') self._wipe_knownnodes() knownnodes.addKnownNode(1, Peer('127.0.0.1', 8444), is_self=True) knownnodes.cleanupKnownNodes() @@ -188,8 +188,8 @@ class TestCore(unittest.TestCase): fail otherwise. """ _started = time.time() - BMConfigParser().remove_option('bitmessagesettings', 'dontconnect') - proxy_type = BMConfigParser().safeGet( + config.remove_option('bitmessagesettings', 'dontconnect') + proxy_type = config.safeGet( 'bitmessagesettings', 'socksproxytype') if proxy_type == 'SOCKS5': connection_base = Socks5BMConnection @@ -250,7 +250,7 @@ class TestCore(unittest.TestCase): def test_bootstrap(self): """test bootstrapping""" - BMConfigParser().set('bitmessagesettings', 'socksproxytype', 'none') + config.set('bitmessagesettings', 'socksproxytype', 'none') self._initiate_bootstrap() self._check_connection() self._check_knownnodes() @@ -262,7 +262,7 @@ class TestCore(unittest.TestCase): @unittest.skipIf(tor_port_free, 'no running tor detected') def test_bootstrap_tor(self): """test bootstrapping with tor""" - BMConfigParser().set('bitmessagesettings', 'socksproxytype', 'SOCKS5') + config.set('bitmessagesettings', 'socksproxytype', 'SOCKS5') self._initiate_bootstrap() self._check_connection() self._check_knownnodes() @@ -272,8 +272,8 @@ class TestCore(unittest.TestCase): """ensure bitmessage doesn't try to connect to non-onion nodes if onionservicesonly set, wait at least 3 onion nodes """ - BMConfigParser().set('bitmessagesettings', 'socksproxytype', 'SOCKS5') - BMConfigParser().set('bitmessagesettings', 'onionservicesonly', 'true') + config.set('bitmessagesettings', 'socksproxytype', 'SOCKS5') + config.set('bitmessagesettings', 'onionservicesonly', 'true') self._load_knownnodes(knownnodes_file + '.bak') if len([ node for node in knownnodes.knownNodes[1] @@ -282,7 +282,7 @@ class TestCore(unittest.TestCase): with knownnodes.knownNodesLock: for f in ('a', 'b', 'c', 'd'): knownnodes.addKnownNode(1, Peer(f * 16 + '.onion', 8444)) - BMConfigParser().remove_option('bitmessagesettings', 'dontconnect') + config.remove_option('bitmessagesettings', 'dontconnect') tried_hosts = set() for _ in range(360): time.sleep(1) @@ -301,7 +301,7 @@ class TestCore(unittest.TestCase): def test_udp(self): """check default udp setting and presence of Announcer thread""" self.assertTrue( - BMConfigParser().safeGetBoolean('bitmessagesettings', 'udp')) + config.safeGetBoolean('bitmessagesettings', 'udp')) for thread in threading.enumerate(): if thread.name == 'Announcer': # find Announcer thread break diff --git a/src/tests/test_config.py b/src/tests/test_config.py index 7b702016..cb725369 100644 --- a/src/tests/test_config.py +++ b/src/tests/test_config.py @@ -1,4 +1,4 @@ -# pylint: disable=no-member +# pylint: disable=no-member, no-self-use """ Various tests for config """ @@ -40,58 +40,59 @@ class TestConfig(unittest.TestCase): """A test case for bmconfigparser""" configfile = StringIO('') - def setUp(self): - """creates a backup of BMConfigparser current state""" - BMConfigParser().write(self.configfile) - self.configfile.seek(0) - - def tearDown(self): - """restore to the backup of BMConfigparser""" - # pylint: disable=protected-access - BMConfigParser()._reset() - BMConfigParser().readfp(self.configfile) - def test_safeGet(self): """safeGet retuns provided default for nonexistent option or None""" + config = BMConfigParser() self.assertIs( - BMConfigParser().safeGet('nonexistent', 'nonexistent'), None) + config.safeGet('nonexistent', 'nonexistent'), None) self.assertEqual( - BMConfigParser().safeGet('nonexistent', 'nonexistent', 42), 42) + config.safeGet('nonexistent', 'nonexistent', 42), 42) def test_safeGetBoolean(self): """safeGetBoolean returns False for nonexistent option, no default""" + config = BMConfigParser() self.assertIs( - BMConfigParser().safeGetBoolean('nonexistent', 'nonexistent'), + config.safeGetBoolean('nonexistent', 'nonexistent'), False ) # no arg for default # pylint: disable=too-many-function-args with self.assertRaises(TypeError): - BMConfigParser().safeGetBoolean( + config.safeGetBoolean( 'nonexistent', 'nonexistent', True) def test_safeGetInt(self): """safeGetInt retuns provided default for nonexistent option or 0""" + config = BMConfigParser() self.assertEqual( - BMConfigParser().safeGetInt('nonexistent', 'nonexistent'), 0) + config.safeGetInt('nonexistent', 'nonexistent'), 0) self.assertEqual( - BMConfigParser().safeGetInt('nonexistent', 'nonexistent', 42), 42) + config.safeGetInt('nonexistent', 'nonexistent', 42), 42) def test_safeGetFloat(self): """safeGetFloat retuns provided default for nonexistent option or 0.0""" + config = BMConfigParser() self.assertEqual( - BMConfigParser().safeGetFloat('nonexistent', 'nonexistent'), 0.0) + config.safeGetFloat('nonexistent', 'nonexistent'), 0.0) self.assertEqual( - BMConfigParser().safeGetFloat('nonexistent', 'nonexistent', 42.0), 42.0) + config.safeGetFloat('nonexistent', 'nonexistent', 42.0), 42.0) def test_reset(self): """safeGetInt retuns provided default for bitmessagesettings option or 0""" + config = BMConfigParser() test_config_object = StringIO(test_config) - BMConfigParser().readfp(test_config_object) - + config.readfp(test_config_object) self.assertEqual( - BMConfigParser().safeGetInt('bitmessagesettings', 'maxaddrperstreamsend'), 100) + config.safeGetInt('bitmessagesettings', 'maxaddrperstreamsend'), 100) # pylint: disable=protected-access - BMConfigParser()._reset() + config._reset() + self.assertEqual(config.sections(), []) + + def test_defaults(self): + """Loading defaults""" + config = BMConfigParser() + config.add_section('bitmessagesettings') + config.set("bitmessagesettings", "maxaddrperstreamsend", "100") + config.read() self.assertEqual( - BMConfigParser().safeGetInt('bitmessagesettings', 'maxaddrperstreamsend'), 500) + config.safeGetInt('bitmessagesettings', 'maxaddrperstreamsend'), 500) diff --git a/src/tests/test_config_process.py b/src/tests/test_config_process.py index 173d323f..9322a2f0 100644 --- a/src/tests/test_config_process.py +++ b/src/tests/test_config_process.py @@ -4,7 +4,7 @@ Various tests for config import os import tempfile -from pybitmessage.bmconfigparser import BMConfigParser +from pybitmessage.bmconfigparser import config from .test_process import TestProcessProto from .common import skip_python3 @@ -17,7 +17,6 @@ class TestProcessConfig(TestProcessProto): def test_config_defaults(self): """Test settings in the generated config""" - config = BMConfigParser() self._stop_process() self._kill_process() config.read(os.path.join(self.home, 'keys.dat')) diff --git a/src/upnp.py b/src/upnp.py index c6db487b..2fa71f9c 100644 --- a/src/upnp.py +++ b/src/upnp.py @@ -15,7 +15,7 @@ from xml.dom.minidom import Document, parseString import queues import state import tr -from bmconfigparser import BMConfigParser +from bmconfigparser import config from debug import logger from network import BMConnectionPool, knownnodes, StoppableThread from network.node import Peer @@ -207,7 +207,7 @@ class uPnPThread(StoppableThread): def __init__(self): super(uPnPThread, self).__init__(name="uPnPThread") - self.extPort = BMConfigParser().safeGetInt('bitmessagesettings', 'extport', default=None) + self.extPort = config.safeGetInt('bitmessagesettings', 'extport', default=None) self.localIP = self.getLocalIP() self.routers = [] self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) @@ -233,9 +233,9 @@ class uPnPThread(StoppableThread): time.sleep(1) # pylint: disable=attribute-defined-outside-init - self.localPort = BMConfigParser().getint('bitmessagesettings', 'port') + self.localPort = config.getint('bitmessagesettings', 'port') - while state.shutdown == 0 and BMConfigParser().safeGetBoolean('bitmessagesettings', 'upnp'): + while state.shutdown == 0 and config.safeGetBoolean('bitmessagesettings', 'upnp'): if time.time() - lastSent > self.sendSleep and not self.routers: try: self.sendSearchRouter() @@ -243,7 +243,7 @@ class uPnPThread(StoppableThread): pass lastSent = time.time() try: - while state.shutdown == 0 and BMConfigParser().safeGetBoolean('bitmessagesettings', 'upnp'): + while state.shutdown == 0 and config.safeGetBoolean('bitmessagesettings', 'upnp'): resp, (ip, _) = self.sock.recvfrom(1000) if resp is None: continue @@ -337,8 +337,8 @@ class uPnPThread(StoppableThread): extPort) router.AddPortMapping(extPort, self.localPort, localIP, 'TCP', 'BitMessage') self.extPort = extPort - BMConfigParser().set('bitmessagesettings', 'extport', str(extPort)) - BMConfigParser().save() + config.set('bitmessagesettings', 'extport', str(extPort)) + config.save() break except UPnPError: logger.debug("UPnP error: ", exc_info=True) From 08099d4409d4699825670ac6027a1910df9530bb Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Wed, 16 Feb 2022 22:16:41 +0200 Subject: [PATCH 010/424] Revert arguments renaming in bitmessageqt.settings --- src/bitmessageqt/settings.py | 82 ++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/src/bitmessageqt/settings.py b/src/bitmessageqt/settings.py index 501b2114..237d14e1 100644 --- a/src/bitmessageqt/settings.py +++ b/src/bitmessageqt/settings.py @@ -16,7 +16,7 @@ import paths import queues import state import widgets -from bmconfigparser import config +from bmconfigparser import config as config_obj from helper_sql import sqlExecute, sqlStoredProcedure from helper_startup import start_proxyconfig from network import knownnodes, AnnounceThread @@ -24,11 +24,11 @@ from network.asyncore_pollchoose import set_rates from tr import _translate -def getSOCKSProxyType(config_): +def getSOCKSProxyType(config): """Get user socksproxytype setting from *config*""" try: result = ConfigParser.SafeConfigParser.get( - config_, 'bitmessagesettings', 'socksproxytype') + config, 'bitmessagesettings', 'socksproxytype') except (ConfigParser.NoSectionError, ConfigParser.NoOptionError): return None else: @@ -45,7 +45,7 @@ class SettingsDialog(QtGui.QDialog): self.parent = parent self.firstrun = firstrun - self.config = config + self.config = config_obj self.net_restart_needed = False self.timer = QtCore.QTimer() @@ -80,7 +80,7 @@ class SettingsDialog(QtGui.QDialog): ) QtGui.QWidget.resize(self, QtGui.QWidget.sizeHint(self)) - def adjust_from_config(self, config_): + def adjust_from_config(self, config): """Adjust all widgets state according to config settings""" # pylint: disable=too-many-branches,too-many-statements if not self.parent.tray.isSystemTrayAvailable(): @@ -89,31 +89,31 @@ class SettingsDialog(QtGui.QDialog): "MainWindow", "Tray (not available in your system)")) for setting in ( 'minimizetotray', 'trayonclose', 'startintray'): - config_.set('bitmessagesettings', setting, 'false') + config.set('bitmessagesettings', setting, 'false') else: self.checkBoxMinimizeToTray.setChecked( - config_.getboolean('bitmessagesettings', 'minimizetotray')) + config.getboolean('bitmessagesettings', 'minimizetotray')) self.checkBoxTrayOnClose.setChecked( - config_.safeGetBoolean('bitmessagesettings', 'trayonclose')) + config.safeGetBoolean('bitmessagesettings', 'trayonclose')) self.checkBoxStartInTray.setChecked( - config_.getboolean('bitmessagesettings', 'startintray')) + config.getboolean('bitmessagesettings', 'startintray')) self.checkBoxHideTrayConnectionNotifications.setChecked( - config_.getboolean( + config.getboolean( 'bitmessagesettings', 'hidetrayconnectionnotifications')) self.checkBoxShowTrayNotifications.setChecked( - config_.getboolean('bitmessagesettings', 'showtraynotifications')) + config.getboolean('bitmessagesettings', 'showtraynotifications')) self.checkBoxStartOnLogon.setChecked( - config_.getboolean('bitmessagesettings', 'startonlogon')) + config.getboolean('bitmessagesettings', 'startonlogon')) self.checkBoxWillinglySendToMobile.setChecked( - config_.safeGetBoolean( + config.safeGetBoolean( 'bitmessagesettings', 'willinglysendtomobile')) self.checkBoxUseIdenticons.setChecked( - config_.safeGetBoolean('bitmessagesettings', 'useidenticons')) + config.safeGetBoolean('bitmessagesettings', 'useidenticons')) self.checkBoxReplyBelow.setChecked( - config_.safeGetBoolean('bitmessagesettings', 'replybelow')) + config.safeGetBoolean('bitmessagesettings', 'replybelow')) if state.appdata == paths.lookupExeFolder(): self.checkBoxPortableMode.setChecked(True) @@ -142,57 +142,57 @@ class SettingsDialog(QtGui.QDialog): # On the Network settings tab: self.lineEditTCPPort.setText(str( - config_.get('bitmessagesettings', 'port'))) + config.get('bitmessagesettings', 'port'))) self.checkBoxUPnP.setChecked( - config_.safeGetBoolean('bitmessagesettings', 'upnp')) + config.safeGetBoolean('bitmessagesettings', 'upnp')) self.checkBoxUDP.setChecked( - config_.safeGetBoolean('bitmessagesettings', 'udp')) + config.safeGetBoolean('bitmessagesettings', 'udp')) self.checkBoxAuthentication.setChecked( - config_.getboolean('bitmessagesettings', 'socksauthentication')) + config.getboolean('bitmessagesettings', 'socksauthentication')) self.checkBoxSocksListen.setChecked( - config_.getboolean('bitmessagesettings', 'sockslisten')) + config.getboolean('bitmessagesettings', 'sockslisten')) self.checkBoxOnionOnly.setChecked( - config_.safeGetBoolean('bitmessagesettings', 'onionservicesonly')) + config.safeGetBoolean('bitmessagesettings', 'onionservicesonly')) - self._proxy_type = getSOCKSProxyType(config_) + self._proxy_type = getSOCKSProxyType(config) self.comboBoxProxyType.setCurrentIndex( 0 if not self._proxy_type else self.comboBoxProxyType.findText(self._proxy_type)) self.comboBoxProxyTypeChanged(self.comboBoxProxyType.currentIndex()) self.lineEditSocksHostname.setText( - config_.get('bitmessagesettings', 'sockshostname')) + config.get('bitmessagesettings', 'sockshostname')) self.lineEditSocksPort.setText(str( - config_.get('bitmessagesettings', 'socksport'))) + config.get('bitmessagesettings', 'socksport'))) self.lineEditSocksUsername.setText( - config_.get('bitmessagesettings', 'socksusername')) + config.get('bitmessagesettings', 'socksusername')) self.lineEditSocksPassword.setText( - config_.get('bitmessagesettings', 'sockspassword')) + config.get('bitmessagesettings', 'sockspassword')) self.lineEditMaxDownloadRate.setText(str( - config_.get('bitmessagesettings', 'maxdownloadrate'))) + config.get('bitmessagesettings', 'maxdownloadrate'))) self.lineEditMaxUploadRate.setText(str( - config_.get('bitmessagesettings', 'maxuploadrate'))) + config.get('bitmessagesettings', 'maxuploadrate'))) self.lineEditMaxOutboundConnections.setText(str( - config_.get('bitmessagesettings', 'maxoutboundconnections'))) + config.get('bitmessagesettings', 'maxoutboundconnections'))) # Demanded difficulty tab self.lineEditTotalDifficulty.setText(str((float( - config_.getint( + config.getint( 'bitmessagesettings', 'defaultnoncetrialsperbyte') ) / defaults.networkDefaultProofOfWorkNonceTrialsPerByte))) self.lineEditSmallMessageDifficulty.setText(str((float( - config_.getint( + config.getint( 'bitmessagesettings', 'defaultpayloadlengthextrabytes') ) / defaults.networkDefaultPayloadLengthExtraBytes))) # Max acceptable difficulty tab self.lineEditMaxAcceptableTotalDifficulty.setText(str((float( - config_.getint( + config.getint( 'bitmessagesettings', 'maxacceptablenoncetrialsperbyte') ) / defaults.networkDefaultProofOfWorkNonceTrialsPerByte))) self.lineEditMaxAcceptableSmallMessageDifficulty.setText(str((float( - config_.getint( + config.getint( 'bitmessagesettings', 'maxacceptablepayloadlengthextrabytes') ) / defaults.networkDefaultPayloadLengthExtraBytes))) @@ -203,21 +203,21 @@ class SettingsDialog(QtGui.QDialog): self.comboBoxOpenCL.addItems(openclpow.vendors) self.comboBoxOpenCL.setCurrentIndex(0) for i in range(self.comboBoxOpenCL.count()): - if self.comboBoxOpenCL.itemText(i) == config_.safeGet( + if self.comboBoxOpenCL.itemText(i) == config.safeGet( 'bitmessagesettings', 'opencl'): self.comboBoxOpenCL.setCurrentIndex(i) break # Namecoin integration tab - nmctype = config_.get('bitmessagesettings', 'namecoinrpctype') + nmctype = config.get('bitmessagesettings', 'namecoinrpctype') self.lineEditNamecoinHost.setText( - config_.get('bitmessagesettings', 'namecoinrpchost')) + config.get('bitmessagesettings', 'namecoinrpchost')) self.lineEditNamecoinPort.setText(str( - config_.get('bitmessagesettings', 'namecoinrpcport'))) + config.get('bitmessagesettings', 'namecoinrpcport'))) self.lineEditNamecoinUser.setText( - config_.get('bitmessagesettings', 'namecoinrpcuser')) + config.get('bitmessagesettings', 'namecoinrpcuser')) self.lineEditNamecoinPassword.setText( - config_.get('bitmessagesettings', 'namecoinrpcpassword')) + config.get('bitmessagesettings', 'namecoinrpcpassword')) if nmctype == "namecoind": self.radioButtonNamecoinNamecoind.setChecked(True) @@ -232,9 +232,9 @@ class SettingsDialog(QtGui.QDialog): # Message Resend tab self.lineEditDays.setText(str( - config_.get('bitmessagesettings', 'stopresendingafterxdays'))) + config.get('bitmessagesettings', 'stopresendingafterxdays'))) self.lineEditMonths.setText(str( - config_.get('bitmessagesettings', 'stopresendingafterxmonths'))) + config.get('bitmessagesettings', 'stopresendingafterxmonths'))) def comboBoxProxyTypeChanged(self, comboBoxIndex): """A callback for currentIndexChanged event of comboBoxProxyType""" From 161a0b2059b5f3f3b62cb36f5239d8a87f2f7eab Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Wed, 16 Feb 2022 22:55:17 +0200 Subject: [PATCH 011/424] Return use of BMConfigParser._temp dict, also empty _temp in BMConfigParser._reset(). --- src/bmconfigparser.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/bmconfigparser.py b/src/bmconfigparser.py index 312a6f97..16d217e8 100644 --- a/src/bmconfigparser.py +++ b/src/bmconfigparser.py @@ -36,6 +36,15 @@ class BMConfigParser(SafeConfigParser): raise ValueError("Invalid value %s" % value) return SafeConfigParser.set(self, section, option, value) + def get(self, section, option, **kwargs): + """Try returning temporary value before using parent get()""" + try: + return self._temp[section][option] + except KeyError: + pass + return SafeConfigParser.get( + self, section, option, **kwargs) + def setTemp(self, section, option, value=None): """Temporary set option to value, not saving.""" try: @@ -91,8 +100,11 @@ class BMConfigParser(SafeConfigParser): 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""" + """ + Reset current config. + There doesn't appear to be a built in method for this. + """ + self._temp = {} sections = self.sections() for x in sections: self.remove_section(x) From 7d2b13e4ac52ebdfbfa6a7528a1e355ac4e6c941 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Wed, 16 Feb 2022 23:04:52 +0200 Subject: [PATCH 012/424] Format bmconfigparser: - remove meaningless comments, rename strange args, fix line lengths, - revert BMConfigParser.addresses() entanglement, - revert BMConfigParser read_file -> readfp alias. --- src/bmconfigparser.py | 51 ++++++++++++++-------------------------- src/tests/test_config.py | 2 +- 2 files changed, 19 insertions(+), 34 deletions(-) diff --git a/src/bmconfigparser.py b/src/bmconfigparser.py index 16d217e8..0a2ec558 100644 --- a/src/bmconfigparser.py +++ b/src/bmconfigparser.py @@ -4,7 +4,6 @@ BMConfigParser class definition and default configuration settings import os import shutil -import sys # FIXME: bad style! write more generally from threading import Event from datetime import datetime @@ -52,41 +51,36 @@ class BMConfigParser(SafeConfigParser): except KeyError: self._temp[section] = {option: value} - def safeGetBoolean(self, section, field): + def safeGetBoolean(self, section, option): """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) + return self.getboolean(section, option) except (configparser.NoSectionError, configparser.NoOptionError, ValueError, AttributeError): return False - def safeGetInt(self, section, field, default=0): + def safeGetInt(self, section, option, 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)) + return int(self.get(section, option)) except (configparser.NoSectionError, configparser.NoOptionError, ValueError, AttributeError): return default - def safeGetFloat(self, section, field, default=0.0): + def safeGetFloat(self, section, option, default=0.0): """Return value as float, default on exceptions, 0.0 if default missing""" try: - return self.getfloat(section, field) + return self.getfloat(section, option) except (configparser.NoSectionError, configparser.NoOptionError, ValueError, AttributeError): return default def safeGet(self, section, option, default=None): - """Return value as is, default on exceptions, None if default missing""" + """ + Return value as is, default on exceptions, None if default missing + """ try: return self.get(section, option) except (configparser.NoSectionError, configparser.NoOptionError, @@ -111,26 +105,14 @@ class BMConfigParser(SafeConfigParser): def read(self, filenames=None): self._reset() - SafeConfigParser.read(self, os.path.join(os.path.dirname(__file__), 'default.ini')) + SafeConfigParser.read( + self, os.path.join(os.path.dirname(__file__), 'default.ini')) if filenames: SafeConfigParser.read(self, filenames) - 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 config.sections() if x.startswith('BM-') and ( - hidden or not config.safeGetBoolean(x, 'hidden'))] - - 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 config.sections() if x.startswith('BM-')] + def addresses(self): + """Return a list of local bitmessage addresses (from section labels)""" + return [x for x in self.sections() if x.startswith('BM-')] def save(self): """Save the runtime config onto the filesystem""" @@ -173,4 +155,7 @@ class BMConfigParser(SafeConfigParser): return True -config = BMConfigParser() +if not getattr(BMConfigParser, 'read_file', False): + BMConfigParser.read_file = BMConfigParser.readfp + +config = BMConfigParser() # TODO: remove this crutch diff --git a/src/tests/test_config.py b/src/tests/test_config.py index cb725369..f2574522 100644 --- a/src/tests/test_config.py +++ b/src/tests/test_config.py @@ -81,7 +81,7 @@ class TestConfig(unittest.TestCase): """safeGetInt retuns provided default for bitmessagesettings option or 0""" config = BMConfigParser() test_config_object = StringIO(test_config) - config.readfp(test_config_object) + config.read_file(test_config_object) self.assertEqual( config.safeGetInt('bitmessagesettings', 'maxaddrperstreamsend'), 100) # pylint: disable=protected-access From 858705357a7367c655a019078e4e108bc40d5d50 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Wed, 16 Feb 2022 23:51:12 +0200 Subject: [PATCH 013/424] Format test_config - for PEP8 compatibility, rewrite copypasted docstring, - remove unused configfile slot, move pylint hint, - define TestConfig.setUp(), creating BMConfigParser obj, --- src/tests/test_config.py | 56 +++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/src/tests/test_config.py b/src/tests/test_config.py index f2574522..26624996 100644 --- a/src/tests/test_config.py +++ b/src/tests/test_config.py @@ -1,4 +1,3 @@ -# pylint: disable=no-member, no-self-use """ Various tests for config """ @@ -36,63 +35,60 @@ maxnodes = 15000 maxsize = 1048576""" +# pylint: disable=protected-access class TestConfig(unittest.TestCase): """A test case for bmconfigparser""" - configfile = StringIO('') + + def setUp(self): + self.config = BMConfigParser() + self.config.add_section('bitmessagesettings') def test_safeGet(self): """safeGet retuns provided default for nonexistent option or None""" - config = BMConfigParser() self.assertIs( - config.safeGet('nonexistent', 'nonexistent'), None) + self.config.safeGet('nonexistent', 'nonexistent'), None) self.assertEqual( - config.safeGet('nonexistent', 'nonexistent', 42), 42) + self.config.safeGet('nonexistent', 'nonexistent', 42), 42) def test_safeGetBoolean(self): """safeGetBoolean returns False for nonexistent option, no default""" - config = BMConfigParser() self.assertIs( - config.safeGetBoolean('nonexistent', 'nonexistent'), - False - ) + self.config.safeGetBoolean('nonexistent', 'nonexistent'), False) # no arg for default # pylint: disable=too-many-function-args with self.assertRaises(TypeError): - config.safeGetBoolean( - 'nonexistent', 'nonexistent', True) + self.config.safeGetBoolean('nonexistent', 'nonexistent', True) def test_safeGetInt(self): """safeGetInt retuns provided default for nonexistent option or 0""" - config = BMConfigParser() self.assertEqual( - config.safeGetInt('nonexistent', 'nonexistent'), 0) + self.config.safeGetInt('nonexistent', 'nonexistent'), 0) self.assertEqual( - config.safeGetInt('nonexistent', 'nonexistent', 42), 42) + self.config.safeGetInt('nonexistent', 'nonexistent', 42), 42) def test_safeGetFloat(self): - """safeGetFloat retuns provided default for nonexistent option or 0.0""" - config = BMConfigParser() + """ + safeGetFloat retuns provided default for nonexistent option or 0.0 + """ self.assertEqual( - config.safeGetFloat('nonexistent', 'nonexistent'), 0.0) + self.config.safeGetFloat('nonexistent', 'nonexistent'), 0.0) self.assertEqual( - config.safeGetFloat('nonexistent', 'nonexistent', 42.0), 42.0) + self.config.safeGetFloat('nonexistent', 'nonexistent', 42.0), 42.0) def test_reset(self): - """safeGetInt retuns provided default for bitmessagesettings option or 0""" - config = BMConfigParser() + """Some logic for testing _reset()""" test_config_object = StringIO(test_config) - config.read_file(test_config_object) + self.config.read_file(test_config_object) self.assertEqual( - config.safeGetInt('bitmessagesettings', 'maxaddrperstreamsend'), 100) - # pylint: disable=protected-access - config._reset() - self.assertEqual(config.sections(), []) + self.config.safeGetInt( + 'bitmessagesettings', 'maxaddrperstreamsend'), 100) + self.config._reset() + self.assertEqual(self.config.sections(), []) def test_defaults(self): """Loading defaults""" - config = BMConfigParser() - config.add_section('bitmessagesettings') - config.set("bitmessagesettings", "maxaddrperstreamsend", "100") - config.read() + self.config.set('bitmessagesettings', 'maxaddrperstreamsend', '100') + self.config.read() self.assertEqual( - config.safeGetInt('bitmessagesettings', 'maxaddrperstreamsend'), 500) + self.config.safeGetInt( + 'bitmessagesettings', 'maxaddrperstreamsend'), 500) From e778ee923137e0c4b28f44e353fa3bd7c441ebe9 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Thu, 17 Feb 2022 00:39:48 +0200 Subject: [PATCH 014/424] Add a test for BMConfigParser.setTemp() --- src/tests/test_config.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/tests/test_config.py b/src/tests/test_config.py index 26624996..0d5d03b8 100644 --- a/src/tests/test_config.py +++ b/src/tests/test_config.py @@ -75,6 +75,18 @@ class TestConfig(unittest.TestCase): self.assertEqual( self.config.safeGetFloat('nonexistent', 'nonexistent', 42.0), 42.0) + def test_setTemp(self): + """Set a temporary value and ensure it's returned by get()""" + self.config.setTemp('bitmessagesettings', 'connect', 'true') + self.assertIs( + self.config.safeGetBoolean('bitmessagesettings', 'connect'), True) + written_fp = StringIO('') + self.config.write(written_fp) + self.config._reset() + self.config.read_file(written_fp) + self.assertIs( + self.config.safeGetBoolean('bitmessagesettings', 'connect'), False) + def test_reset(self): """Some logic for testing _reset()""" test_config_object = StringIO(test_config) From 3b5c239c73bd6d83b4336bed43433220a7931296 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Fri, 18 Feb 2022 19:34:49 +0530 Subject: [PATCH 015/424] Add get_platform to kivy live --- src/bitmessagekivy/get_platform.py | 31 ++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/bitmessagekivy/get_platform.py diff --git a/src/bitmessagekivy/get_platform.py b/src/bitmessagekivy/get_platform.py new file mode 100644 index 00000000..654b31f4 --- /dev/null +++ b/src/bitmessagekivy/get_platform.py @@ -0,0 +1,31 @@ +# pylint: disable=no-else-return, too-many-return-statements + +"""To check the platform""" + +from sys import platform as _sys_platform +from os import environ + + +def _get_platform(): + kivy_build = environ.get("KIVY_BUILD", "") + if kivy_build in {"android", "ios"}: + return kivy_build + elif "P4A_BOOTSTRAP" in environ: + return "android" + elif "ANDROID_ARGUMENT" in environ: + return "android" + elif _sys_platform in ("win32", "cygwin"): + return "win" + elif _sys_platform == "darwin": + return "macosx" + elif _sys_platform.startswith("linux"): + return "linux" + elif _sys_platform.startswith("freebsd"): + return "linux" + return "unknown" + + +platform = _get_platform() + +if platform not in ("android", "unknown"): + environ["KIVY_CAMERA"] = "opencv" From 2b1187a244d1e9ba54313678d64a767d2fcb8946 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Fri, 25 Feb 2022 19:00:53 +0530 Subject: [PATCH 016/424] Update winbuild.sh --- buildscripts/winbuild.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/buildscripts/winbuild.sh b/buildscripts/winbuild.sh index 66a2f7aa..c26766f6 100755 --- a/buildscripts/winbuild.sh +++ b/buildscripts/winbuild.sh @@ -134,13 +134,13 @@ function build_dll(){ cd src/bitmsghash || exit 1 if [ "${MACHINE_TYPE}" == 'x86_64' ]; then echo "Create dll" - x86_64-w64-mingw32-g++ -D_WIN32 -Wall -O3 -march=native \ + x86_64-w64-mingw32-g++ -D_WIN32 -Wall -O3 -march=x86-64 \ "-I$HOME/.wine64/drive_c/OpenSSL-Win64/include" \ -I/usr/x86_64-w64-mingw32/include \ "-L$HOME/.wine64/drive_c/OpenSSL-Win64/lib" \ -c bitmsghash.cpp x86_64-w64-mingw32-g++ -static-libgcc -shared bitmsghash.o \ - -D_WIN32 -O3 -march=native \ + -D_WIN32 -O3 -march=x86-64 \ "-I$HOME/.wine64/drive_c/OpenSSL-Win64/include" \ "-L$HOME/.wine64/drive_c/OpenSSL-Win64" \ -L/usr/lib/x86_64-linux-gnu/wine \ @@ -148,13 +148,13 @@ function build_dll(){ -o bitmsghash64.dll -Wl,--out-implib,bitmsghash.a else echo "Create dll" - i686-w64-mingw32-g++ -D_WIN32 -Wall -m32 -O3 -march=native \ + i686-w64-mingw32-g++ -D_WIN32 -Wall -m32 -O3 -march=i686 \ "-I$HOME/.wine32/drive_c/OpenSSL-Win32/include" \ -I/usr/i686-w64-mingw32/include \ "-L$HOME/.wine32/drive_c/OpenSSL-Win32/lib" \ -c bitmsghash.cpp i686-w64-mingw32-g++ -static-libgcc -shared bitmsghash.o \ - -D_WIN32 -O3 -march=native \ + -D_WIN32 -O3 -march=i686 \ "-I$HOME/.wine32/drive_c/OpenSSL-Win32/include" \ "-L$HOME/.wine32/drive_c/OpenSSL-Win32/lib/MinGW" \ -fPIC -shared -lcrypt32 -leay32 -lwsock32 \ From 59deb75059c22863b1d1ff6f6d8baccba6d7e330 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Thu, 24 Feb 2022 19:41:39 +0530 Subject: [PATCH 017/424] Set dontconnet to true in loadConfig --- src/default.ini | 1 - src/helper_startup.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/default.ini b/src/default.ini index fbf731f8..d4420ba5 100644 --- a/src/default.ini +++ b/src/default.ini @@ -23,7 +23,6 @@ sockspassword = keysencrypted = False messagesencrypted = False minimizeonclose = False -dontconnect = True replybelow = False stopresendingafterxdays = stopresendingafterxmonths = diff --git a/src/helper_startup.py b/src/helper_startup.py index c8a56c09..a8789b4a 100644 --- a/src/helper_startup.py +++ b/src/helper_startup.py @@ -86,7 +86,7 @@ def loadConfig(): config.set( 'bitmessagesettings', 'defaultpayloadlengthextrabytes', str(defaults.networkDefaultPayloadLengthExtraBytes)) - + config.set('bitmessagesettings', 'dontconnect', 'true') # UI setting to stop trying to send messages after X days/months # config.set('bitmessagesettings', 'stopresendingafterxdays', '') # config.set('bitmessagesettings', 'stopresendingafterxmonths', '') From f88d70c275ebf76d691d11b939fd5a6862ae400f Mon Sep 17 00:00:00 2001 From: surbhicis Date: Tue, 1 Mar 2022 10:41:25 +0530 Subject: [PATCH 018/424] kivy build run & dockerfile --- .buildbot/android/Dockerfile | 76 ++++++++++++++++++++++++++++++++++++ .buildbot/android/build.sh | 3 ++ 2 files changed, 79 insertions(+) create mode 100644 .buildbot/android/Dockerfile create mode 100644 .buildbot/android/build.sh diff --git a/.buildbot/android/Dockerfile b/.buildbot/android/Dockerfile new file mode 100644 index 00000000..890355e1 --- /dev/null +++ b/.buildbot/android/Dockerfile @@ -0,0 +1,76 @@ +# A container for buildbot +FROM ubuntu:bionic AS android +# FROM ubuntu:20.04 AS buildbot-bionic + +RUN wget -nc "https://dl.google.com/android/repository/sdk-tools-linux-4333796.zip" +RUN wget -nc "https://dl.google.com/android/repository/android-ndk-r23b-linux.zip" +RUN wget -nc "http://archive.apache.org/dist/ant/binaries/apache-ant-1.10.12-bin.tar.gz" + +# SYSTEM DEPENDENCIES + +RUN apt -y update -qq +RUN apt -y install --no-install-recommends python3-pip \ + pip3 python3 virtualenv python3-setuptools \ + python3-wheel git wget unzip sudo patch bzip2 lzma +RUN apt -y autoremove + +# BUILD DEPENDENCIES + +RUN dpkg --add-architecture i386 +RUN apt -y update -qq +RUN apt -y install -qq --no-install-recommends build-essential \ + ccache git python3 python3-dev libncurses5:i386 libstdc++6:i386 \ + libgtk2.0-0:i386 libpangox-1.0-0:i386 libpangoxft-1.0-0:i386 \ + libidn11:i386 zip zlib1g-dev zlib1g:i386 +RUN apt -y autoremove +RUN apt -y clean + +# RECIPES DEPENDENCIES + +RUN dpkg --add-architecture i386 +RUN apt -y update -qq +RUN apt -y install -qq --no-install-recommends libffi-dev autoconf \ + automake cmake gettext libltdl-dev libtool pkg-config +RUN apt -y autoremove +RUN apt -y clean + +# INSTALL ANDROID PACKAGES + +RUN pip3 install buildozer==1.2.0 +RUN pip3 install --upgrade cython==0.29.15 + +# INSTALL NDK + +# get the latest version from https://developer.android.com/ndk/downloads/index.html +RUN mkdir --parents "/opt/android/android-ndk-r23b" +RUN unzip -q "android-ndk-r23b-linux.zip" -d "/opt/android" +RUN ln -sfn "/opt/android/android-ndk-r23b" "/opt/android/android-ndk" +RUN rm -rf "android-ndk-r23b-linux.zip" + +# INSTALL APACHE-ANT + +RUN tar -xf "apache-ant-1.10.12-bin.tar.gz" -C "/opt/android" +RUN ln -sfn "/opt/android/apache-ant-1.10.12" "/opt/android/apache-ant" +RUN rm -rf "apache-ant-1.10.12-bin.tar.gz" + +# INSTALL SDK + +# get the latest version from https://developer.android.com/studio/index.html +RUN mkdir --parents "/opt/android/android-sdk" +RUN unzip -q "sdk-tools-linux-4333796.zip" -d "/opt/android/android-sdk" +RUN rm -rf "sdk-tools-linux-4333796.zip" + # update Android SDK, install Android API, Build Tools... +RUN mkdir --parents "/opt/android/android-sdk/.android/" +# accept Android licenses (JDK necessary!) +RUN apt -y update -qq +RUN apt -y install -qq --no-install-recommends openjdk-11-jdk +RUN apt -y autoremove +RUN yes | "/opt/android/android-sdk/tools/bin/sdkmanager" "build-tools;29.0.2" > /dev/null +# download platforms, API, build tools +RUN "/opt/android/android-sdk/tools/bin/sdkmanager" "platforms;android-24" > /dev/null +RUN "/opt/android/android-sdk/tools/bin/sdkmanager" "platforms;android-28" > /dev/null +RUN "/opt/android/android-sdk/tools/bin/sdkmanager" "build-tools;29.0.2" > /dev/null +RUN "/opt/android/android-sdk/tools/bin/sdkmanager" "extras;android;m2repository" > /dev/null +RUN find /opt/android/android-sdk -type f -perm /0111 -print0|xargs -0 chmod a+x +RUN chown -R buildbot.buildbot /opt/android/android-sdk +RUN chmod +x "/opt/android/android-sdk/tools/bin/avdmanager" diff --git a/.buildbot/android/build.sh b/.buildbot/android/build.sh new file mode 100644 index 00000000..53f2c8e6 --- /dev/null +++ b/.buildbot/android/build.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +buildozer android debug logcat run From 5cb7b51b308617efdf489541dc51e285dba405f0 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Fri, 11 Mar 2022 14:54:26 +0530 Subject: [PATCH 019/424] Refactor kivy screens[Method for set the dynamic screens] --- src/bitmessagekivy/kv/common_widgets.kv | 2 +- src/bitmessagekivy/kv/credits.kv | 2 +- src/bitmessagekivy/kv/login.kv | 2 +- src/bitmessagekivy/kv/msg_composer.kv | 2 +- src/bitmessagekivy/kv/popup.kv | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/bitmessagekivy/kv/common_widgets.kv b/src/bitmessagekivy/kv/common_widgets.kv index 792fef1e..2707c8d8 100644 --- a/src/bitmessagekivy/kv/common_widgets.kv +++ b/src/bitmessagekivy/kv/common_widgets.kv @@ -40,7 +40,7 @@ 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.set_screen('create') on_press: app.clear_composer() diff --git a/src/bitmessagekivy/kv/credits.kv b/src/bitmessagekivy/kv/credits.kv index b5eb3db7..1680d6f0 100644 --- a/src/bitmessagekivy/kv/credits.kv +++ b/src/bitmessagekivy/kv/credits.kv @@ -25,4 +25,4 @@ MDRaisedButton: height: dp(38) text: app.tr._("+Add more credits") - on_press: app.root.ids.scr_mngr.current = 'payment' + on_press: app.set_screen('payment') \ No newline at end of file diff --git a/src/bitmessagekivy/kv/login.kv b/src/bitmessagekivy/kv/login.kv index 44e24c04..992938dc 100644 --- a/src/bitmessagekivy/kv/login.kv +++ b/src/bitmessagekivy/kv/login.kv @@ -80,7 +80,7 @@ icon: "chevron-double-right" text: app.tr._("Proceed Next") on_release: - app.root.ids.scr_mngr.current = 'random' + app.set_screen('random') on_press: app.root.ids.sc7.reset_address_label() diff --git a/src/bitmessagekivy/kv/msg_composer.kv b/src/bitmessagekivy/kv/msg_composer.kv index 82a2a8cb..75c9be30 100644 --- a/src/bitmessagekivy/kv/msg_composer.kv +++ b/src/bitmessagekivy/kv/msg_composer.kv @@ -76,7 +76,7 @@ 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' + if root.is_camara_attached(): app.set_screen('scanscreen') else: root.camera_alert() on_press: app.root.ids.sc23.get_screen('composer') diff --git a/src/bitmessagekivy/kv/popup.kv b/src/bitmessagekivy/kv/popup.kv index 217d9131..fd64ee26 100644 --- a/src/bitmessagekivy/kv/popup.kv +++ b/src/bitmessagekivy/kv/popup.kv @@ -153,7 +153,7 @@ MDRaisedButton: size_hint: 1.5, None height: dp(40) - on_press: app.root.ids.scr_mngr.current = 'showqrcode' + on_press: app.set_screen('showqrcode') on_press: app.root.ids.sc15.qrdisplay(root, root.address) MDLabel: font_style: 'H6' From 8add82c0a2b38500e163e3a4fa5eef2684cdb9e6 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Fri, 18 Feb 2022 16:25:18 +0530 Subject: [PATCH 020/424] Add main.kv in kivy live --- src/bitmessagekivy/main.kv | 378 +++++++++++++++++++++++++++++++++++++ 1 file changed, 378 insertions(+) create mode 100644 src/bitmessagekivy/main.kv diff --git a/src/bitmessagekivy/main.kv b/src/bitmessagekivy/main.kv new file mode 100644 index 00000000..e9c89796 --- /dev/null +++ b/src/bitmessagekivy/main.kv @@ -0,0 +1,378 @@ +#:import get_color_from_hex kivy.utils.get_color_from_hex +#:import Factory kivy.factory.Factory +#:import Spinner kivy.uix.spinner.Spinner + +#:import colors kivymd.color_definitions.colors +#:import images_path kivymd.images_path + +#:import IconLeftWidget kivymd.uix.list.IconLeftWidget +#:import MDCard kivymd.uix.card.MDCard +#:import MDCheckbox kivymd.uix.selectioncontrol.MDCheckbox +#:import MDFloatingActionButton kivymd.uix.button.MDFloatingActionButton +#:import MDList kivymd.uix.list.MDList +#:import MDScrollViewRefreshLayout kivymd.uix.refreshlayout.MDScrollViewRefreshLayout +#:import MDSpinner kivymd.uix.spinner.MDSpinner +#:import MDTextField kivymd.uix.textfield.MDTextField +#:import MDTabs kivymd.uix.tab.MDTabs +#:import MDTabsBase kivymd.uix.tab.MDTabsBase +#:import OneLineListItem kivymd.uix.list.OneLineListItem + + + +#:set color_button (0.784, 0.443, 0.216, 1) # brown +#:set color_button_pressed (0.659, 0.522, 0.431, 1) # darker brown +#:set color_font (0.957, 0.890, 0.843, 1) # off white + +: + font_size: '12.5sp' + background_normal: 'atlas://data/images/defaulttheme/textinput_active' + background_color: app.theme_cls.primary_color + color: color_font + + + on_press: root.currentlyActive() + active_color: root.theme_cls.primary_color if root.active else root.theme_cls.text_color + + IconLeftWidget: + icon: root.icon + theme_text_color: "Custom" + text_color: root.active_color + + BadgeText: + id: badge_txt + text: f"{root.badge_text}" + theme_text_color: "Custom" + halign: 'right' + +: + canvas: + Color: + rgba: self.theme_cls.divider_color + Line: + points: root.x, root.y + dp(8), root.x + self.width, root.y + dp(8) + + + + BoxLayout: + orientation: 'vertical' + + FloatLayout: + size_hint_y: None + height: "200dp" + + MDIconButton: + id: reset_image + icon: "refresh" + x: root.parent.x + dp(10) + pos_hint: {"top": 1, 'left': 1} + color: [1,0,0,1] + on_release: app.rest_default_avatar_img() + theme_text_color: "Custom" + text_color: app.theme_cls.primary_color + opacity: 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() + 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" + x: root.parent.x + pos_hint: {"top": 1} + Image: + source: app.get_default_logo(self) + + ScrollView: + id: scroll_y + pos_hint: {"top": 1} + + GridLayout: + id: box_item + cols: 1 + size_hint_y: None + height: self.minimum_height + NavigationDrawerDivider: + NavigationDrawerSubheader: + text: app.tr._('Accounts') + height:"35dp" + NavigationItem: + 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') + 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) + 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._('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 + Create: + id:sc3 + Sent: + id:sc4 + Trash: + id:sc5 + Login: + id:sc6 + Random: + id:sc7 + 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 + ScanScreen: + id:sc23 + + MDNavigationDrawer: + id: nav_drawer + + ContentNavigationDrawer: + id: content_drawer + + +: + source: app.image_path +('/down-arrow.png' if self.parent.is_open == True else '/right-arrow.png') + size: 15, 15 + x: self.parent.x + self.parent.width - self.width - 5 + y: self.parent.y + self.parent.height/2 - self.height + 5 + + +: + size_hint_y: None + height: self.minimum_height + + MDIconButton: + icon: 'magnify' + + MDTextField: + id: search_field + hint_text: 'Search' + on_text: app.searchQuery(self) + canvas.before: + Color: + rgba: (0,0,0,1) + + +: + id: spinner + size_hint: None, None + size: dp(46), dp(46) + pos_hint: {'center_x': 0.5, 'center_y': 0.5} + active: False + +: + size_hint_y: None + height: dp(56) + spacing: '10dp' + pos_hint: {'center_x':0.45, 'center_y': .1} + + Widget: + + MDFloatingActionButton: + icon: 'plus' + opposite_colors: True + elevation_normal: 8 + md_bg_color: [0.941, 0, 0,1] + on_press: app.root.ids.scr_mngr.current = 'create' + on_press: app.clear_composer() + + +: + size_hint_y: None + height: content.height + + MDCardSwipeLayerBox: + padding: "8dp" + + MDIconButton: + id: delete_msg + icon: "trash-can" + pos_hint: {"center_y": .5} + md_bg_color: (1, 0, 0, 1) + disabled: True + + MDCardSwipeFrontBox: + + TwoLineAvatarIconListItem: + id: content + text: root.text + _no_ripple_effect: True + + AvatarSampleWidget: + id: avater_img + source: None + + TimeTagRightSampleWidget: + id: time_tag + text: '' + font_size: "11sp" + font_style: "Caption" + size: [120, 140] if app.app_platform == "android" else [64, 80] + + +: + size_hint_y: None + height: content.height + + MDCardSwipeLayerBox: + padding: "8dp" + + MDIconButton: + id: delete_msg + icon: "trash-can" + pos_hint: {"center_y": .5} + md_bg_color: (1, 0, 0, 1) + disabled: True + + MDCardSwipeFrontBox: + + TwoLineAvatarIconListItem: + id: content + text: root.text + _no_ripple_effect: True + + AvatarSampleWidget: + id: avater_img + source: None + + TimeTagRightSampleWidget: + id: time_tag + text: 'time' + font_size: "11sp" + font_style: "Caption" + size: [120, 140] if app.app_platform == "android" else [64, 80] + MDChip: + id: chip_tag + size_hint: (0.16 if app.app_platform == "android" else 0.08, None) + text: 'test' + icon: "" + pos_hint: {"center_x": 0.91 if app.app_platform == "android" else 0.94, "center_y": 0.3} + height: '18dp' + text_color: (1,1,1,1) + radius: [8] From 3254d8f2881f1d9cba111aa088956f422c8feece Mon Sep 17 00:00:00 2001 From: Muzahid Date: Tue, 18 Jan 2022 20:13:52 +0530 Subject: [PATCH 021/424] Add test sql scripts for sqlthread --- src/tests/sql/init_version_10.sql | 1 + src/tests/sql/init_version_2.sql | 1 + src/tests/sql/init_version_3.sql | 1 + src/tests/sql/init_version_4.sql | 1 + src/tests/sql/init_version_5.sql | 1 + src/tests/sql/init_version_6.sql | 1 + src/tests/sql/init_version_7.sql | 3 +++ src/tests/sql/init_version_8.sql | 1 + src/tests/sql/init_version_9.sql | 2 ++ 9 files changed, 12 insertions(+) create mode 100644 src/tests/sql/init_version_10.sql create mode 100644 src/tests/sql/init_version_2.sql create mode 100644 src/tests/sql/init_version_3.sql create mode 100644 src/tests/sql/init_version_4.sql create mode 100644 src/tests/sql/init_version_5.sql create mode 100644 src/tests/sql/init_version_6.sql create mode 100644 src/tests/sql/init_version_7.sql create mode 100644 src/tests/sql/init_version_8.sql create mode 100644 src/tests/sql/init_version_9.sql diff --git a/src/tests/sql/init_version_10.sql b/src/tests/sql/init_version_10.sql new file mode 100644 index 00000000..b1764e76 --- /dev/null +++ b/src/tests/sql/init_version_10.sql @@ -0,0 +1 @@ +INSERT INTO `addressbook` VALUES ('test', "BM-2cWzMnxjJ7yRP3nLEWUV5LisTZyREWSxYz"), ('testone', "BM-2cWzMnxjJ7yRP3nLEWUV5LisTZyREWSxYz"); diff --git a/src/tests/sql/init_version_2.sql b/src/tests/sql/init_version_2.sql new file mode 100644 index 00000000..133284ec --- /dev/null +++ b/src/tests/sql/init_version_2.sql @@ -0,0 +1 @@ +INSERT INTO `inventory` VALUES ('hash', 1, 1,1, 1,'test'); diff --git a/src/tests/sql/init_version_3.sql b/src/tests/sql/init_version_3.sql new file mode 100644 index 00000000..875d859d --- /dev/null +++ b/src/tests/sql/init_version_3.sql @@ -0,0 +1 @@ +INSERT INTO `settings` VALUES ('version','3'); diff --git a/src/tests/sql/init_version_4.sql b/src/tests/sql/init_version_4.sql new file mode 100644 index 00000000..ea3f1768 --- /dev/null +++ b/src/tests/sql/init_version_4.sql @@ -0,0 +1 @@ +INSERT INTO `pubkeys` VALUES ('hash', 1, 1, 1,'test'); diff --git a/src/tests/sql/init_version_5.sql b/src/tests/sql/init_version_5.sql new file mode 100644 index 00000000..b894c038 --- /dev/null +++ b/src/tests/sql/init_version_5.sql @@ -0,0 +1 @@ +INSERT INTO `objectprocessorqueue` VALUES ('hash', 1); diff --git a/src/tests/sql/init_version_6.sql b/src/tests/sql/init_version_6.sql new file mode 100644 index 00000000..7cd30571 --- /dev/null +++ b/src/tests/sql/init_version_6.sql @@ -0,0 +1 @@ +INSERT INTO `inventory` VALUES ('hash', 1, 1, 1,'test','test'); diff --git a/src/tests/sql/init_version_7.sql b/src/tests/sql/init_version_7.sql new file mode 100644 index 00000000..bd87f8d8 --- /dev/null +++ b/src/tests/sql/init_version_7.sql @@ -0,0 +1,3 @@ +INSERT INTO `sent` VALUES +(1,'BM-2cWzMnxjJ7yRP3nLEWUV5LisTZyREWSxYz',1,'BM-2cWzSnwjJ7yRP3nLEWUV5LisTZyREWSzUK','Test1 subject','message test 1','ackdata',1638176409,1638176409,1638176423,'msgqueued',1,'testfolder',1,2), +(2,'BM-2cWzMnxjJ7yRP3nLEWUV5LisTZyREWSxYz',1,'BM-2cWzSnwjJ7yRP3nLEWUV5LisTZyREWSzUK','Test2 subject','message test 2','ackdata',1638176423,1638176423,1638176423,'msgqueued',1,'testfolder',1,2); diff --git a/src/tests/sql/init_version_8.sql b/src/tests/sql/init_version_8.sql new file mode 100644 index 00000000..9d9b6f3a --- /dev/null +++ b/src/tests/sql/init_version_8.sql @@ -0,0 +1 @@ +INSERT INTO `inbox` VALUES (1, "poland", "malasia", "test", "yes", "test message", "folder", 1, 1, 1); diff --git a/src/tests/sql/init_version_9.sql b/src/tests/sql/init_version_9.sql new file mode 100644 index 00000000..764634d2 --- /dev/null +++ b/src/tests/sql/init_version_9.sql @@ -0,0 +1,2 @@ +INSERT INTO `sent` VALUES +(1,'BM-2cWzMnxjJ7yRP3nLEWUV5LisTZyREWSxYz',1,'BM-2cWzSnwjJ7yRP3nLEWUV5LisTZyREWSzUK','Test1 subject','message test 1','ackdata',1638176409,1638176409,1638176423,'msgqueued',1,'testfolder',1,2); From 008eab130f894dacdba19da50ede63c823a4ef48 Mon Sep 17 00:00:00 2001 From: Muzahid Date: Thu, 23 Dec 2021 21:54:59 +0530 Subject: [PATCH 022/424] seperate kivy test dockerfile --- .buildbot/kivy/Dockerfile | 19 +++++++++++++++++++ .buildbot/kivy/test.sh | 3 +++ 2 files changed, 22 insertions(+) create mode 100644 .buildbot/kivy/Dockerfile create mode 100644 .buildbot/kivy/test.sh diff --git a/.buildbot/kivy/Dockerfile b/.buildbot/kivy/Dockerfile new file mode 100644 index 00000000..e46b16bf --- /dev/null +++ b/.buildbot/kivy/Dockerfile @@ -0,0 +1,19 @@ +# A container for buildbot +FROM ubuntu:bionic AS kivy +# FROM ubuntu:20.04 AS buildbot-bionic + +RUN apt-get update + +RUN apt-get -y install sudo + +RUN apt-get install -yq python-setuptools \ + python-setuptools libssl-dev libpq-dev python-prctl python-dev \ + python-dev python-virtualenv python-pip virtualenv \ + libssl-dev \ + python-dev \ + python3-virtualenv \ + python3-pip \ + +RUN sudo wget -0 "/usr/local/bin/travis2bash.sh" "https://git.bitmessage.org/Bitmessage/buildbot-scripts/raw/branch/master/travis2bash.sh" + +RUN sudo chmod a+rx "/usr/local/bin/travis2bash.sh" diff --git a/.buildbot/kivy/test.sh b/.buildbot/kivy/test.sh new file mode 100644 index 00000000..04838747 --- /dev/null +++ b/.buildbot/kivy/test.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +travis2bash.sh .travis-kivy.yml From e11b654e377b761984e39611c5a8549eb373fee6 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Tue, 15 Mar 2022 17:00:22 +0530 Subject: [PATCH 023/424] Refactor kivy addressbook.py --- src/bitmessagekivy/baseclass/addressbook.py | 171 ++++++++++++++++++ .../baseclass/addressbook_widgets.py | 54 ++++++ 2 files changed, 225 insertions(+) create mode 100644 src/bitmessagekivy/baseclass/addressbook.py create mode 100644 src/bitmessagekivy/baseclass/addressbook_widgets.py diff --git a/src/bitmessagekivy/baseclass/addressbook.py b/src/bitmessagekivy/baseclass/addressbook.py new file mode 100644 index 00000000..5a11ee32 --- /dev/null +++ b/src/bitmessagekivy/baseclass/addressbook.py @@ -0,0 +1,171 @@ +# pylint: disable=unused-argument, consider-using-f-string, import-error +# pylint: disable=unnecessary-comprehension, no-member, no-name-in-module + +""" +addressbook.py +============== + +All saved addresses are managed in Addressbook + +""" + +from functools import partial + +from kivy.clock import Clock +from kivy.properties import ( + ListProperty, + StringProperty +) +from kivy.uix.screenmanager import Screen + +from helper_sql import sqlExecute + +import state + +from bitmessagekivy.get_platform import platform +from bitmessagekivy import kivy_helper_search +from bitmessagekivy.baseclass.common import ( + avatarImageFirstLetter, toast, + ThemeClsColor, SwipeToDeleteItem +) +from bitmessagekivy.baseclass.popup import AddbookDetailPopup +from bitmessagekivy.baseclass.addressbook_widgets import HelperAddressBook + + +class AddressBook(Screen, HelperAddressBook): + """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.searching_text: + self.ids.scroll_y.scroll_y = 1.0 + where = ['label', 'address'] + what = state.searching_text + xAddress = '' + self.ids.tag_label.text = '' + self.queryreturn = kivy_helper_search.search_sql( + xAddress, account, "addressbook", where, what, False) + self.queryreturn = [obj for obj in reversed(self.queryreturn)] + if self.queryreturn: + self.ids.tag_label.text = 'Address Book' + self.has_refreshed = True + self.set_mdList(0, 20) + self.ids.scroll_y.bind(scroll_y=self.check_scroll_y) + else: + self.ids.ml.add_widget(self.default_label_when_empty()) + + def set_mdList(self, start_index, end_index): + """Creating the mdList""" + for item in self.queryreturn[start_index:end_index]: + message_row = SwipeToDeleteItem( + text=item[0], + ) + listItem = message_row.ids.content + listItem.secondary_text = item[1] + listItem.theme_text_color = "Custom" + listItem.text_color = ThemeClsColor + image = 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""" + + # @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 = self.address_detail_popup( + self.send_message_to, self.update_addbook_label, self.close_pop, + width=width, obj=obj) + 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() diff --git a/src/bitmessagekivy/baseclass/addressbook_widgets.py b/src/bitmessagekivy/baseclass/addressbook_widgets.py new file mode 100644 index 00000000..6b1547d8 --- /dev/null +++ b/src/bitmessagekivy/baseclass/addressbook_widgets.py @@ -0,0 +1,54 @@ +# pylint: disable=no-member, too-many-arguments, no-self-use +""" +Addressbook widgets are here. +""" + + +from kivymd.uix.button import MDRaisedButton +from kivymd.uix.dialog import MDDialog +from kivymd.uix.label import MDLabel + +import state + + +class HelperAddressBook(object): + """Widget used in Addressbook are here""" + def __init__(self): + pass + + @staticmethod + def default_label_when_empty(): + """This function returns default message while no address is there.""" + content = MDLabel( + font_style='Caption', + theme_text_color='Primary', + # FIXME: searching_text supposed to be inside kivy_sate.py and need to create a PR for kivy_state.py + text="No contact found!" if state.searching_text + else "No contact found yet...... ", + halign='center', + size_hint_y=None, + valign='top') + return content + + def address_detail_popup(self, send_message, update_address, close_popup, width, obj): + """This function shows the address's details and opens the popup.""" + show_dialogue = MDDialog( + type="custom", + size_hint=(width, .25), + content_cls=obj, + buttons=[ + MDRaisedButton( + text="Send message to", + on_release=send_message, + ), + MDRaisedButton( + text="Save", + on_release=update_address, + ), + MDRaisedButton( + text="Cancel", + on_release=close_popup, + ), + ], + ) + return show_dialogue From b772b2ce9be6caf9e2d50a2338add1512074c931 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Wed, 16 Mar 2022 19:06:35 +0530 Subject: [PATCH 024/424] Fixed code quality --- src/bitmessagekivy/baseclass/addressbook.py | 17 +++++++++-------- .../baseclass/addressbook_widgets.py | 10 ++++------ 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/bitmessagekivy/baseclass/addressbook.py b/src/bitmessagekivy/baseclass/addressbook.py index 5a11ee32..6b289c43 100644 --- a/src/bitmessagekivy/baseclass/addressbook.py +++ b/src/bitmessagekivy/baseclass/addressbook.py @@ -18,10 +18,10 @@ from kivy.properties import ( ) from kivy.uix.screenmanager import Screen -from helper_sql import sqlExecute import state + from bitmessagekivy.get_platform import platform from bitmessagekivy import kivy_helper_search from bitmessagekivy.baseclass.common import ( @@ -30,6 +30,8 @@ from bitmessagekivy.baseclass.common import ( ) from bitmessagekivy.baseclass.popup import AddbookDetailPopup from bitmessagekivy.baseclass.addressbook_widgets import HelperAddressBook +from debug import logger +from helper_sql import sqlExecute class AddressBook(Screen, HelperAddressBook): @@ -49,7 +51,7 @@ class AddressBook(Screen, HelperAddressBook): def init_ui(self, dt=0): """Clock Schdule for method AddressBook""" self.loadAddresslist(None, 'All', '') - print(dt) + logger.debug(dt) def loadAddresslist(self, account, where="", what=""): """Clock Schdule for method AddressBook""" @@ -118,8 +120,8 @@ class AddressBook(Screen, HelperAddressBook): self.address = obj.address = address width = .9 if platform == 'android' else .8 self.addbook_popup = self.address_detail_popup( - self.send_message_to, self.update_addbook_label, self.close_pop, - width=width, obj=obj) + obj, self.send_message_to, self.update_addbook_label, + self.close_pop, width) self.addbook_popup.auto_dismiss = False self.addbook_popup.open() else: @@ -132,7 +134,7 @@ class AddressBook(Screen, HelperAddressBook): if self.ids.ml.children is not None: self.ids.tag_label.text = '' sqlExecute( - "DELETE FROM addressbook WHERE address = '{}';".format(address)) + "DELETE FROM addressbook WHERE address = ?", address) toast('Address Deleted') def close_pop(self, instance): @@ -150,9 +152,8 @@ class AddressBook(Screen, HelperAddressBook): 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)) + "UPDATE addressbook SET label = ? WHERE" + " address = ?", 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() diff --git a/src/bitmessagekivy/baseclass/addressbook_widgets.py b/src/bitmessagekivy/baseclass/addressbook_widgets.py index 6b1547d8..3eb139e1 100644 --- a/src/bitmessagekivy/baseclass/addressbook_widgets.py +++ b/src/bitmessagekivy/baseclass/addressbook_widgets.py @@ -1,4 +1,4 @@ -# pylint: disable=no-member, too-many-arguments, no-self-use +# pylint: disable=no-member, too-many-arguments """ Addressbook widgets are here. """ @@ -24,13 +24,11 @@ class HelperAddressBook(object): theme_text_color='Primary', # FIXME: searching_text supposed to be inside kivy_sate.py and need to create a PR for kivy_state.py text="No contact found!" if state.searching_text - else "No contact found yet...... ", - halign='center', - size_hint_y=None, - valign='top') + else "No contact found yet...... ", halign='center', size_hint_y=None, valign='top') return content - def address_detail_popup(self, send_message, update_address, close_popup, width, obj): + @staticmethod + def address_detail_popup(obj, send_message, update_address, close_popup, width): """This function shows the address's details and opens the popup.""" show_dialogue = MDDialog( type="custom", From 0ec38b43f833902ece948ac20f34300537beca00 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Thu, 17 Mar 2022 18:25:31 +0530 Subject: [PATCH 025/424] Created a UI independent function --- src/bitmessagekivy/baseclass/addressbook.py | 8 +------- .../baseclass/addressbook_widgets.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/bitmessagekivy/baseclass/addressbook.py b/src/bitmessagekivy/baseclass/addressbook.py index 6b289c43..98b1ef56 100644 --- a/src/bitmessagekivy/baseclass/addressbook.py +++ b/src/bitmessagekivy/baseclass/addressbook.py @@ -162,11 +162,5 @@ class AddressBook(Screen, HelperAddressBook): 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.compose_message(None, self.address) self.addbook_popup.dismiss() diff --git a/src/bitmessagekivy/baseclass/addressbook_widgets.py b/src/bitmessagekivy/baseclass/addressbook_widgets.py index 3eb139e1..ed7e6196 100644 --- a/src/bitmessagekivy/baseclass/addressbook_widgets.py +++ b/src/bitmessagekivy/baseclass/addressbook_widgets.py @@ -50,3 +50,17 @@ class HelperAddressBook(object): ], ) return show_dialogue + + @staticmethod + def compose_message(from_addr=None, to_addr=None): + """This UI independent method for message sending to reciever""" + window_obj = state.kivyapp.root.ids + if to_addr: + window_obj.sc3.children[1].ids.txt_input.text = to_addr + if from_addr: + window_obj.sc3.children[1].ids.txt_input.text = from_addr + 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' From 70dcb944c8bb9b9eb5dc1dfd219c6438b90c057a Mon Sep 17 00:00:00 2001 From: Muzahid Date: Tue, 18 Jan 2022 19:59:17 +0530 Subject: [PATCH 026/424] add sql scripts files for sqlthread, change in sql import, updated code for sql file loading and fixed some linting --- packages/pyinstaller/bitmessagemain.spec | 23 ++++-- setup.py | 2 +- src/build_osx.py | 1 + src/sql/config_setting_ver_2.sql | 1 + src/sql/config_setting_ver_3.sql | 5 ++ src/sql/init_version_10.sql | 15 ++++ src/sql/init_version_2.sql | 29 +++++++ src/sql/init_version_3.sql | 5 ++ src/sql/init_version_4.sql | 17 ++++ src/sql/init_version_5.sql | 12 +++ src/sql/init_version_6.sql | 25 ++++++ src/sql/init_version_7.sql | 11 +++ src/sql/init_version_8.sql | 7 ++ src/sql/init_version_9.sql | 74 +++++++++++++++++ src/sql/initialize_schema.sql | 100 +++++++++++++++++++++++ src/sql/upg_sc_if_old_ver_1.sql | 30 +++++++ src/sql/upg_sc_if_old_ver_2.sql | 7 ++ 17 files changed, 355 insertions(+), 9 deletions(-) create mode 100644 src/sql/config_setting_ver_2.sql create mode 100644 src/sql/config_setting_ver_3.sql create mode 100644 src/sql/init_version_10.sql create mode 100644 src/sql/init_version_2.sql create mode 100644 src/sql/init_version_3.sql create mode 100644 src/sql/init_version_4.sql create mode 100644 src/sql/init_version_5.sql create mode 100644 src/sql/init_version_6.sql create mode 100644 src/sql/init_version_7.sql create mode 100644 src/sql/init_version_8.sql create mode 100644 src/sql/init_version_9.sql create mode 100644 src/sql/initialize_schema.sql create mode 100644 src/sql/upg_sc_if_old_ver_1.sql create mode 100644 src/sql/upg_sc_if_old_ver_2.sql diff --git a/packages/pyinstaller/bitmessagemain.spec b/packages/pyinstaller/bitmessagemain.spec index 8d38ec09..2ee65035 100644 --- a/packages/pyinstaller/bitmessagemain.spec +++ b/packages/pyinstaller/bitmessagemain.spec @@ -6,6 +6,7 @@ import time from PyInstaller.utils.hooks import copy_metadata + site_root = os.path.abspath(HOMEPATH) spec_root = os.path.abspath(SPECPATH) arch = 32 if ctypes.sizeof(ctypes.c_voidp) == 4 else 64 @@ -22,13 +23,13 @@ os.chdir(srcPath) snapshot = False -hookspath=os.path.join(spec_root, 'hooks') +hookspath = os.path.join(spec_root, 'hooks') a = Analysis( [os.path.join(srcPath, 'bitmessagemain.py')], - datas = [ + datas=[ (os.path.join(spec_root[:-20], 'pybitmessage.egg-info') + '/*', - 'pybitmessage.egg-info') + 'pybitmessage.egg-info') ] + copy_metadata('msgpack-python') + copy_metadata('qrcode') + copy_metadata('six') + copy_metadata('stem'), pathex=[outPath], @@ -72,6 +73,13 @@ a.datas += [ for file_ in os.listdir(dir_append) if file_.endswith('.ui') ] +sql_dir = os.path.join(srcPath, 'sql') + +a.datas += [ + ('sql', os.path.join(sql_dir, file_), 'DATA') + for file_ in os.listdir(sql_dir) if file_.endswith('.sql') +] + # append the translations directory a.datas += addTranslations() a.datas += [('default.ini', os.path.join(srcPath, 'default.ini'), 'DATA')] @@ -88,16 +96,15 @@ a.binaries += [ ('libeay32.dll', os.path.join(openSSLPath, 'libeay32.dll'), 'BINARY'), (os.path.join('bitmsghash', 'bitmsghash%i.dll' % arch), os.path.join(srcPath, 'bitmsghash', 'bitmsghash%i.dll' % arch), - 'BINARY'), + 'BINARY'), (os.path.join('bitmsghash', 'bitmsghash.cl'), - os.path.join(srcPath, 'bitmsghash', 'bitmsghash.cl'), 'BINARY'), + os.path.join(srcPath, 'bitmsghash', 'bitmsghash.cl'), 'BINARY'), (os.path.join('sslkeys', 'cert.pem'), - os.path.join(srcPath, 'sslkeys', 'cert.pem'), 'BINARY'), + os.path.join(srcPath, 'sslkeys', 'cert.pem'), 'BINARY'), (os.path.join('sslkeys', 'key.pem'), - os.path.join(srcPath, 'sslkeys', 'key.pem'), 'BINARY') + os.path.join(srcPath, 'sslkeys', 'key.pem'), 'BINARY') ] - from version import softwareVersion today = time.strftime("%Y%m%d") diff --git a/setup.py b/setup.py index fea2e58a..171526c3 100644 --- a/setup.py +++ b/setup.py @@ -136,7 +136,7 @@ if __name__ == "__main__": packages=packages, package_data={'': [ 'bitmessageqt/*.ui', 'bitmsghash/*.cl', 'sslkeys/*.pem', - 'translations/*.ts', 'translations/*.qm', 'default.ini', + 'translations/*.ts', 'translations/*.qm', 'default.ini', 'sql/*.sql', 'images/*.png', 'images/*.ico', 'images/*.icns' ]}, data_files=data_files, diff --git a/src/build_osx.py b/src/build_osx.py index 3fcefbfb..d83e9b9b 100644 --- a/src/build_osx.py +++ b/src/build_osx.py @@ -10,6 +10,7 @@ mainscript = ["bitmessagemain.py"] DATA_FILES = [ ('', ['sslkeys', 'images', 'default.ini']), + ('sql', glob('sql/*.sql')), ('bitmsghash', ['bitmsghash/bitmsghash.cl', 'bitmsghash/bitmsghash.so']), ('translations', glob('translations/*.qm')), ('ui', glob('bitmessageqt/*.ui')), diff --git a/src/sql/config_setting_ver_2.sql b/src/sql/config_setting_ver_2.sql new file mode 100644 index 00000000..087d297a --- /dev/null +++ b/src/sql/config_setting_ver_2.sql @@ -0,0 +1 @@ +ALTER TABLE pubkeys ADD usedpersonally text DEFAULT 'no'; diff --git a/src/sql/config_setting_ver_3.sql b/src/sql/config_setting_ver_3.sql new file mode 100644 index 00000000..4bdcccc8 --- /dev/null +++ b/src/sql/config_setting_ver_3.sql @@ -0,0 +1,5 @@ +ALTER TABLE inbox ADD encodingtype int DEFAULT '2'; + +ALTER TABLE inbox ADD read bool DEFAULT '1'; + +ALTER TABLE sent ADD encodingtype int DEFAULT '2'; diff --git a/src/sql/init_version_10.sql b/src/sql/init_version_10.sql new file mode 100644 index 00000000..8bd8b0b3 --- /dev/null +++ b/src/sql/init_version_10.sql @@ -0,0 +1,15 @@ +-- -- +-- -- Update the address colunm to unique in addressbook table +-- -- + +ALTER TABLE addressbook RENAME TO old_addressbook; + +CREATE TABLE `addressbook` ( + `label` text , + `address` text , + UNIQUE(address) ON CONFLICT IGNORE +) ; + +INSERT INTO addressbook SELECT label, address FROM old_addressbook; + +DROP TABLE old_addressbook; diff --git a/src/sql/init_version_2.sql b/src/sql/init_version_2.sql new file mode 100644 index 00000000..ea42df4c --- /dev/null +++ b/src/sql/init_version_2.sql @@ -0,0 +1,29 @@ +-- +-- Let's get rid of the first20bytesofencryptedmessage field in the inventory table. +-- + +CREATE TEMP TABLE `inventory_backup` ( + `hash` blob , + `objecttype` text , + `streamnumber` int , + `payload` blob , + `receivedtime` int , + UNIQUE(hash) ON CONFLICT REPLACE +) ; + +INSERT INTO `inventory_backup` SELECT hash, objecttype, streamnumber, payload, receivedtime FROM inventory; + +DROP TABLE inventory; + +CREATE TABLE `inventory` ( + `hash` blob , + `objecttype` text , + `streamnumber` int , + `payload` blob , + `receivedtime` int , + UNIQUE(hash) ON CONFLICT REPLACE +) ; + +INSERT INTO inventory SELECT hash, objecttype, streamnumber, payload, receivedtime FROM inventory_backup; + +DROP TABLE inventory_backup; diff --git a/src/sql/init_version_3.sql b/src/sql/init_version_3.sql new file mode 100644 index 00000000..9de784a5 --- /dev/null +++ b/src/sql/init_version_3.sql @@ -0,0 +1,5 @@ +-- +-- Add a new column to the inventory table to store tags. +-- + +ALTER TABLE inventory ADD tag blob DEFAULT ''; diff --git a/src/sql/init_version_4.sql b/src/sql/init_version_4.sql new file mode 100644 index 00000000..d2fd393d --- /dev/null +++ b/src/sql/init_version_4.sql @@ -0,0 +1,17 @@ + -- + -- Add a new column to the pubkeys table to store the address version. + -- We're going to trash all of our pubkeys and let them be redownloaded. + -- + +DROP TABLE pubkeys; + +CREATE TABLE `pubkeys` ( + `hash` blob , + `addressversion` int , + `transmitdata` blob , + `time` int , + `usedpersonally` text , + UNIQUE(hash, addressversion) ON CONFLICT REPLACE +) ; + +DELETE FROM inventory WHERE objecttype = 'pubkey'; diff --git a/src/sql/init_version_5.sql b/src/sql/init_version_5.sql new file mode 100644 index 00000000..a13fa8cf --- /dev/null +++ b/src/sql/init_version_5.sql @@ -0,0 +1,12 @@ + -- + -- Add a new table: objectprocessorqueue with which to hold objects + -- that have yet to be processed if the user shuts down Bitmessage. + -- + +DROP TABLE knownnodes; + +CREATE TABLE `objectprocessorqueue` ( + `objecttype` text, + `data` blob, + UNIQUE(objecttype, data) ON CONFLICT REPLACE +) ; diff --git a/src/sql/init_version_6.sql b/src/sql/init_version_6.sql new file mode 100644 index 00000000..b9a03669 --- /dev/null +++ b/src/sql/init_version_6.sql @@ -0,0 +1,25 @@ +-- +-- changes related to protocol v3 +-- In table inventory and objectprocessorqueue, objecttype is now +-- an integer (it was a human-friendly string previously) +-- + +DROP TABLE inventory; + +CREATE TABLE `inventory` ( + `hash` blob, + `objecttype` int, + `streamnumber` int, + `payload` blob, + `expirestime` integer, + `tag` blob, + UNIQUE(hash) ON CONFLICT REPLACE +) ; + +DROP TABLE objectprocessorqueue; + +CREATE TABLE `objectprocessorqueue` ( + `objecttype` int, + `data` blob, + UNIQUE(objecttype, data) ON CONFLICT REPLACE +) ; diff --git a/src/sql/init_version_7.sql b/src/sql/init_version_7.sql new file mode 100644 index 00000000..a2f6f6e3 --- /dev/null +++ b/src/sql/init_version_7.sql @@ -0,0 +1,11 @@ +-- +-- The format of data stored in the pubkeys table has changed. Let's +-- clear it, and the pubkeys from inventory, so that they'll +-- be re-downloaded. +-- + +DELETE FROM inventory WHERE objecttype = 1; + +DELETE FROM pubkeys; + +UPDATE sent SET status='msgqueued' WHERE status='doingmsgpow' or status='badkey'; diff --git a/src/sql/init_version_8.sql b/src/sql/init_version_8.sql new file mode 100644 index 00000000..0c1813d3 --- /dev/null +++ b/src/sql/init_version_8.sql @@ -0,0 +1,7 @@ +-- +-- Add a new column to the inbox table to store the hash of +-- the message signature. We'll use this as temporary message UUID +-- in order to detect duplicates. +-- + +ALTER TABLE inbox ADD sighash blob DEFAULT ''; diff --git a/src/sql/init_version_9.sql b/src/sql/init_version_9.sql new file mode 100644 index 00000000..bc8296b9 --- /dev/null +++ b/src/sql/init_version_9.sql @@ -0,0 +1,74 @@ +CREATE TEMPORARY TABLE `sent_backup` ( + `msgid` blob, + `toaddress` text, + `toripe` blob, + `fromaddress` text, + `subject` text, + `message` text, + `ackdata` blob, + `lastactiontime` integer, + `status` text, + `retrynumber` integer, + `folder` text, + `encodingtype` int +) ; + +INSERT INTO sent_backup SELECT msgid, toaddress, toripe, fromaddress, subject, message, ackdata, lastactiontime, status, 0, folder, encodingtype FROM sent; + +DROP TABLE sent; + +CREATE TABLE `sent` ( + `msgid` blob, + `toaddress` text, + `toripe` blob, + `fromaddress` text, + `subject` text, + `message` text, + `ackdata` blob, + `senttime` integer, + `lastactiontime` integer, + `sleeptill` int, + `status` text, + `retrynumber` integer, + `folder` text, + `encodingtype` int, + `ttl` int +) ; + +INSERT INTO sent SELECT msgid, toaddress, toripe, fromaddress, subject, message, ackdata, lastactiontime, lastactiontime, 0, status, 0, folder, encodingtype, 216000 FROM sent_backup; + +DROP TABLE sent_backup; + +ALTER TABLE pubkeys ADD address text DEFAULT '' ; + +-- +-- replica for loop to update hashed address +-- + +UPDATE pubkeys SET address=(enaddr(pubkeys.addressversion, 1, hash)); + +CREATE TEMPORARY TABLE `pubkeys_backup` ( + `address` text, + `addressversion` int, + `transmitdata` blob, + `time` int, + `usedpersonally` text, + UNIQUE(address) ON CONFLICT REPLACE +) ; + +INSERT INTO pubkeys_backup SELECT address, addressversion, transmitdata, `time`, usedpersonally FROM pubkeys; + +DROP TABLE pubkeys; + +CREATE TABLE `pubkeys` ( + `address` text, + `addressversion` int, + `transmitdata` blob, + `time` int, + `usedpersonally` text, + UNIQUE(address) ON CONFLICT REPLACE +) ; + +INSERT INTO pubkeys SELECT address, addressversion, transmitdata, `time`, usedpersonally FROM pubkeys_backup; + +DROP TABLE pubkeys_backup; diff --git a/src/sql/initialize_schema.sql b/src/sql/initialize_schema.sql new file mode 100644 index 00000000..8413aa0a --- /dev/null +++ b/src/sql/initialize_schema.sql @@ -0,0 +1,100 @@ +CREATE TABLE `inbox` ( + `msgid` blob, + `toaddress` text, + `fromaddress` text, + `subject` text, + `received` text, + `message` text, + `folder` text, + `encodingtype` int, + `read` bool, + `sighash` blob, +UNIQUE(msgid) ON CONFLICT REPLACE +) ; + +CREATE TABLE `sent` ( + `msgid` blob, + `toaddress` text, + `toripe` blob, + `fromaddress` text, + `subject` text, + `message` text, + `ackdata` blob, + `senttime` integer, + `lastactiontime` integer, + `sleeptill` integer, + `status` text, + `retrynumber` integer, + `folder` text, + `encodingtype` int, + `ttl` int +) ; + + +CREATE TABLE `subscriptions` ( + `label` text, + `address` text, + `enabled` bool +) ; + + +CREATE TABLE `addressbook` ( + `label` text, + `address` text, + UNIQUE(address) ON CONFLICT IGNORE +) ; + + + CREATE TABLE `blacklist` ( + `label` text, + `address` text, + `enabled` bool + ) ; + + + CREATE TABLE `whitelist` ( + `label` text, + `address` text, + `enabled` bool + ) ; + + +CREATE TABLE `pubkeys` ( + `address` text, + `addressversion` int, + `transmitdata` blob, + `time` int, + `usedpersonally` text, + UNIQUE(address) ON CONFLICT REPLACE +) ; + + +CREATE TABLE `inventory` ( + `hash` blob, + `objecttype` int, + `streamnumber` int, + `payload` blob, + `expirestime` integer, + `tag` blob, + UNIQUE(hash) ON CONFLICT REPLACE +) ; + + +INSERT INTO subscriptions VALUES ('Bitmessage new releases/announcements', 'BM-GtovgYdgs7qXPkoYaRgrLFuFKz1SFpsw', 1); + + + CREATE TABLE `settings` ( + `key` blob, + `value` blob, + UNIQUE(key) ON CONFLICT REPLACE + ) ; + +INSERT INTO settings VALUES('version','11'); + +INSERT INTO settings VALUES('lastvacuumtime', CAST(strftime('%s', 'now') AS STR) ); + +CREATE TABLE `objectprocessorqueue` ( + `objecttype` int, + `data` blob, + UNIQUE(objecttype, data) ON CONFLICT REPLACE +) ; diff --git a/src/sql/upg_sc_if_old_ver_1.sql b/src/sql/upg_sc_if_old_ver_1.sql new file mode 100644 index 00000000..18a5ecfc --- /dev/null +++ b/src/sql/upg_sc_if_old_ver_1.sql @@ -0,0 +1,30 @@ +CREATE TEMPORARY TABLE `pubkeys_backup` ( + `hash` blob, + `transmitdata` blob, + `time` int, + `usedpersonally` text, + UNIQUE(hash) ON CONFLICT REPLACE +) ; + +INSERT INTO `pubkeys_backup` SELECT hash, transmitdata, `time`, usedpersonally FROM `pubkeys`; + +DROP TABLE `pubkeys` + +CREATE TABLE `pubkeys` ( + `hash` blob, + `transmitdata` blob, + `time` int, + `usedpersonally` text, + UNIQUE(hash) ON CONFLICT REPLACE +) ; + + +INSERT INTO `pubkeys` SELECT hash, transmitdata, `time`, usedpersonally FROM `pubkeys_backup`; + +DROP TABLE `pubkeys_backup`; + +DELETE FROM inventory WHERE objecttype = 'pubkey'; + +DELETE FROM subscriptions WHERE address='BM-BbkPSZbzPwpVcYZpU4yHwf9ZPEapN5Zx' + +INSERT INTO subscriptions VALUES('Bitmessage new releases/announcements','BM-GtovgYdgs7qXPkoYaRgrLFuFKz1SFpsw',1) diff --git a/src/sql/upg_sc_if_old_ver_2.sql b/src/sql/upg_sc_if_old_ver_2.sql new file mode 100644 index 00000000..1fde0098 --- /dev/null +++ b/src/sql/upg_sc_if_old_ver_2.sql @@ -0,0 +1,7 @@ +UPDATE `sent` SET status='doingmsgpow' WHERE status='doingpow'; + +UPDATE `sent` SET status='msgsent' WHERE status='sentmessage'; + +UPDATE `sent` SET status='doingpubkeypow' WHERE status='findingpubkey'; + +UPDATE `sent` SET status='broadcastqueued' WHERE status='broadcastpending'; From aae3f0e6ac33382ad557888c1c98580fe97e617c Mon Sep 17 00:00:00 2001 From: surbhicis Date: Fri, 1 Apr 2022 15:57:04 +0530 Subject: [PATCH 027/424] Docker bug fixes --- .buildbot/android/Dockerfile | 6 ++++-- .buildbot/kivy/Dockerfile | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.buildbot/android/Dockerfile b/.buildbot/android/Dockerfile index 890355e1..f8b8f673 100644 --- a/.buildbot/android/Dockerfile +++ b/.buildbot/android/Dockerfile @@ -2,16 +2,18 @@ FROM ubuntu:bionic AS android # FROM ubuntu:20.04 AS buildbot-bionic +RUN apt -y update -qq +RUN apt -y install wget + RUN wget -nc "https://dl.google.com/android/repository/sdk-tools-linux-4333796.zip" RUN wget -nc "https://dl.google.com/android/repository/android-ndk-r23b-linux.zip" RUN wget -nc "http://archive.apache.org/dist/ant/binaries/apache-ant-1.10.12-bin.tar.gz" # SYSTEM DEPENDENCIES -RUN apt -y update -qq RUN apt -y install --no-install-recommends python3-pip \ pip3 python3 virtualenv python3-setuptools \ - python3-wheel git wget unzip sudo patch bzip2 lzma + python3-wheel git unzip sudo patch bzip2 lzma RUN apt -y autoremove # BUILD DEPENDENCIES diff --git a/.buildbot/kivy/Dockerfile b/.buildbot/kivy/Dockerfile index e46b16bf..4e78165e 100644 --- a/.buildbot/kivy/Dockerfile +++ b/.buildbot/kivy/Dockerfile @@ -14,6 +14,6 @@ RUN apt-get install -yq python-setuptools \ python3-virtualenv \ python3-pip \ -RUN sudo wget -0 "/usr/local/bin/travis2bash.sh" "https://git.bitmessage.org/Bitmessage/buildbot-scripts/raw/branch/master/travis2bash.sh" +RUN sudo wget -O "/usr/local/bin/travis2bash.sh" "https://git.bitmessage.org/Bitmessage/buildbot-scripts/raw/branch/master/travis2bash.sh" RUN sudo chmod a+rx "/usr/local/bin/travis2bash.sh" From 315973ceec7686d9867b61f232859b937180d180 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Mon, 28 Mar 2022 16:59:22 +0530 Subject: [PATCH 028/424] Fixed image path --- src/bitmessagekivy/baseclass/addressbook.py | 6 +++-- .../baseclass/addressbook_widgets.py | 22 ++++++++++++------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/bitmessagekivy/baseclass/addressbook.py b/src/bitmessagekivy/baseclass/addressbook.py index 98b1ef56..8e6cd860 100644 --- a/src/bitmessagekivy/baseclass/addressbook.py +++ b/src/bitmessagekivy/baseclass/addressbook.py @@ -9,6 +9,7 @@ All saved addresses are managed in Addressbook """ +import os from functools import partial from kivy.clock import Clock @@ -82,8 +83,9 @@ class AddressBook(Screen, HelperAddressBook): listItem.secondary_text = item[1] listItem.theme_text_color = "Custom" listItem.text_color = ThemeClsColor - image = state.imageDir + "/text_images/{}.png".format( - avatarImageFirstLetter(item[0].strip())) + image = os.path.join( + 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)) diff --git a/src/bitmessagekivy/baseclass/addressbook_widgets.py b/src/bitmessagekivy/baseclass/addressbook_widgets.py index ed7e6196..e8cd982d 100644 --- a/src/bitmessagekivy/baseclass/addressbook_widgets.py +++ b/src/bitmessagekivy/baseclass/addressbook_widgets.py @@ -1,4 +1,4 @@ -# pylint: disable=no-member, too-many-arguments +# pylint: disable=no-member, too-many-arguments, too-few-public-methods """ Addressbook widgets are here. """ @@ -10,11 +10,13 @@ from kivymd.uix.label import MDLabel import state +no_address_found = "No contact found yet......" +empty_search_label = "No contact found!" -class HelperAddressBook(object): - """Widget used in Addressbook are here""" - def __init__(self): - pass + +# pylint: disable=no-init, old-style-class +class DefaultLabelMixin: + """Common label on blank screen""" @staticmethod def default_label_when_empty(): @@ -22,11 +24,15 @@ class HelperAddressBook(object): content = MDLabel( font_style='Caption', theme_text_color='Primary', - # FIXME: searching_text supposed to be inside kivy_sate.py and need to create a PR for kivy_state.py - text="No contact found!" if state.searching_text - else "No contact found yet...... ", halign='center', size_hint_y=None, valign='top') + # FIXME: searching_text supposed to be inside kivy_sate.py, typo and need to create a PR for kivy_state.py + text=empty_search_label if state.searching_text else no_address_found, + halign='center', size_hint_y=None, valign='top') return content + +class HelperAddressBook(DefaultLabelMixin): + """Widget used in Addressbook are here""" + @staticmethod def address_detail_popup(obj, send_message, update_address, close_popup, width): """This function shows the address's details and opens the popup.""" From 9a3922f36f78535da8f3c2b9d783977af480bff6 Mon Sep 17 00:00:00 2001 From: surbhicis Date: Wed, 6 Apr 2022 12:46:04 +0530 Subject: [PATCH 029/424] fixes to the Dockerfile in the .buildbot/kivy --- .buildbot/kivy/Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.buildbot/kivy/Dockerfile b/.buildbot/kivy/Dockerfile index 4e78165e..56d1c9c4 100644 --- a/.buildbot/kivy/Dockerfile +++ b/.buildbot/kivy/Dockerfile @@ -13,7 +13,8 @@ RUN apt-get install -yq python-setuptools \ python-dev \ python3-virtualenv \ python3-pip \ + wget -RUN sudo wget -O "/usr/local/bin/travis2bash.sh" "https://git.bitmessage.org/Bitmessage/buildbot-scripts/raw/branch/master/travis2bash.sh" +RUN wget -O /usr/local/bin/travis2bash.sh https://git.bitmessage.org/Bitmessage/buildbot-scripts/raw/branch/master/travis2bash.sh -RUN sudo chmod a+rx "/usr/local/bin/travis2bash.sh" +RUN chmod a+rx "/usr/local/bin/travis2bash.sh" From 6535770baebf699d013f39645c08aa9ecba8bd13 Mon Sep 17 00:00:00 2001 From: surbhicis Date: Thu, 7 Apr 2022 15:04:51 +0530 Subject: [PATCH 030/424] fixes to the buildbot kivy test script permission issue --- .buildbot/kivy/test.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 .buildbot/kivy/test.sh diff --git a/.buildbot/kivy/test.sh b/.buildbot/kivy/test.sh old mode 100644 new mode 100755 From 3a316d99ccfcac6360a76aff8d0aa475838a35e8 Mon Sep 17 00:00:00 2001 From: surbhicis Date: Thu, 7 Apr 2022 16:45:03 +0530 Subject: [PATCH 031/424] buildbot android test script permission issue fixed --- .buildbot/android/build.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 .buildbot/android/build.sh diff --git a/.buildbot/android/build.sh b/.buildbot/android/build.sh old mode 100644 new mode 100755 From 09c2f2b6944a5a93aff38f50a8f9fdbccdc19448 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Mon, 11 Apr 2022 10:59:35 +0530 Subject: [PATCH 032/424] Fixed kivy buildbot --- .buildbot/kivy/Dockerfile | 16 ++++++++++------ .buildbot/kivy/build.sh | 3 +++ .buildbot/kivy/test.sh | 2 +- 3 files changed, 14 insertions(+), 7 deletions(-) create mode 100755 .buildbot/kivy/build.sh diff --git a/.buildbot/kivy/Dockerfile b/.buildbot/kivy/Dockerfile index 56d1c9c4..2ce964dd 100644 --- a/.buildbot/kivy/Dockerfile +++ b/.buildbot/kivy/Dockerfile @@ -2,19 +2,23 @@ FROM ubuntu:bionic AS kivy # FROM ubuntu:20.04 AS buildbot-bionic +ENV DEBIAN_FRONTEND=noninteractive + RUN apt-get update RUN apt-get -y install sudo RUN apt-get install -yq python-setuptools \ python-setuptools libssl-dev libpq-dev python-prctl python-dev \ - python-dev python-virtualenv python-pip virtualenv \ - libssl-dev \ - python-dev \ + python-virtualenv python-pip virtualenv \ + libjpeg-dev zlib1g-dev python3-dev \ python3-virtualenv \ python3-pip \ - wget + wget \ + build-essential libcap-dev libmtdev-dev xvfb xclip git python3-opencv + +RUN pip3 install Cython Pillow pyzbar telenium -RUN wget -O /usr/local/bin/travis2bash.sh https://git.bitmessage.org/Bitmessage/buildbot-scripts/raw/branch/master/travis2bash.sh +RUN pip3 install --upgrade setuptools pip -RUN chmod a+rx "/usr/local/bin/travis2bash.sh" +RUN pip3 install -e git+https://github.com/kivymd/KivyMD#egg=kivymd diff --git a/.buildbot/kivy/build.sh b/.buildbot/kivy/build.sh new file mode 100755 index 00000000..f07e5756 --- /dev/null +++ b/.buildbot/kivy/build.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +python setup.py install --user diff --git a/.buildbot/kivy/test.sh b/.buildbot/kivy/test.sh index 04838747..d01ca870 100755 --- a/.buildbot/kivy/test.sh +++ b/.buildbot/kivy/test.sh @@ -1,3 +1,3 @@ #!/bin/bash -travis2bash.sh .travis-kivy.yml +xvfb-run python3 tests-kivy.py From 003d1e51698e22a17bbb9b06b88ea0da03acd618 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Fri, 15 Apr 2022 12:16:07 +0530 Subject: [PATCH 033/424] Update test-kivy --- .buildbot/kivy/Dockerfile | 4 +++- .buildbot/kivy/test.sh | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.buildbot/kivy/Dockerfile b/.buildbot/kivy/Dockerfile index 2ce964dd..62c3e36d 100644 --- a/.buildbot/kivy/Dockerfile +++ b/.buildbot/kivy/Dockerfile @@ -16,7 +16,9 @@ RUN apt-get install -yq python-setuptools \ python3-pip \ wget \ build-essential libcap-dev libmtdev-dev xvfb xclip git python3-opencv - + +RUN ln -sf /usr/bin/python3 /usr/bin/python + RUN pip3 install Cython Pillow pyzbar telenium RUN pip3 install --upgrade setuptools pip diff --git a/.buildbot/kivy/test.sh b/.buildbot/kivy/test.sh index d01ca870..caccbb9d 100755 --- a/.buildbot/kivy/test.sh +++ b/.buildbot/kivy/test.sh @@ -1,3 +1,3 @@ #!/bin/bash -xvfb-run python3 tests-kivy.py +xvfb-run python tests-kivy.py From 3c3f6aa5c7a0b5a63a416a4aad4b8a9123c1ff07 Mon Sep 17 00:00:00 2001 From: "lee.miller" Date: Wed, 23 Mar 2022 17:17:38 +0200 Subject: [PATCH 034/424] Update Dockerfile to bionic and resolve pip upgrade issue --- Dockerfile | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/Dockerfile b/Dockerfile index 6e665ff6..f97a3082 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # A container for PyBitmessage daemon -FROM ubuntu:xenial +FROM ubuntu:bionic RUN apt-get update @@ -9,8 +9,6 @@ RUN apt-get install -yq --no-install-suggests --no-install-recommends \ build-essential libcap-dev libssl-dev \ python-all-dev python-msgpack python-pip python-setuptools -RUN pip2 install --upgrade pip - EXPOSE 8444 8442 ENV HOME /home/bitmessage @@ -19,19 +17,18 @@ ENV BITMESSAGE_HOME ${HOME} WORKDIR ${HOME} ADD . ${HOME} -# Install tests dependencies -RUN pip2 install -r requirements.txt # Install -RUN python2 setup.py install +RUN pip2 install . + +# Cleanup +RUN rm -rf /var/lib/apt/lists/* +RUN rm -rf ${HOME} # Create a user -RUN useradd bitmessage && chown -R bitmessage ${HOME} +RUN useradd -r bitmessage && chown -R bitmessage ${HOME} USER bitmessage -# Clean HOME -RUN rm -rf ${HOME}/* - # Generate default config RUN pybitmessage -t From 8a9899c9df608c86d78d1705ccd749e702f6d9bb Mon Sep 17 00:00:00 2001 From: "lee.miller" Date: Wed, 23 Mar 2022 18:24:29 +0200 Subject: [PATCH 035/424] Setup json API --- Dockerfile | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index f97a3082..89ab8cf5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,7 +18,7 @@ WORKDIR ${HOME} ADD . ${HOME} # Install -RUN pip2 install . +RUN pip2 install jsonrpclib . # Cleanup RUN rm -rf /var/lib/apt/lists/* @@ -35,6 +35,9 @@ RUN pybitmessage -t # Setup environment RUN APIPASS=$(tr -dc a-zA-Z0-9 < /dev/urandom | head -c32 && echo) \ && echo "\napiusername: api\napipassword: $APIPASS" \ - && echo "apienabled = true\napiinterface = 0.0.0.0\napiusername = api\napipassword = $APIPASS" >> keys.dat + && sed -ie "s|\(apiinterface = \).*|\10\.0\.0\.0|g" keys.dat \ + && sed -ie "s|\(apivariant = \).*|\1json|g" keys.dat \ + && sed -ie "s|\(apiusername = \).*|\1api|g" keys.dat \ + && sed -ie "s|\(apipassword = \).*|\1$APIPASS|g" keys.dat CMD ["pybitmessage", "-d"] From 3b060d84b941ab065ea32ddd7cf8e718f6e4f036 Mon Sep 17 00:00:00 2001 From: "lee.miller" Date: Wed, 23 Mar 2022 21:50:40 +0200 Subject: [PATCH 036/424] Make a separate script launcher.sh for the entry point, it allows - passing args to the pybitmessage - setting API username and password through env --- Dockerfile | 12 +++--------- packages/docker/launcher.sh | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 9 deletions(-) create mode 100755 packages/docker/launcher.sh diff --git a/Dockerfile b/Dockerfile index 89ab8cf5..b409d27a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,6 +16,7 @@ ENV BITMESSAGE_HOME ${HOME} WORKDIR ${HOME} ADD . ${HOME} +COPY packages/docker/launcher.sh /usr/bin/ # Install RUN pip2 install jsonrpclib . @@ -32,12 +33,5 @@ USER bitmessage # Generate default config RUN pybitmessage -t -# Setup environment -RUN APIPASS=$(tr -dc a-zA-Z0-9 < /dev/urandom | head -c32 && echo) \ - && echo "\napiusername: api\napipassword: $APIPASS" \ - && sed -ie "s|\(apiinterface = \).*|\10\.0\.0\.0|g" keys.dat \ - && sed -ie "s|\(apivariant = \).*|\1json|g" keys.dat \ - && sed -ie "s|\(apiusername = \).*|\1api|g" keys.dat \ - && sed -ie "s|\(apipassword = \).*|\1$APIPASS|g" keys.dat - -CMD ["pybitmessage", "-d"] +ENTRYPOINT ["launcher.sh"] +CMD ["-d"] diff --git a/packages/docker/launcher.sh b/packages/docker/launcher.sh new file mode 100755 index 00000000..c0e48855 --- /dev/null +++ b/packages/docker/launcher.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +# Setup the environment for docker container +APIUSER=${USER:-api} +APIPASS=${PASSWORD:-$(tr -dc a-zA-Z0-9 < /dev/urandom | head -c32 && echo)} + +echo "\napiusername: $APIUSER\napipassword: $APIPASS" + +sed -i -e "s|\(apiinterface = \).*|\10\.0\.0\.0|g" \ + -e "s|\(apivariant = \).*|\1json|g" \ + -e "s|\(apiusername = \).*|\1$APIUSER|g" \ + -e "s|\(apipassword = \).*|\1$APIPASS|g" \ + -e "s|apinotifypath = .*||g" ${BITMESSAGE_HOME}/keys.dat + +# Run +exec pybitmessage "$@" From a67572d70854fd59a3baffbe286a9d77a90e43d7 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Thu, 17 Mar 2022 15:15:24 +0530 Subject: [PATCH 037/424] Separated kivy variables in kivy_state.py instead of state.py and initialized inside mpybit --- src/bitmessagekivy/kivy_state.py | 36 ++++++++++++++++++++++++++++++++ src/bitmessagekivy/mpybit.py | 3 +++ 2 files changed, 39 insertions(+) create mode 100644 src/bitmessagekivy/kivy_state.py diff --git a/src/bitmessagekivy/kivy_state.py b/src/bitmessagekivy/kivy_state.py new file mode 100644 index 00000000..f5388b77 --- /dev/null +++ b/src/bitmessagekivy/kivy_state.py @@ -0,0 +1,36 @@ +# pylint: disable=too-many-instance-attributes, too-few-public-methods + +""" +Kivy State variables are assigned here, they are separated from state.py +================================= +""" + + +class KivyStateVariables(object): + """This Class hold all the kivy state variables""" + + def __init__(self): + self.association = '' + self.navinstance = None + self.mail_id = 0 + self.myAddressObj = None + self.detailPageType = None + self.ackdata = None + self.status = None + self.screen_density = None + self.msg_counter_objs = None + self.check_sent_acc = None + self.sent_count = 0 + self.inbox_count = 0 + self.trash_count = 0 + self.draft_count = 0 + self.all_count = 0 + self.searcing_text = '' + self.search_screen = '' + self.send_draft_mail = None + self.is_allmail = False + self.in_composer = False + self.availabe_credit = 0 + self.in_sent_method = False + self.in_search_mode = False + self.imageDir = None diff --git a/src/bitmessagekivy/mpybit.py b/src/bitmessagekivy/mpybit.py index b44b1070..14eed581 100644 --- a/src/bitmessagekivy/mpybit.py +++ b/src/bitmessagekivy/mpybit.py @@ -6,9 +6,12 @@ from kivy.app import App from kivy.uix.label import Label +from pybitmessage.bitmessagekivy.kivy_state import KivyStateVariables + class NavigateApp(App): """Navigation Layout of class""" + kivy_state_obj = KivyStateVariables() def build(self): """Method builds the widget""" From 271d7fe6ade922dd7aad1d6ce803478548d75380 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Fri, 29 Apr 2022 17:22:51 +0530 Subject: [PATCH 038/424] Add init inside kivy/baseclass --- src/bitmessagekivy/baseclass/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/bitmessagekivy/baseclass/__init__.py diff --git a/src/bitmessagekivy/baseclass/__init__.py b/src/bitmessagekivy/baseclass/__init__.py new file mode 100644 index 00000000..e69de29b From 0937e957bda97daa7e79cdff2753c5592b78db8d Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Fri, 22 Apr 2022 18:00:08 +0300 Subject: [PATCH 039/424] Add tox buildbot testing scenario --- .buildbot/tox/Dockerfile | 18 ++++++++++++++++++ .buildbot/tox/test.sh | 3 +++ 2 files changed, 21 insertions(+) create mode 100644 .buildbot/tox/Dockerfile create mode 100755 .buildbot/tox/test.sh diff --git a/.buildbot/tox/Dockerfile b/.buildbot/tox/Dockerfile new file mode 100644 index 00000000..931bc0f7 --- /dev/null +++ b/.buildbot/tox/Dockerfile @@ -0,0 +1,18 @@ +FROM ubuntu:bionic AS tox + +RUN apt-get update + +# Common apt packages +RUN apt-get install -yq --no-install-suggests --no-install-recommends \ + software-properties-common build-essential libcap-dev libssl-dev \ + python-all-dev python-setuptools wget xvfb language-pack-en \ + libffi-dev python3-dev python3-pip python3.8 python3.8-dev python3.8-venv \ + python-msgpack python-pip python-qt4 python-six qtbase5-dev qt5-default \ + tor + +RUN python3.8 -m pip install setuptools wheel +RUN python3.8 -m pip install --upgrade pip tox virtualenv + +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en +ENV LC_ALL en_US.UTF-8 diff --git a/.buildbot/tox/test.sh b/.buildbot/tox/test.sh new file mode 100755 index 00000000..f57f61e5 --- /dev/null +++ b/.buildbot/tox/test.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +tox From b92a78cb4671518b536f1e34ccd940bd22d522bc Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Fri, 22 Apr 2022 18:32:10 +0300 Subject: [PATCH 040/424] Add packages/apparmor into manifest --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 3cbc72cd..15a6bf81 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,3 +2,4 @@ include COPYING include README.md include requirements.txt recursive-include desktop * +recursive-include packages/apparmor * From ace9bd7b49b25243e1ecd0f58983775d80de80d7 Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Tue, 5 Apr 2022 01:34:35 +0300 Subject: [PATCH 041/424] Add bandit and flake8 lint run as separate testenv in tox.ini; uncomment fail fast in test.sh and possibly remove --exit-zero bandit arg when ready. --- .buildbot/tox/test.sh | 1 + setup.cfg | 1 + tox.ini | 10 ++++++++++ 3 files changed, 12 insertions(+) diff --git a/.buildbot/tox/test.sh b/.buildbot/tox/test.sh index f57f61e5..8b994fd4 100755 --- a/.buildbot/tox/test.sh +++ b/.buildbot/tox/test.sh @@ -1,3 +1,4 @@ #!/bin/sh +tox -e lint-basic # || exit 1 tox diff --git a/setup.cfg b/setup.cfg index a4e0547c..28ceaede 100644 --- a/setup.cfg +++ b/setup.cfg @@ -8,6 +8,7 @@ max-line-length = 119 [flake8] max-line-length = 119 +exclude = bitmessagecli.py,bitmessagecurses,bitmessageqt,plugins,tests,umsgpack ignore = E722,F841,W503 # E722: pylint is preferred for bare-except # F841: pylint is preferred for unused-variable diff --git a/tox.ini b/tox.ini index 632c7381..bbea28df 100644 --- a/tox.ini +++ b/tox.ini @@ -12,6 +12,16 @@ commands = coverage run -a src/bitmessagemain.py -t coverage run -a -m tests +[testenv:lint-basic] +deps = + bandit + flake8 +commands = + bandit -r --exit-zero -s B105,B301,B411,B413,B608 \ + -x checkdeps.*,bitmessagecurses,bitmessageqt,tests pybitmessage + flake8 pybitmessage --count --select=E9,F63,F7,F82 \ + --show-source --statistics + [testenv:py27-doc] deps = .[docs] From 760ff5f060cd3395c2d0c3661fe3e8c58e5818a4 Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Thu, 7 Apr 2022 11:35:38 +0300 Subject: [PATCH 042/424] Enable qt tests in py27 env --- tox.ini | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tox.ini b/tox.ini index bbea28df..5a1d44e1 100644 --- a/tox.ini +++ b/tox.ini @@ -5,6 +5,7 @@ skip_missing_interpreters = true [testenv] setenv = BITMESSAGE_HOME = {envtmpdir} + HOME = {envtmpdir} PYTHONWARNINGS = default deps = -rrequirements.txt commands = @@ -22,6 +23,9 @@ commands = flake8 pybitmessage --count --select=E9,F63,F7,F82 \ --show-source --statistics +[testenv:py27] +sitepackages = true + [testenv:py27-doc] deps = .[docs] From b8c2795b82376b49252383b96fbe8f46431deec1 Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Fri, 29 Apr 2022 17:54:24 +0300 Subject: [PATCH 043/424] Fix coverage omit config value to not include /var/lib/buildbot/... and remove also __init__. --- tox.ini | 2 -- 1 file changed, 2 deletions(-) diff --git a/tox.ini b/tox.ini index 5a1d44e1..f76ee054 100644 --- a/tox.ini +++ b/tox.ini @@ -49,11 +49,9 @@ commands = [coverage:run] source = src omit = - */lib* tests.py */tests/* src/version.py - */__init__.py src/fallback/umsgpack/* [coverage:report] From 93c283a467f7488eb5a13e309a2d47538d3263b1 Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Fri, 29 Apr 2022 18:05:48 +0300 Subject: [PATCH 044/424] Place obvious bandit nosec comments --- src/api.py | 8 ++++---- src/class_objectProcessor.py | 11 ++++++----- src/depends.py | 2 +- src/network/connectionchooser.py | 11 ++++++----- src/network/dandelion.py | 2 +- src/network/knownnodes.py | 2 +- src/network/tcp.py | 2 +- src/plugins/proxyconfig_stem.py | 4 ++-- src/protocol.py | 2 +- src/upnp.py | 2 +- 10 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/api.py b/src/api.py index 736ba024..3300fc72 100644 --- a/src/api.py +++ b/src/api.py @@ -62,9 +62,9 @@ import errno import hashlib import httplib import json -import random # nosec +import random import socket -import subprocess +import subprocess # nosec B404 import time import xmlrpclib from binascii import hexlify, unhexlify @@ -240,7 +240,7 @@ class singleAPI(StoppableThread): if attempt > 0: logger.warning( 'Failed to start API listener on port %s', port) - port = random.randint(32767, 65535) + port = random.randint(32767, 65535) # nosec B311 se = StoppableRPCServer( (config.get( 'bitmessagesettings', 'apiinterface'), @@ -266,7 +266,7 @@ class singleAPI(StoppableThread): if apiNotifyPath: logger.info('Trying to call %s', apiNotifyPath) try: - subprocess.call([apiNotifyPath, "startingUp"]) + subprocess.call([apiNotifyPath, "startingUp"]) # nosec B603 except OSError: logger.warning( 'Failed to call %s, removing apinotifypath setting', diff --git a/src/class_objectProcessor.py b/src/class_objectProcessor.py index bbf622e4..b83a0b50 100644 --- a/src/class_objectProcessor.py +++ b/src/class_objectProcessor.py @@ -7,7 +7,7 @@ processes the network objects import hashlib import logging import random -import subprocess # nosec +import subprocess # nosec B404 import threading import time from binascii import hexlify @@ -458,7 +458,7 @@ class objectProcessor(threading.Thread): for key, cryptorObject in sorted( shared.myECCryptorObjects.items(), - key=lambda x: random.random()): + key=lambda x: random.random()): # nosec B311 try: # continue decryption attempts to avoid timing attacks if initialDecryptionSuccessful: @@ -680,7 +680,8 @@ class objectProcessor(threading.Thread): apiNotifyPath = config.safeGet( 'bitmessagesettings', 'apinotifypath') if apiNotifyPath: - subprocess.call([apiNotifyPath, "newMessage"]) + subprocess.call( # nosec B603 + [apiNotifyPath, "newMessage"]) # Let us now check and see whether our receiving address is # behaving as a mailing list @@ -776,7 +777,7 @@ class objectProcessor(threading.Thread): initialDecryptionSuccessful = False for key, cryptorObject in sorted( shared.MyECSubscriptionCryptorObjects.items(), - key=lambda x: random.random()): + key=lambda x: random.random()): # nosec B311 try: # continue decryption attempts to avoid timing attacks if initialDecryptionSuccessful: @@ -964,7 +965,7 @@ class objectProcessor(threading.Thread): apiNotifyPath = config.safeGet( 'bitmessagesettings', 'apinotifypath') if apiNotifyPath: - subprocess.call([apiNotifyPath, "newBroadcast"]) + subprocess.call([apiNotifyPath, "newBroadcast"]) # nosec B603 # Display timing data logger.info( diff --git a/src/depends.py b/src/depends.py index 212c3143..ed3c7ce3 100755 --- a/src/depends.py +++ b/src/depends.py @@ -17,7 +17,7 @@ if not hasattr(sys, 'hexversion') or sys.hexversion < 0x20300F0: ) import logging # noqa:E402 -import subprocess +import subprocess # nosec B404 from importlib import import_module diff --git a/src/network/connectionchooser.py b/src/network/connectionchooser.py index db6f0ff8..d7062d24 100644 --- a/src/network/connectionchooser.py +++ b/src/network/connectionchooser.py @@ -3,7 +3,7 @@ Select which node to connect to """ # pylint: disable=too-many-branches import logging -import random # nosec +import random import knownnodes import protocol @@ -17,7 +17,7 @@ logger = logging.getLogger('default') def getDiscoveredPeer(): """Get a peer from the local peer discovery list""" try: - peer = random.choice(state.discoveredPeers.keys()) + peer = random.choice(state.discoveredPeers.keys()) # nosec B311 except (IndexError, KeyError): raise ValueError try: @@ -40,11 +40,12 @@ def chooseConnection(stream): except queue.Empty: pass # with a probability of 0.5, connect to a discovered peer - if random.choice((False, True)) and not haveOnion: + if random.choice((False, True)) and not haveOnion: # nosec B311 # discovered peers are already filtered by allowed streams return getDiscoveredPeer() for _ in range(50): - peer = random.choice(knownnodes.knownNodes[stream].keys()) + peer = random.choice( # nosec B311 + knownnodes.knownNodes[stream].keys()) try: peer_info = knownnodes.knownNodes[stream][peer] if peer_info.get('self'): @@ -70,7 +71,7 @@ def chooseConnection(stream): if rating > 1: rating = 1 try: - if 0.05 / (1.0 - rating) > random.random(): + if 0.05 / (1.0 - rating) > random.random(): # nosec B311 return peer except ZeroDivisionError: return peer diff --git a/src/network/dandelion.py b/src/network/dandelion.py index 03f45bd7..4f3cd07b 100644 --- a/src/network/dandelion.py +++ b/src/network/dandelion.py @@ -140,7 +140,7 @@ class Dandelion: # pylint: disable=old-style-class """ try: # pick a random from available stems - stem = choice(range(len(self.stem))) + stem = choice(range(len(self.stem))) # nosec B311 if self.stem[stem] == parent: # one stem available and it's the parent if len(self.stem) == 1: diff --git a/src/network/knownnodes.py b/src/network/knownnodes.py index 79912a67..d3b6dd01 100644 --- a/src/network/knownnodes.py +++ b/src/network/knownnodes.py @@ -7,7 +7,7 @@ Manipulations with knownNodes dictionary. import json import logging import os -import pickle +import pickle # nosec B403 import threading import time try: diff --git a/src/network/tcp.py b/src/network/tcp.py index 14fc72f0..c02d1ef5 100644 --- a/src/network/tcp.py +++ b/src/network/tcp.py @@ -398,7 +398,7 @@ class TCPServer(AdvancedDispatcher): try: if attempt > 0: logger.warning('Failed to bind on port %s', port) - port = random.randint(32767, 65535) + port = random.randint(32767, 65535) # nosec B311 self.bind((host, port)) except socket.error as e: if e.errno in (asyncore.EADDRINUSE, asyncore.WSAEADDRINUSE): diff --git a/src/plugins/proxyconfig_stem.py b/src/plugins/proxyconfig_stem.py index d18a2e5f..25f75f69 100644 --- a/src/plugins/proxyconfig_stem.py +++ b/src/plugins/proxyconfig_stem.py @@ -13,7 +13,7 @@ Configure tor proxy and hidden service with """ import logging import os -import random # noseq +import random import sys import tempfile @@ -79,7 +79,7 @@ def connect_plugin(config): port = config.safeGetInt('bitmessagesettings', 'socksport', 9050) for attempt in range(50): if attempt > 0: - port = random.randint(32767, 65535) + port = random.randint(32767, 65535) # nosec B311 tor_config['SocksPort'] = str(port) if tor_config.get('DataDirectory'): control_port = port + 1 diff --git a/src/protocol.py b/src/protocol.py index 6ee35d53..291d4ebd 100644 --- a/src/protocol.py +++ b/src/protocol.py @@ -56,7 +56,7 @@ OBJECT_I2P = 0x493250 OBJECT_ADDR = 0x61646472 eightBytesOfRandomDataUsedToDetectConnectionsToSelf = pack( - '>Q', random.randrange(1, 18446744073709551615)) + '>Q', random.randrange(1, 18446744073709551615)) # nosec B311 # Compiled struct for packing/unpacking headers # New code should use CreatePacket instead of Header.pack diff --git a/src/upnp.py b/src/upnp.py index 2fa71f9c..dc1334e2 100644 --- a/src/upnp.py +++ b/src/upnp.py @@ -328,7 +328,7 @@ class uPnPThread(StoppableThread): elif i == 1 and self.extPort: extPort = self.extPort # try external port from last time next else: - extPort = randint(32767, 65535) + extPort = randint(32767, 65535) # nosec B311 logger.debug( "Attempt %i, requesting UPnP mapping for %s:%i on external port %i", i, From a6823275bdd154b1d84a457496a725a3e649d273 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Thu, 5 May 2022 17:24:30 +0530 Subject: [PATCH 045/424] Fixed typos and variables name --- src/bitmessagekivy/kivy_state.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/bitmessagekivy/kivy_state.py b/src/bitmessagekivy/kivy_state.py index f5388b77..6ce16610 100644 --- a/src/bitmessagekivy/kivy_state.py +++ b/src/bitmessagekivy/kivy_state.py @@ -13,8 +13,8 @@ class KivyStateVariables(object): self.association = '' self.navinstance = None self.mail_id = 0 - self.myAddressObj = None - self.detailPageType = None + self.my_address_obj = None + self.detail_page_type = None self.ackdata = None self.status = None self.screen_density = None @@ -25,12 +25,12 @@ class KivyStateVariables(object): self.trash_count = 0 self.draft_count = 0 self.all_count = 0 - self.searcing_text = '' + self.searching_text = '' self.search_screen = '' self.send_draft_mail = None self.is_allmail = False self.in_composer = False - self.availabe_credit = 0 + self.available_credit = 0 self.in_sent_method = False self.in_search_mode = False - self.imageDir = None + self.image_dir = None From 96d80a008a576f8bf61feddbe8beea36fa28b155 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Thu, 5 May 2022 16:05:14 +0530 Subject: [PATCH 046/424] Initialize kivy_state obj inside init in mpybit --- src/bitmessagekivy/mpybit.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/bitmessagekivy/mpybit.py b/src/bitmessagekivy/mpybit.py index 14eed581..d4caa883 100644 --- a/src/bitmessagekivy/mpybit.py +++ b/src/bitmessagekivy/mpybit.py @@ -11,7 +11,10 @@ from pybitmessage.bitmessagekivy.kivy_state import KivyStateVariables class NavigateApp(App): """Navigation Layout of class""" - kivy_state_obj = KivyStateVariables() + + def __init__(self): + super(NavigateApp, self).__init__() + self.kivy_state_obj = KivyStateVariables() def build(self): """Method builds the widget""" From d195784554ccb657983847b7b8e604cc38d17a0f Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Tue, 10 May 2022 13:27:10 +0530 Subject: [PATCH 047/424] Add delete query --- src/helper_sent.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/helper_sent.py b/src/helper_sent.py index 1fc98de2..2996939e 100644 --- a/src/helper_sent.py +++ b/src/helper_sent.py @@ -46,3 +46,8 @@ def insert(msgid=None, toAddress='[Broadcast subscribers]', fromAddress=None, su return ackdata else: return None + + +def delete(ack_data): + """Perform Delete query""" + sqlExecute("DELETE FROM sent WHERE ackdata = ?", ack_data) From 69961335bf25bf26e5a0870602fe3ed6a8f3efa0 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Fri, 22 Apr 2022 12:26:55 +0530 Subject: [PATCH 048/424] Created a common method for empty screen label --- src/bitmessagekivy/baseclass/addressbook.py | 6 +++-- .../baseclass/addressbook_widgets.py | 22 +------------------ 2 files changed, 5 insertions(+), 23 deletions(-) diff --git a/src/bitmessagekivy/baseclass/addressbook.py b/src/bitmessagekivy/baseclass/addressbook.py index 8e6cd860..931c7ac2 100644 --- a/src/bitmessagekivy/baseclass/addressbook.py +++ b/src/bitmessagekivy/baseclass/addressbook.py @@ -26,7 +26,7 @@ import state from bitmessagekivy.get_platform import platform from bitmessagekivy import kivy_helper_search from bitmessagekivy.baseclass.common import ( - avatarImageFirstLetter, toast, + avatarImageFirstLetter, toast, empty_screen_label, ThemeClsColor, SwipeToDeleteItem ) from bitmessagekivy.baseclass.popup import AddbookDetailPopup @@ -42,6 +42,8 @@ class AddressBook(Screen, HelperAddressBook): has_refreshed = True address_label = StringProperty() address = StringProperty() + label_str = "No contact Address found yet......" + no_search_res_found = "No search result found" def __init__(self, *args, **kwargs): """Getting AddressBook Details""" @@ -71,7 +73,7 @@ class AddressBook(Screen, HelperAddressBook): self.set_mdList(0, 20) self.ids.scroll_y.bind(scroll_y=self.check_scroll_y) else: - self.ids.ml.add_widget(self.default_label_when_empty()) + self.ids.ml.add_widget(empty_screen_label(self.label_str, self.no_search_res_found)) def set_mdList(self, start_index, end_index): """Creating the mdList""" diff --git a/src/bitmessagekivy/baseclass/addressbook_widgets.py b/src/bitmessagekivy/baseclass/addressbook_widgets.py index e8cd982d..a1f5c5f4 100644 --- a/src/bitmessagekivy/baseclass/addressbook_widgets.py +++ b/src/bitmessagekivy/baseclass/addressbook_widgets.py @@ -6,31 +6,11 @@ Addressbook widgets are here. from kivymd.uix.button import MDRaisedButton from kivymd.uix.dialog import MDDialog -from kivymd.uix.label import MDLabel import state -no_address_found = "No contact found yet......" -empty_search_label = "No contact found!" - -# pylint: disable=no-init, old-style-class -class DefaultLabelMixin: - """Common label on blank screen""" - - @staticmethod - def default_label_when_empty(): - """This function returns default message while no address is there.""" - content = MDLabel( - font_style='Caption', - theme_text_color='Primary', - # FIXME: searching_text supposed to be inside kivy_sate.py, typo and need to create a PR for kivy_state.py - text=empty_search_label if state.searching_text else no_address_found, - halign='center', size_hint_y=None, valign='top') - return content - - -class HelperAddressBook(DefaultLabelMixin): +class HelperAddressBook(object): """Widget used in Addressbook are here""" @staticmethod From a9f50e2af445dc22d2cc519c1881ff17798151e7 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Thu, 5 May 2022 17:22:15 +0530 Subject: [PATCH 049/424] Use kivy_state variable instead of state --- src/bitmessagekivy/baseclass/addressbook.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/bitmessagekivy/baseclass/addressbook.py b/src/bitmessagekivy/baseclass/addressbook.py index 931c7ac2..dd20d181 100644 --- a/src/bitmessagekivy/baseclass/addressbook.py +++ b/src/bitmessagekivy/baseclass/addressbook.py @@ -18,11 +18,10 @@ from kivy.properties import ( StringProperty ) from kivy.uix.screenmanager import Screen - +from kivy.app import App import state - from bitmessagekivy.get_platform import platform from bitmessagekivy import kivy_helper_search from bitmessagekivy.baseclass.common import ( @@ -49,6 +48,8 @@ class AddressBook(Screen, HelperAddressBook): """Getting AddressBook Details""" super(AddressBook, self).__init__(*args, **kwargs) self.addbook_popup = None + self.kivy_running_app = App.get_running_app() + self.kivy_state = self.kivy_running_app.kivy_state_obj Clock.schedule_once(self.init_ui, 0) def init_ui(self, dt=0): @@ -58,10 +59,10 @@ class AddressBook(Screen, HelperAddressBook): def loadAddresslist(self, account, where="", what=""): """Clock Schdule for method AddressBook""" - if state.searching_text: + if self.kivy_state.searching_text: self.ids.scroll_y.scroll_y = 1.0 where = ['label', 'address'] - what = state.searching_text + what = self.kivy_state.searching_text xAddress = '' self.ids.tag_label.text = '' self.queryreturn = kivy_helper_search.search_sql( From 991e470a06f5f477f0dcb36912b131fda7d2c3d0 Mon Sep 17 00:00:00 2001 From: surbhicis Date: Thu, 12 May 2022 13:22:11 +0530 Subject: [PATCH 050/424] update the Dockerfile in the .buildbot/kivy --- .buildbot/android/Dockerfile | 165 ++++++++++++++++++++++------------- 1 file changed, 102 insertions(+), 63 deletions(-) diff --git a/.buildbot/android/Dockerfile b/.buildbot/android/Dockerfile index f8b8f673..329f3cb4 100644 --- a/.buildbot/android/Dockerfile +++ b/.buildbot/android/Dockerfile @@ -1,78 +1,117 @@ + # A container for buildbot FROM ubuntu:bionic AS android # FROM ubuntu:20.04 AS buildbot-bionic -RUN apt -y update -qq -RUN apt -y install wget +ENV ANDROID_HOME="/opt/android" -RUN wget -nc "https://dl.google.com/android/repository/sdk-tools-linux-4333796.zip" -RUN wget -nc "https://dl.google.com/android/repository/android-ndk-r23b-linux.zip" -RUN wget -nc "http://archive.apache.org/dist/ant/binaries/apache-ant-1.10.12-bin.tar.gz" +RUN apt -y update -qq \ + && apt -y install -qq --no-install-recommends curl unzip ca-certificates \ + && apt -y autoremove -# SYSTEM DEPENDENCIES -RUN apt -y install --no-install-recommends python3-pip \ - pip3 python3 virtualenv python3-setuptools \ - python3-wheel git unzip sudo patch bzip2 lzma -RUN apt -y autoremove +ENV ANDROID_NDK_HOME="${ANDROID_HOME}/android-ndk" +ENV ANDROID_NDK_VERSION="22b" +ENV ANDROID_NDK_HOME_V="${ANDROID_NDK_HOME}-r${ANDROID_NDK_VERSION}" -# BUILD DEPENDENCIES +# get the latest version from https://developer.android.com/ndk/downloads/index.html +ENV ANDROID_NDK_ARCHIVE="android-ndk-r${ANDROID_NDK_VERSION}-linux-x86_64.zip" +ENV ANDROID_NDK_DL_URL="https://dl.google.com/android/repository/${ANDROID_NDK_ARCHIVE}" -RUN dpkg --add-architecture i386 -RUN apt -y update -qq -RUN apt -y install -qq --no-install-recommends build-essential \ - ccache git python3 python3-dev libncurses5:i386 libstdc++6:i386 \ - libgtk2.0-0:i386 libpangox-1.0-0:i386 libpangoxft-1.0-0:i386 \ - libidn11:i386 zip zlib1g-dev zlib1g:i386 -RUN apt -y autoremove -RUN apt -y clean +# download and install Android NDK +RUN curl "${ANDROID_NDK_DL_URL}" \ + --output "${ANDROID_NDK_ARCHIVE}" \ + && mkdir --parents "${ANDROID_NDK_HOME_V}" \ + && unzip -q "${ANDROID_NDK_ARCHIVE}" -d "${ANDROID_HOME}" \ + && ln -sfn "${ANDROID_NDK_HOME_V}" "${ANDROID_NDK_HOME}" \ + && rm -rf "${ANDROID_NDK_ARCHIVE}" -# RECIPES DEPENDENCIES +ENV ANDROID_SDK_HOME="${ANDROID_HOME}/android-sdk" -RUN dpkg --add-architecture i386 -RUN apt -y update -qq -RUN apt -y install -qq --no-install-recommends libffi-dev autoconf \ - automake cmake gettext libltdl-dev libtool pkg-config -RUN apt -y autoremove -RUN apt -y clean +# get the latest version from https://developer.android.com/studio/index.html +ENV ANDROID_SDK_TOOLS_VERSION="8092744" +ENV ANDROID_SDK_BUILD_TOOLS_VERSION="30.0.3" +ENV ANDROID_SDK_TOOLS_ARCHIVE="commandlinetools-linux-${ANDROID_SDK_TOOLS_VERSION}_latest.zip" +ENV ANDROID_SDK_TOOLS_DL_URL="https://dl.google.com/android/repository/${ANDROID_SDK_TOOLS_ARCHIVE}" +ENV ANDROID_SDK_MANAGER="${ANDROID_SDK_HOME}/tools/bin/sdkmanager --sdk_root=${ANDROID_SDK_HOME}" + +# download and install Android SDK +RUN curl "${ANDROID_SDK_TOOLS_DL_URL}" \ + --output "${ANDROID_SDK_TOOLS_ARCHIVE}" \ + && mkdir --parents "${ANDROID_SDK_HOME}" \ + && unzip -q "${ANDROID_SDK_TOOLS_ARCHIVE}" -d "${ANDROID_SDK_HOME}" \ + && mv "${ANDROID_SDK_HOME}/cmdline-tools" "${ANDROID_SDK_HOME}/tools" \ + && rm -rf "${ANDROID_SDK_TOOLS_ARCHIVE}" + +# update Android SDK, install Android API, Build Tools... +RUN mkdir --parents "${ANDROID_SDK_HOME}/.android/" \ + && echo '### User Sources for Android SDK Manager' \ + > "${ANDROID_SDK_HOME}/.android/repositories.cfg" + +# accept Android licenses (JDK necessary!) +RUN apt -y update -qq \ + && apt -y install -qq --no-install-recommends \ + openjdk-11-jdk-headless \ + && apt -y autoremove +RUN yes | ${ANDROID_SDK_MANAGER} --licenses > /dev/null + +# download platforms, API, build tools +RUN ${ANDROID_SDK_MANAGER} "platforms;android-30" > /dev/null && \ + ${ANDROID_SDK_MANAGER} "build-tools;${ANDROID_SDK_BUILD_TOOLS_VERSION}" > /dev/null && \ + ${ANDROID_SDK_MANAGER} "extras;android;m2repository" > /dev/null && \ + chmod +x "${ANDROID_SDK_HOME}/tools/bin/avdmanager" + +# download ANT +ENV APACHE_ANT_VERSION="1.9.4" +ENV APACHE_ANT_ARCHIVE="apache-ant-${APACHE_ANT_VERSION}-bin.tar.gz" +ENV APACHE_ANT_DL_URL="https://archive.apache.org/dist/ant/binaries/${APACHE_ANT_ARCHIVE}" +ENV APACHE_ANT_HOME="${ANDROID_HOME}/apache-ant" +ENV APACHE_ANT_HOME_V="${APACHE_ANT_HOME}-${APACHE_ANT_VERSION}" + +RUN curl "${APACHE_ANT_DL_URL}" \ + --output "${APACHE_ANT_ARCHIVE}" \ + && tar -xf "${APACHE_ANT_ARCHIVE}" -C "${ANDROID_HOME}" \ + && ln -sfn "${APACHE_ANT_HOME_V}" "${APACHE_ANT_HOME}" \ + && rm -rf "${APACHE_ANT_ARCHIVE}" + +# install system/build dependencies +RUN apt -y update -qq \ + && apt -y install -qq --no-install-recommends \ + python3 \ + python3-dev \ + python3-pip \ + python3-setuptools \ + python3-venv \ + wget \ + lbzip2 \ + bzip2 \ + lzma \ + patch \ + sudo \ + software-properties-common \ + git \ + zip \ + unzip \ + build-essential \ + ccache \ + autoconf \ + libtool \ + pkg-config \ + zlib1g-dev \ + libncurses5-dev \ + libncursesw5-dev \ + libtinfo5 \ + cmake \ + libffi-dev \ + libssl-dev \ + automake \ + gettext \ + libltdl-dev \ + libidn11 \ + && apt -y autoremove \ + && apt -y clean # INSTALL ANDROID PACKAGES RUN pip3 install buildozer==1.2.0 -RUN pip3 install --upgrade cython==0.29.15 - -# INSTALL NDK - -# get the latest version from https://developer.android.com/ndk/downloads/index.html -RUN mkdir --parents "/opt/android/android-ndk-r23b" -RUN unzip -q "android-ndk-r23b-linux.zip" -d "/opt/android" -RUN ln -sfn "/opt/android/android-ndk-r23b" "/opt/android/android-ndk" -RUN rm -rf "android-ndk-r23b-linux.zip" - -# INSTALL APACHE-ANT - -RUN tar -xf "apache-ant-1.10.12-bin.tar.gz" -C "/opt/android" -RUN ln -sfn "/opt/android/apache-ant-1.10.12" "/opt/android/apache-ant" -RUN rm -rf "apache-ant-1.10.12-bin.tar.gz" - -# INSTALL SDK - -# get the latest version from https://developer.android.com/studio/index.html -RUN mkdir --parents "/opt/android/android-sdk" -RUN unzip -q "sdk-tools-linux-4333796.zip" -d "/opt/android/android-sdk" -RUN rm -rf "sdk-tools-linux-4333796.zip" - # update Android SDK, install Android API, Build Tools... -RUN mkdir --parents "/opt/android/android-sdk/.android/" -# accept Android licenses (JDK necessary!) -RUN apt -y update -qq -RUN apt -y install -qq --no-install-recommends openjdk-11-jdk -RUN apt -y autoremove -RUN yes | "/opt/android/android-sdk/tools/bin/sdkmanager" "build-tools;29.0.2" > /dev/null -# download platforms, API, build tools -RUN "/opt/android/android-sdk/tools/bin/sdkmanager" "platforms;android-24" > /dev/null -RUN "/opt/android/android-sdk/tools/bin/sdkmanager" "platforms;android-28" > /dev/null -RUN "/opt/android/android-sdk/tools/bin/sdkmanager" "build-tools;29.0.2" > /dev/null -RUN "/opt/android/android-sdk/tools/bin/sdkmanager" "extras;android;m2repository" > /dev/null -RUN find /opt/android/android-sdk -type f -perm /0111 -print0|xargs -0 chmod a+x -RUN chown -R buildbot.buildbot /opt/android/android-sdk -RUN chmod +x "/opt/android/android-sdk/tools/bin/avdmanager" +RUN pip3 install --upgrade cython==0.29.15 \ No newline at end of file From ae5a264df0ec30184945ac79c88ec4e0bb19f050 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Thu, 5 May 2022 13:21:11 +0530 Subject: [PATCH 051/424] Update BMConfigParser and add test to enable/disable identity --- src/bmconfigparser.py | 15 +++++++++ src/tests/test_config_address.py | 57 ++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 src/tests/test_config_address.py diff --git a/src/bmconfigparser.py b/src/bmconfigparser.py index 0a2ec558..dc26783d 100644 --- a/src/bmconfigparser.py +++ b/src/bmconfigparser.py @@ -154,6 +154,21 @@ class BMConfigParser(SafeConfigParser): return False return True + def search_addresses(self, address, searched_text): + """Return the searched label of MyAddress""" + return [x for x in [self.get(address, 'label').lower(), + address.lower()] if searched_text in x] + + def disable_address(self, address): + """"Disabling the specific Address""" + self.set(str(address), 'enabled', 'false') + self.save() + + def enable_address(self, address): + """"Enabling the specific Address""" + self.set(address, 'enabled', 'true') + self.save() + if not getattr(BMConfigParser, 'read_file', False): BMConfigParser.read_file = BMConfigParser.readfp diff --git a/src/tests/test_config_address.py b/src/tests/test_config_address.py new file mode 100644 index 00000000..b76df7ec --- /dev/null +++ b/src/tests/test_config_address.py @@ -0,0 +1,57 @@ +""" +Various tests to Enable and Disable the identity +""" + +import unittest +from six import StringIO +from six.moves import configparser +from pybitmessage.bmconfigparser import BMConfigParser + + +address_obj = """[BM-enabled_identity] +label = Test_address_1 +enabled = true + +[BM-disabled_identity] +label = Test_address_2 +enabled = false +""" + + +# pylint: disable=protected-access +class TestAddressEnableDisable(unittest.TestCase): + """A test case for bmconfigparser""" + + def setUp(self): + self.config = BMConfigParser() + self.config.read_file(StringIO(address_obj)) + + def test_enable_enabled_identity(self): + """Test enabling already enabled identity""" + self.config.enable_address('BM-enabled_identity') + self.assertEqual(self.config.safeGet('BM-enabled_identity', 'enabled'), 'true') + + def test_enable_disabled_identity(self): + """Test enabling the Disabled identity""" + self.config.enable_address('BM-disabled_identity') + self.assertEqual(self.config.safeGet('BM-disabled_identity', 'enabled'), 'true') + + def test_enable_non_existent_identity(self): + """Test enable non-existent address""" + with self.assertRaises(configparser.NoSectionError): + self.config.enable_address('non_existent_address') + + def test_disable_disabled_identity(self): + """Test disabling already disabled identity""" + self.config.disable_address('BM-disabled_identity') + self.assertEqual(self.config.safeGet('BM-disabled_identity', 'enabled'), 'false') + + def test_disable_enabled_identity(self): + """Test Disabling the Enabled identity""" + self.config.disable_address('BM-enabled_identity') + self.assertEqual(self.config.safeGet('BM-enabled_identity', 'enabled'), 'false') + + def test_disable_non_existent_identity(self): + """Test dsiable non-existent address""" + with self.assertRaises(configparser.NoSectionError): + self.config.disable_address('non_existent_address') From e1c4f368d6e26f707e4e278ce1571fbc50026518 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Wed, 25 May 2022 19:42:12 +0530 Subject: [PATCH 052/424] Fixed Update Query --- src/bitmessagekivy/baseclass/addressbook.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/bitmessagekivy/baseclass/addressbook.py b/src/bitmessagekivy/baseclass/addressbook.py index dd20d181..d0153348 100644 --- a/src/bitmessagekivy/baseclass/addressbook.py +++ b/src/bitmessagekivy/baseclass/addressbook.py @@ -156,9 +156,10 @@ class AddressBook(Screen, HelperAddressBook): if label in stored_labels and self.address == add_dict[label]: stored_labels.remove(label) if label and label not in stored_labels: - sqlExecute( - "UPDATE addressbook SET label = ? WHERE" - " address = ?", label, self.addbook_popup.content_cls.address) + sqlExecute(""" + UPDATE addressbook + SET label = ? + WHERE address = ?""", 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() From d4300c3f6f9204ec8242fb77afae071fcc6ae381 Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Sat, 7 May 2022 15:44:17 +0300 Subject: [PATCH 053/424] Add Dockerfile for focal and unify with bionic --- .buildbot/tox-bionic/Dockerfile | 18 ++++++++++++++++++ .buildbot/{tox => tox-bionic}/test.sh | 0 .buildbot/tox-focal/Dockerfile | 13 +++++++++++++ .buildbot/tox-focal/test.sh | 1 + .buildbot/tox/Dockerfile | 18 ------------------ 5 files changed, 32 insertions(+), 18 deletions(-) create mode 100644 .buildbot/tox-bionic/Dockerfile rename .buildbot/{tox => tox-bionic}/test.sh (100%) create mode 100644 .buildbot/tox-focal/Dockerfile create mode 120000 .buildbot/tox-focal/test.sh delete mode 100644 .buildbot/tox/Dockerfile diff --git a/.buildbot/tox-bionic/Dockerfile b/.buildbot/tox-bionic/Dockerfile new file mode 100644 index 00000000..5280289b --- /dev/null +++ b/.buildbot/tox-bionic/Dockerfile @@ -0,0 +1,18 @@ +FROM ubuntu:bionic + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update + +RUN apt-get install -yq --no-install-suggests --no-install-recommends \ + software-properties-common build-essential libcap-dev libffi-dev \ + libssl-dev python-all-dev python-setuptools \ + python3-dev python3-pip python3.8 python3.8-dev python3.8-venv \ + python-msgpack python-qt4 language-pack-en qt5dxcb-plugin tor xvfb + +RUN python3.8 -m pip install setuptools wheel +RUN python3.8 -m pip install --upgrade pip tox virtualenv + +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en +ENV LC_ALL en_US.UTF-8 diff --git a/.buildbot/tox/test.sh b/.buildbot/tox-bionic/test.sh similarity index 100% rename from .buildbot/tox/test.sh rename to .buildbot/tox-bionic/test.sh diff --git a/.buildbot/tox-focal/Dockerfile b/.buildbot/tox-focal/Dockerfile new file mode 100644 index 00000000..116317f3 --- /dev/null +++ b/.buildbot/tox-focal/Dockerfile @@ -0,0 +1,13 @@ +FROM ubuntu:focal + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update + +RUN apt-get install -yq --no-install-suggests --no-install-recommends \ + software-properties-common build-essential libcap-dev libffi-dev \ + libssl-dev python-all-dev python-setuptools \ + python3-dev python3-pip python3.9 python3.9-dev python3.9-venv \ + language-pack-en qt5dxcb-plugin tor xvfb + +RUN python3.9 -m pip install --upgrade pip tox virtualenv diff --git a/.buildbot/tox-focal/test.sh b/.buildbot/tox-focal/test.sh new file mode 120000 index 00000000..a9f8525c --- /dev/null +++ b/.buildbot/tox-focal/test.sh @@ -0,0 +1 @@ +../tox-bionic/test.sh \ No newline at end of file diff --git a/.buildbot/tox/Dockerfile b/.buildbot/tox/Dockerfile deleted file mode 100644 index 931bc0f7..00000000 --- a/.buildbot/tox/Dockerfile +++ /dev/null @@ -1,18 +0,0 @@ -FROM ubuntu:bionic AS tox - -RUN apt-get update - -# Common apt packages -RUN apt-get install -yq --no-install-suggests --no-install-recommends \ - software-properties-common build-essential libcap-dev libssl-dev \ - python-all-dev python-setuptools wget xvfb language-pack-en \ - libffi-dev python3-dev python3-pip python3.8 python3.8-dev python3.8-venv \ - python-msgpack python-pip python-qt4 python-six qtbase5-dev qt5-default \ - tor - -RUN python3.8 -m pip install setuptools wheel -RUN python3.8 -m pip install --upgrade pip tox virtualenv - -ENV LANG en_US.UTF-8 -ENV LANGUAGE en_US:en -ENV LC_ALL en_US.UTF-8 From 8e708c62770daba36cf8df1020639aaa826546b9 Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Mon, 9 May 2022 19:36:27 +0300 Subject: [PATCH 054/424] Adjust tox.ini: add py35 and py310, set basepython = python3 for bandit --- tox.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index f76ee054..7c5ad2f9 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = reset,py{27,27-portable,36,38,39},stats +envlist = reset,py{27,27-portable,35,36,38,39,310},stats skip_missing_interpreters = true [testenv] @@ -14,6 +14,7 @@ commands = coverage run -a -m tests [testenv:lint-basic] +basepython = python3 deps = bandit flake8 From 3be996eb6450d0a183a93a37cc0cc091c8d2f921 Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Wed, 11 May 2022 02:01:57 +0300 Subject: [PATCH 055/424] Replace obsolete pycrypto with pycryptodome to support jammy: pycrypto fails to install, openssl 3 has no ripemd160 hash. Also skip test_crypto.TestHashlib if openssl 3 is found. --- requirements.txt | 2 +- src/fallback/__init__.py | 4 ++-- src/tests/test_crypto.py | 11 +++++++---- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/requirements.txt b/requirements.txt index c7c599d5..ecb021cd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ coverage psutil -pycrypto +pycryptodome PyQt5;python_version>="3.7" python_prctl;platform_system=="Linux" six diff --git a/src/fallback/__init__.py b/src/fallback/__init__.py index 9a8d646f..f65999a1 100644 --- a/src/fallback/__init__.py +++ b/src/fallback/__init__.py @@ -18,11 +18,11 @@ try: hashlib.new('ripemd160') except ValueError: try: - from Crypto.Hash import RIPEMD + from Crypto.Hash import RIPEMD160 except ImportError: RIPEMD160Hash = None else: - RIPEMD160Hash = RIPEMD.RIPEMD160Hash + RIPEMD160Hash = RIPEMD160.new else: def RIPEMD160Hash(data=None): """hashlib based RIPEMD160Hash""" diff --git a/src/tests/test_crypto.py b/src/tests/test_crypto.py index 38410359..e68e7ee5 100644 --- a/src/tests/test_crypto.py +++ b/src/tests/test_crypto.py @@ -3,6 +3,7 @@ Test the alternatives for crypto primitives """ import hashlib +import ssl import unittest from abc import ABCMeta, abstractmethod from binascii import hexlify @@ -11,9 +12,9 @@ from pybitmessage import highlevelcrypto try: - from Crypto.Hash import RIPEMD + from Crypto.Hash import RIPEMD160 except ImportError: - RIPEMD = None + RIPEMD160 = None from .samples import ( sample_pubsigningkey, sample_pubencryptionkey, @@ -42,6 +43,8 @@ class RIPEMD160TestCase(object): self.assertEqual(hexlify(self._hashdigest(pubkey_sha)), sample_ripe) +@unittest.skipIf( + ssl.OPENSSL_VERSION.startswith('OpenSSL 3'), 'no ripemd160 in openssl 3') class TestHashlib(RIPEMD160TestCase, unittest.TestCase): """RIPEMD160 test case for hashlib""" @staticmethod @@ -51,12 +54,12 @@ class TestHashlib(RIPEMD160TestCase, unittest.TestCase): return hasher.digest() -@unittest.skipUnless(RIPEMD, 'pycrypto package not found') +@unittest.skipUnless(RIPEMD160, 'pycrypto package not found') class TestCrypto(RIPEMD160TestCase, unittest.TestCase): """RIPEMD160 test case for Crypto""" @staticmethod def _hashdigest(data): - return RIPEMD.RIPEMD160Hash(data).digest() + return RIPEMD160.new(data).digest() class TestHighlevelcrypto(unittest.TestCase): From 06e5b7bdf38ea9db8830a97c08305edc72554eb3 Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Wed, 11 May 2022 18:26:52 +0300 Subject: [PATCH 056/424] Start tor service in the build script --- .buildbot/tox-bionic/build.sh | 3 +++ 1 file changed, 3 insertions(+) create mode 100755 .buildbot/tox-bionic/build.sh diff --git a/.buildbot/tox-bionic/build.sh b/.buildbot/tox-bionic/build.sh new file mode 100755 index 00000000..87f670ce --- /dev/null +++ b/.buildbot/tox-bionic/build.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +sudo service tor start From a0d49272eb8982da7dccca1c2585b8996fa80c99 Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Wed, 11 May 2022 19:04:29 +0300 Subject: [PATCH 057/424] Install sudo and add builder to sudoers --- .buildbot/tox-bionic/Dockerfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.buildbot/tox-bionic/Dockerfile b/.buildbot/tox-bionic/Dockerfile index 5280289b..5cc36b7f 100644 --- a/.buildbot/tox-bionic/Dockerfile +++ b/.buildbot/tox-bionic/Dockerfile @@ -10,6 +10,10 @@ RUN apt-get install -yq --no-install-suggests --no-install-recommends \ python3-dev python3-pip python3.8 python3.8-dev python3.8-venv \ python-msgpack python-qt4 language-pack-en qt5dxcb-plugin tor xvfb +RUN apt-get install -yq sudo + +RUN echo 'builder ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers + RUN python3.8 -m pip install setuptools wheel RUN python3.8 -m pip install --upgrade pip tox virtualenv From 5b615d5ab54ea1f53406b0d281f774c5033a056c Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Mon, 30 May 2022 13:59:13 +0530 Subject: [PATCH 058/424] Add kivy QR-code Screen --- src/bitmessagekivy/baseclass/qrcode.py | 31 ++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/bitmessagekivy/baseclass/qrcode.py diff --git a/src/bitmessagekivy/baseclass/qrcode.py b/src/bitmessagekivy/baseclass/qrcode.py new file mode 100644 index 00000000..b3ad56b2 --- /dev/null +++ b/src/bitmessagekivy/baseclass/qrcode.py @@ -0,0 +1,31 @@ +# pylint: disable=import-error, no-name-in-module, too-few-public-methods + +""" +Generate QRcode of saved addresses in addressbook. +""" + +from kivy.app import App +from kivy.uix.screenmanager import Screen +from kivy.properties import StringProperty +from kivy_garden.qrcode import QRCodeWidget +from debug import logger + + +class ShowQRCode(Screen): + """ShowQRCode Screen class for kivy Ui""" + address = StringProperty() + + def __init__(self, *args, **kwargs): + """Instantiate kivy state variable""" + super(ShowQRCode, self).__init__(*args, **kwargs) + self.kivy_running_app = App.get_running_app() + + def qrdisplay(self, instance, address): + """Method used for showing QR Code""" + self.ids.qr.clear_widgets() + self.kivy_running_app.set_toolbar_for_QrCode() + self.address = address # used for label + self.ids.qr.add_widget(QRCodeWidget(data=self.address)) + self.ids.qr.children[0].show_border = False + instance.parent.parent.parent.dismiss() + logger.debug('Show QR code') From 9ed856f3a774df13b4b5d9844cc95bbcff634511 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Thu, 21 Apr 2022 20:19:07 +0530 Subject: [PATCH 059/424] Add common methods for all screens --- src/bitmessagekivy/baseclass/common.py | 165 +++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 src/bitmessagekivy/baseclass/common.py diff --git a/src/bitmessagekivy/baseclass/common.py b/src/bitmessagekivy/baseclass/common.py new file mode 100644 index 00000000..45e06ff0 --- /dev/null +++ b/src/bitmessagekivy/baseclass/common.py @@ -0,0 +1,165 @@ +# pylint: disable=no-name-in-module, attribute-defined-outside-init, import-error +""" + All Common widgets of kivy are managed here. +""" + +from datetime import datetime + +from kivy.core.window import Window +from kivy.metrics import dp +from kivy.uix.image import Image +from kivy.properties import ( + NumericProperty, + StringProperty +) +from kivy.app import App + +from kivymd.uix.list import ( + ILeftBody, + IRightBodyTouch, +) +from kivymd.uix.label import MDLabel +from kivymd.toast import kivytoast +from kivymd.uix.card import MDCardSwipe +from kivymd.uix.chip import MDChip + +from bitmessagekivy.get_platform import platform + + +ThemeClsColor = [0.12, 0.58, 0.95, 1] + +kivy_running_app = App.get_running_app() +kivy_state = kivy_running_app.kivy_state_obj + + +data_screens = { + "MailDetail": { + "kv_string": "maildetail", + "Factory": "MailDetail()", + "name_screen": "mailDetail", + "object": 0, + "Import": "from bitmessagekivy.baseclass.maildetail import MailDetail", + }, +} + + +def chip_tag(text): + """Create a new ChipTag""" + obj = MDChip() + # obj.size_hint = (None, None) + obj.size_hint = (0.16 if platform == "android" else 0.08, None) + obj.text = text + obj.icon = "" + obj.pos_hint = { + "center_x": 0.91 if platform == "android" else 0.94, + "center_y": 0.3 + } + obj.height = dp(18) + obj.text_color = (1, 1, 1, 1) + obj.radius = [8] + return obj + + +def toast(text): + """Method will display the toast message""" + kivytoast.toast(text) + + +def show_limited_cnt(total_msg): + """This method set the total count limit in badge_text""" + max_msg_count = '99+' + return max_msg_count if total_msg > 99 else str(total_msg) + + +def avatar_image_first_letter(letter_string): + """Returns first letter for the avatar image""" + try: + if isinstance(letter_string, int): + return letter_string[0] + elif isinstance(letter_string, str) and letter_string[0].isalnum(): + return letter_string.title()[0] + else: + return '!' + except IndexError: + return '!' + + +def add_time_widget(time): # pylint: disable=redefined-outer-name, W0201 + """This method is used to create TimeWidget""" + action_time = TimeTagRightSampleWidget( + text=str(show_time_history(time)), + font_style="Caption", + size=[120, 140] if platform == "android" else [64, 80], + ) + action_time.font_size = "11sp" + return action_time + + +def show_time_history(act_time): + """This method is used to return the message sent or receive time""" + action_time = datetime.fromtimestamp(int(act_time)) + crnt_date = datetime.now() + duration = crnt_date - action_time + 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) + + +def empty_screen_label(label_str=None, no_search_res_found=None): + """Returns default text on screen when no address is there.""" + content = MDLabel( + font_style='Caption', + theme_text_color='Primary', + text=no_search_res_found if kivy_state.searching_text else label_str, + halign='center', + size_hint_y=None, + valign='top') + return content + + +def mdlist_message_content(queryreturn, data): + """Method to check the length of message content""" + secondary_txt_len = 10 + third_txt_len = 25 + dot_str = '...........' + + for mail in queryreturn: + third_text = mail[3].replace('\n', ' ') + data.append({ + 'text': mail[1].strip(), + 'secondary_text': mail[2][:secondary_txt_len] + dot_str if len( + mail[2]) > secondary_txt_len else mail[2] + '\n' + " " + ( + third_text[:third_txt_len] + '...!') if len( + third_text) > third_txt_len else third_text, + 'ackdata': mail[5], 'senttime': mail[6]}) From 0becffb71fafc17488a142a827cfa6e4a0685451 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Wed, 25 May 2022 18:32:48 +0530 Subject: [PATCH 060/424] Fixed typo and code quality --- src/bitmessagekivy/baseclass/common.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/bitmessagekivy/baseclass/common.py b/src/bitmessagekivy/baseclass/common.py index 45e06ff0..75bd8af8 100644 --- a/src/bitmessagekivy/baseclass/common.py +++ b/src/bitmessagekivy/baseclass/common.py @@ -28,9 +28,6 @@ from bitmessagekivy.get_platform import platform ThemeClsColor = [0.12, 0.58, 0.95, 1] -kivy_running_app = App.get_running_app() -kivy_state = kivy_running_app.kivy_state_obj - data_screens = { "MailDetail": { @@ -74,12 +71,10 @@ def show_limited_cnt(total_msg): def avatar_image_first_letter(letter_string): """Returns first letter for the avatar image""" try: - if isinstance(letter_string, int): - return letter_string[0] - elif isinstance(letter_string, str) and letter_string[0].isalnum(): - return letter_string.title()[0] - else: - return '!' + image_letter = letter_string.title()[0] + if image_letter.isalnum(): + return image_letter + return '!' except IndexError: return '!' @@ -128,7 +123,7 @@ class SwipeToDeleteItem(MDCardSwipe): opening_time = NumericProperty(0.5) -class CutsomSwipeToDeleteItem(MDCardSwipe): +class CustomSwipeToDeleteItem(MDCardSwipe): """Custom swipe delete class for App UI""" text = StringProperty() cla = Window.size[0] / 2 @@ -138,6 +133,8 @@ class CutsomSwipeToDeleteItem(MDCardSwipe): def empty_screen_label(label_str=None, no_search_res_found=None): """Returns default text on screen when no address is there.""" + kivy_running_app = App.get_running_app() + kivy_state = kivy_running_app.kivy_state_obj content = MDLabel( font_style='Caption', theme_text_color='Primary', @@ -153,13 +150,13 @@ def mdlist_message_content(queryreturn, data): secondary_txt_len = 10 third_txt_len = 25 dot_str = '...........' - + dot_str2 = '...!' for mail in queryreturn: third_text = mail[3].replace('\n', ' ') data.append({ 'text': mail[1].strip(), 'secondary_text': mail[2][:secondary_txt_len] + dot_str if len( mail[2]) > secondary_txt_len else mail[2] + '\n' + " " + ( - third_text[:third_txt_len] + '...!') if len( + third_text[:third_txt_len] + dot_str2) if len( third_text) > third_txt_len else third_text, 'ackdata': mail[5], 'senttime': mail[6]}) From df4071bac15e4b912ab654432736688cd11f40ef Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Thu, 2 Jun 2022 21:50:51 +0530 Subject: [PATCH 061/424] Refactor MDList function --- src/bitmessagekivy/baseclass/common.py | 28 +++++++++++++++++--------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/bitmessagekivy/baseclass/common.py b/src/bitmessagekivy/baseclass/common.py index 75bd8af8..1c94efa2 100644 --- a/src/bitmessagekivy/baseclass/common.py +++ b/src/bitmessagekivy/baseclass/common.py @@ -145,18 +145,26 @@ def empty_screen_label(label_str=None, no_search_res_found=None): return content -def mdlist_message_content(queryreturn, data): - """Method to check the length of message content""" +def set_mail_details(mail): + """Return mail details""" secondary_txt_len = 10 third_txt_len = 25 dot_str = '...........' dot_str2 = '...!' + third_text = mail[3].replace('\n', ' ') + + mail_details_data = { + 'text': mail[1].strip(), + 'secondary_text': mail[2][:secondary_txt_len] + dot_str if len(mail[2]) > secondary_txt_len + else mail[2] + '\n' + " " + (third_text[:third_txt_len] + dot_str2) + if len(third_text) > third_txt_len else third_text, + 'ackdata': mail[5], 'senttime': mail[6] + } + return mail_details_data + + +def mdlist_message_content(queryreturn, data): + """Set Mails details in MD_list""" for mail in queryreturn: - third_text = mail[3].replace('\n', ' ') - data.append({ - 'text': mail[1].strip(), - 'secondary_text': mail[2][:secondary_txt_len] + dot_str if len( - mail[2]) > secondary_txt_len else mail[2] + '\n' + " " + ( - third_text[:third_txt_len] + dot_str2) if len( - third_text) > third_txt_len else third_text, - 'ackdata': mail[5], 'senttime': mail[6]}) + mdlist_data = set_mail_details(mail) + data.append(mdlist_data) From e9eb60bad3a6a58ac68c78d308c5c75f2080cdc8 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Tue, 7 Jun 2022 21:44:50 +0530 Subject: [PATCH 062/424] Refactor MDList mail details --- src/bitmessagekivy/baseclass/common.py | 35 +++++++++++++++----------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/bitmessagekivy/baseclass/common.py b/src/bitmessagekivy/baseclass/common.py index 1c94efa2..bd81207a 100644 --- a/src/bitmessagekivy/baseclass/common.py +++ b/src/bitmessagekivy/baseclass/common.py @@ -95,14 +95,11 @@ def show_time_history(act_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 + if duration.days < 1: + return action_time.strftime("%I:%M %p") + if duration.days < 365: + return action_time.strftime("%d %b") + return action_time.strftime("%d/%m/%Y") # pylint: disable=too-few-public-methods @@ -145,20 +142,30 @@ def empty_screen_label(label_str=None, no_search_res_found=None): return content -def set_mail_details(mail): - """Return mail details""" +def retrieve_secondary_text(mail): + """Retriving mail details""" secondary_txt_len = 10 third_txt_len = 25 dot_str = '...........' dot_str2 = '...!' third_text = mail[3].replace('\n', ' ') + if len(third_text) > third_txt_len: + if len(mail[2]) > secondary_txt_len: # pylint: disable=no-else-return + return mail[2][:secondary_txt_len] + dot_str + else: + return mail[2] + '\n' + " " + (third_text[:third_txt_len] + dot_str2) + else: + return third_text + + +def set_mail_details(mail): + """Setting mail details""" mail_details_data = { 'text': mail[1].strip(), - 'secondary_text': mail[2][:secondary_txt_len] + dot_str if len(mail[2]) > secondary_txt_len - else mail[2] + '\n' + " " + (third_text[:third_txt_len] + dot_str2) - if len(third_text) > third_txt_len else third_text, - 'ackdata': mail[5], 'senttime': mail[6] + 'secondary_text': retrieve_secondary_text(mail), + 'ackdata': mail[5], + 'senttime': mail[6] } return mail_details_data From 95cec93952fc2f42567631d62afce9592946e1dd Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Mon, 13 Jun 2022 16:32:32 +0530 Subject: [PATCH 063/424] Add a separate function to get kivy_state variables --- src/bitmessagekivy/baseclass/common.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/bitmessagekivy/baseclass/common.py b/src/bitmessagekivy/baseclass/common.py index bd81207a..25f52b04 100644 --- a/src/bitmessagekivy/baseclass/common.py +++ b/src/bitmessagekivy/baseclass/common.py @@ -40,6 +40,13 @@ data_screens = { } +def kivy_state_variables(): + """Return kivy_state variable""" + kivy_running_app = App.get_running_app() + kivy_state = kivy_running_app.kivy_state_obj + return kivy_state + + def chip_tag(text): """Create a new ChipTag""" obj = MDChip() @@ -65,7 +72,8 @@ def toast(text): def show_limited_cnt(total_msg): """This method set the total count limit in badge_text""" max_msg_count = '99+' - return max_msg_count if total_msg > 99 else str(total_msg) + total_msg_limit = 99 + return max_msg_count if total_msg > total_msg_limit else str(total_msg) def avatar_image_first_letter(letter_string): @@ -130,8 +138,7 @@ class CustomSwipeToDeleteItem(MDCardSwipe): def empty_screen_label(label_str=None, no_search_res_found=None): """Returns default text on screen when no address is there.""" - kivy_running_app = App.get_running_app() - kivy_state = kivy_running_app.kivy_state_obj + kivy_state = kivy_state_variables() content = MDLabel( font_style='Caption', theme_text_color='Primary', From 133a08198b5ac7b33f41a05f641ca0336239058d Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Tue, 14 Jun 2022 18:25:25 +0530 Subject: [PATCH 064/424] Function for kivy_state variable --- src/bitmessagekivy/baseclass/addressbook.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/bitmessagekivy/baseclass/addressbook.py b/src/bitmessagekivy/baseclass/addressbook.py index d0153348..60b22418 100644 --- a/src/bitmessagekivy/baseclass/addressbook.py +++ b/src/bitmessagekivy/baseclass/addressbook.py @@ -26,7 +26,7 @@ from bitmessagekivy.get_platform import platform from bitmessagekivy import kivy_helper_search from bitmessagekivy.baseclass.common import ( avatarImageFirstLetter, toast, empty_screen_label, - ThemeClsColor, SwipeToDeleteItem + ThemeClsColor, SwipeToDeleteItem, kivy_state_variables ) from bitmessagekivy.baseclass.popup import AddbookDetailPopup from bitmessagekivy.baseclass.addressbook_widgets import HelperAddressBook @@ -48,8 +48,7 @@ class AddressBook(Screen, HelperAddressBook): """Getting AddressBook Details""" super(AddressBook, self).__init__(*args, **kwargs) self.addbook_popup = None - self.kivy_running_app = App.get_running_app() - self.kivy_state = self.kivy_running_app.kivy_state_obj + self.kivy_state = kivy_state_variables() Clock.schedule_once(self.init_ui, 0) def init_ui(self, dt=0): @@ -160,13 +159,13 @@ class AddressBook(Screen, HelperAddressBook): UPDATE addressbook SET label = ? WHERE address = ?""", label, self.addbook_popup.content_cls.address) - state.kivyapp.root.ids.sc11.ids.ml.clear_widgets() - state.kivyapp.root.ids.sc11.loadAddresslist(None, 'All', '') + App.get_running_app().root.ids.sc11.ids.ml.clear_widgets() + App.get_running_app().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() + App.get_running_app().set_navbar_for_composer() self.compose_message(None, self.address) self.addbook_popup.dismiss() From d5f00ce6a9ca1c51e243fd7e57d31413f4363fd1 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Wed, 15 Jun 2022 19:35:46 +0530 Subject: [PATCH 065/424] Update Addressbook helper --- src/bitmessagekivy/baseclass/addressbook_widgets.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/bitmessagekivy/baseclass/addressbook_widgets.py b/src/bitmessagekivy/baseclass/addressbook_widgets.py index a1f5c5f4..bd844d6d 100644 --- a/src/bitmessagekivy/baseclass/addressbook_widgets.py +++ b/src/bitmessagekivy/baseclass/addressbook_widgets.py @@ -3,12 +3,10 @@ Addressbook widgets are here. """ - +from kivy.app import App from kivymd.uix.button import MDRaisedButton from kivymd.uix.dialog import MDDialog -import state - class HelperAddressBook(object): """Widget used in Addressbook are here""" @@ -40,7 +38,7 @@ class HelperAddressBook(object): @staticmethod def compose_message(from_addr=None, to_addr=None): """This UI independent method for message sending to reciever""" - window_obj = state.kivyapp.root.ids + window_obj = App.get_runnint_app().root.ids if to_addr: window_obj.sc3.children[1].ids.txt_input.text = to_addr if from_addr: From 4b75c7f8317cd007b2da86c0e46c01bc90567ff6 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Wed, 15 Jun 2022 18:04:59 +0530 Subject: [PATCH 066/424] Add Network Screen --- src/bitmessagekivy/baseclass/network.py | 47 +++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 src/bitmessagekivy/baseclass/network.py diff --git a/src/bitmessagekivy/baseclass/network.py b/src/bitmessagekivy/baseclass/network.py new file mode 100644 index 00000000..7975c1bf --- /dev/null +++ b/src/bitmessagekivy/baseclass/network.py @@ -0,0 +1,47 @@ +# pylint: disable=unused-argument, consider-using-f-string +# pylint: disable=no-name-in-module, too-few-public-methods + +""" + Network status +""" + +from kivy.clock import Clock +from kivy.properties import StringProperty +from kivy.uix.screenmanager import Screen + +from network import objectracker, stats + +import state + + +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)) From cced8fe84e88e153b16fd958da28eefdd7d5a064 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Mon, 30 May 2022 17:03:39 +0530 Subject: [PATCH 067/424] Add Kivy Payment Screen --- src/bitmessagekivy/baseclass/payment.py | 66 +++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 src/bitmessagekivy/baseclass/payment.py diff --git a/src/bitmessagekivy/baseclass/payment.py b/src/bitmessagekivy/baseclass/payment.py new file mode 100644 index 00000000..76037c79 --- /dev/null +++ b/src/bitmessagekivy/baseclass/payment.py @@ -0,0 +1,66 @@ +# pylint: disable=import-error, no-name-in-module, too-few-public-methods, too-many-ancestors + +''' + Payment/subscription frontend +''' + +from kivy.uix.boxlayout import BoxLayout +from kivy.uix.screenmanager import Screen +from kivy.app import App + +from kivymd.uix.behaviors.elevation import RectangularElevationBehavior +from kivymd.uix.label import MDLabel +from kivymd.uix.list import ( + IRightBodyTouch, + OneLineAvatarIconListItem +) + +from bitmessagekivy.baseclass.common import toast, kivy_state_variables + + +class Payment(Screen): + """Payment Screen class for kivy Ui""" + + def __init__(self, *args, **kwargs): + """Instantiate kivy state variable""" + super(Payment, self).__init__(*args, **kwargs) + self.kivy_state = kivy_state_variables() + + # TODO: get_free_credits() is not used anywhere, will be used later for Payment/subscription. + def get_free_credits(self, instance): # pylint: disable=unused-argument + """Get the available credits""" + # pylint: disable=no-self-use + self.kivy_state.available_credit = 0 + existing_credits = 0 + if existing_credits > 0: + toast( + 'We already have added free credit' + ' for the subscription to your account!') + else: + toast('Credit added to your account!') + # TODO: There is no sc18 screen id is available, + # need to create sc18 for Credits screen inside main.kv + App.get_running_app().root.ids.sc18.ids.cred.text = '{0}'.format( + self.kivy_state.available_credit) + + +class Category(BoxLayout, RectangularElevationBehavior): + """Category class for kivy Ui""" + elevation_normal = .01 + + +class ProductLayout(BoxLayout, RectangularElevationBehavior): + """ProductLayout class for kivy Ui""" + elevation_normal = .01 + + +class PaymentMethodLayout(BoxLayout): + """PaymentMethodLayout class for kivy Ui""" + + +class ListItemWithLabel(OneLineAvatarIconListItem): + """ListItemWithLabel class for kivy Ui""" + + +class RightLabel(IRightBodyTouch, MDLabel): + """RightLabel class for kivy Ui""" From b8c39e8dce80c96ef9a98127258f639d387c0d5c Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Mon, 20 Jun 2022 20:07:58 +0530 Subject: [PATCH 068/424] Refactor Addressbook --- src/bitmessagekivy/baseclass/addressbook.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/bitmessagekivy/baseclass/addressbook.py b/src/bitmessagekivy/baseclass/addressbook.py index 60b22418..6aec8eef 100644 --- a/src/bitmessagekivy/baseclass/addressbook.py +++ b/src/bitmessagekivy/baseclass/addressbook.py @@ -20,8 +20,6 @@ from kivy.properties import ( from kivy.uix.screenmanager import Screen from kivy.app import App -import state - from bitmessagekivy.get_platform import platform from bitmessagekivy import kivy_helper_search from bitmessagekivy.baseclass.common import ( @@ -86,7 +84,7 @@ class AddressBook(Screen, HelperAddressBook): listItem.theme_text_color = "Custom" listItem.text_color = ThemeClsColor image = os.path.join( - state.imageDir, "text_images", "{}.png".format(avatarImageFirstLetter(item[0].strip())) + self.kivy_state.imageDir, "text_images", "{}.png".format(avatarImageFirstLetter(item[0].strip())) ) message_row.ids.avater_img.source = image listItem.bind(on_release=partial( From cb0d908a5edce202fdf1a3e4c5a703ccd8ea9a5e Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Mon, 9 May 2022 20:45:58 +0530 Subject: [PATCH 069/424] Add and refactor Maildetail screen --- src/bitmessagekivy/baseclass/maildetail.py | 252 +++++++++++++++++++++ 1 file changed, 252 insertions(+) create mode 100644 src/bitmessagekivy/baseclass/maildetail.py diff --git a/src/bitmessagekivy/baseclass/maildetail.py b/src/bitmessagekivy/baseclass/maildetail.py new file mode 100644 index 00000000..f82c8858 --- /dev/null +++ b/src/bitmessagekivy/baseclass/maildetail.py @@ -0,0 +1,252 @@ +# pylint: disable=unused-argument, consider-using-f-string, import-error, attribute-defined-outside-init +# pylint: disable=unnecessary-comprehension, no-member, no-name-in-module, too-few-public-methods + +""" +Maildetail screen for inbox, sent, draft and trash. +""" + +import os +from datetime import datetime + +from kivy.core.clipboard import Clipboard +from kivy.clock import Clock +from kivy.properties import ( + StringProperty, + NumericProperty +) +from kivy.uix.screenmanager import Screen +from kivy.factory import Factory +from kivy.app import App + +from kivymd.uix.button import MDFlatButton, MDIconButton +from kivymd.uix.dialog import MDDialog +from kivymd.uix.list import ( + OneLineListItem, + IRightBodyTouch +) + +from bitmessagekivy.baseclass.common import ( + toast, avatarImageFirstLetter, ShowTimeHistoy, kivy_state_variables +) +from bitmessagekivy.baseclass.popup import SenderDetailPopup +from bitmessagekivy.get_platform import platform +from helper_sql import sqlQuery +from helper_sent import delete, retrieve_message_details +from helper_inbox import trash + + +class OneLineListTitle(OneLineListItem): + """OneLineListTitle class for kivy Ui""" + __events__ = ('on_long_press', ) + long_press_time = NumericProperty(1) + + def on_state(self, instance, value): + """On state""" + if value == 'down': + lpt = self.long_press_time + self._clockev = Clock.schedule_once(self._do_long_press, lpt) + else: + self._clockev.cancel() + + def _do_long_press(self, dt): + """Do long press""" + self.dispatch('on_long_press') + + def on_long_press(self, *largs): + """On long press""" + self.copymessageTitle(self.text) + + def copymessageTitle(self, title_text): + """this method is for displaying dialog box""" + self.title_text = title_text + width = .8 if platform == 'android' else .55 + self.dialog_box = MDDialog( + text=title_text, + size_hint=(width, .25), + buttons=[ + MDFlatButton( + text="Copy", on_release=self.callback_for_copy_title + ), + MDFlatButton( + text="Cancel", on_release=self.callback_for_copy_title, + ), + ],) + self.dialog_box.open() + + def callback_for_copy_title(self, instance): + """Callback of alert box""" + if instance.text == 'Copy': + Clipboard.copy(self.title_text) + self.dialog_box.dismiss() + toast(instance.text) + + +class IconRightSampleWidget(IRightBodyTouch, MDIconButton): + """IconRightSampleWidget class for kivy Ui""" + + +class MailDetail(Screen): # pylint: disable=too-many-instance-attributes + """MailDetail Screen class for kivy Ui""" + + to_addr = StringProperty() + from_addr = StringProperty() + subject = StringProperty() + message = StringProperty() + status = StringProperty() + page_type = StringProperty() + time_tag = StringProperty() + avatarImg = StringProperty() + no_subject = '(no subject)' + + def __init__(self, *args, **kwargs): + """Mail Details method""" + super(MailDetail, self).__init__(*args, **kwargs) + self.kivy_state = kivy_state_variables() + Clock.schedule_once(self.init_ui, 0) + + def init_ui(self, dt=0): + """Clock Schdule for method MailDetail mails""" + self.page_type = self.kivy_state.detailPageType if self.kivy_state.detailPageType else '' + try: + if self.kivy_state.detailPageType in ('sent', 'draft'): + data = retrieve_message_details(self.kivy_state.mail_id) + self.assign_mail_details(data) + App.get_running_app().set_mail_detail_header() + elif self.kivy_state.detailPageType == 'inbox': + data = sqlQuery( + "select toaddress, fromaddress, subject, message, received from inbox" + " where msgid = ?", self.kivy_state.mail_id) + self.assign_mail_details(data) + App.get_running_app().set_mail_detail_header() + except Exception as e: # pylint: disable=unused-variable + print('Something wents wrong!!') + + def assign_mail_details(self, data): + """Assigning mail details""" + subject = data[0][2].decode() if isinstance(data[0][2], bytes) else data[0][2] + body = data[0][3].decode() if isinstance(data[0][2], bytes) else data[0][3] + self.to_addr = data[0][0] if len(data[0][0]) > 4 else ' ' + self.from_addr = data[0][1] + + self.subject = subject.capitalize( + ) if subject.capitalize() else self.no_subject + self.message = body + if len(data[0]) == 7: + self.status = data[0][4] + self.time_tag = ShowTimeHistoy(data[0][4]) if self.kivy_state.detailPageType == 'inbox' \ + else ShowTimeHistoy(data[0][6]) + self.avatarImg = os.path.join(self.kivy_state.imageDir, 'avatar.png') \ + if self.kivy_state.detailPageType == 'draft' \ + else (os.path.join(self.kivy_state.imageDir, 'text_images', '{0}.png'.format(avatarImageFirstLetter( + self.subject.strip())))) + self.timeinseconds = data[0][4] if self.kivy_state.detailPageType == 'inbox' else data[0][6] + + def delete_mail(self): + """Method for mail delete""" + msg_count_objs = App.get_running_app().root.ids.content_drawer.ids + self.kivy_state.searching_text = '' + self.children[0].children[0].active = True + if self.kivy_state.detailPageType == 'sent': + App.get_running_app().root.ids.sc4.ids.sent_search.ids.search_field.text = '' + delete(self.kivy_state.mail_id) + msg_count_objs.send_cnt.ids.badge_txt.text = str(int(self.kivy_state.sent_count) - 1) + self.kivy_state.sent_count = str(int(self.kivy_state.sent_count) - 1) + self.parent.screens[2].ids.ml.clear_widgets() + self.parent.screens[2].loadSent(self.kivy_state.association) + elif self.kivy_state.detailPageType == 'inbox': + App.get_running_app().root.ids.sc1.ids.inbox_search.ids.search_field.text = '' + trash(self.kivy_state.mail_id) + msg_count_objs.inbox_cnt.ids.badge_txt.text = str( + int(self.kivy_state.inbox_count) - 1) + self.kivy_state.inbox_count = str(int(self.kivy_state.inbox_count) - 1) + self.parent.screens[0].ids.ml.clear_widgets() + self.parent.screens[0].loadMessagelist(self.kivy_state.association) + + elif self.kivy_state.detailPageType == 'draft': + delete(self.kivy_state.mail_id) + msg_count_objs.draft_cnt.ids.badge_txt.text = str( + int(self.kivy_state.draft_count) - 1) + self.kivy_state.draft_count = str(int(self.kivy_state.draft_count) - 1) + self.parent.screens[13].clear_widgets() + self.parent.screens[13].add_widget(Factory.Draft()) + + if self.kivy_state.detailPageType != 'draft': + msg_count_objs.trash_cnt.ids.badge_txt.text = str( + int(self.kivy_state.trash_count) + 1) + msg_count_objs.allmail_cnt.ids.badge_txt.text = str( + int(self.kivy_state.all_count) - 1) + self.kivy_state.trash_count = str(int(self.kivy_state.trash_count) + 1) + self.kivy_state.all_count = str(int(self.kivy_state.all_count) - 1) if \ + int(self.kivy_state.all_count) else '0' + self.parent.screens[3].clear_widgets() + self.parent.screens[3].add_widget(Factory.Trash()) + self.parent.screens[14].clear_widgets() + self.parent.screens[14].add_widget(Factory.Allmails()) + Clock.schedule_once(self.callback_for_delete, 4) + + def callback_for_delete(self, dt=0): + """Delete method from allmails""" + if self.kivy_state.detailPageType: + self.children[0].children[0].active = False + App.get_running_app().set_common_header() + self.parent.current = 'allmails' \ + if self.kivy_state.is_allmail else self.kivy_state.detailPageType + self.kivy_state.detailPageType = '' + toast('Deleted') + + def get_message_details_to_reply(self, data): + """Getting message details and fill into fields when reply""" + sender_address = ' wrote:--------------\n' + message_time = '\n\n --------------On ' + composer_obj = self.parent.screens[1].children[1].ids + composer_obj.ti.text = data[0][0] + composer_obj.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 = data[0][1] + composer_obj.body.text = ( + message_time + time_tag + ', ' + sender_name + sender_address + data[0][3]) + composer_obj.body.focus = True + composer_obj.body.cursor = (0, 0) + + def inbox_reply(self): + """Reply inbox messages""" + self.kivy_state.in_composer = True + data = retrieve_message_details(self.kivy_state.mail_id) + self.get_message_details_to_reply(data) + App.get_running_app().root.ids.sc3.children[1].ids.rv.data = '' + self.parent.current = 'create' + App.get_running_app().set_navbar_for_composer() + + def get_message_details_for_draft_reply(self, data): + """Getting and setting message details fill into fields when draft reply""" + composer_ids = ( + self.parent.parent.ids.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] != self.no_subject else '' + composer_ids.body.text = data[0][3] + + def write_msg(self, navApp): + """Write on draft mail""" + self.kivy_state.send_draft_mail = self.kivy_state.mail_id + data = retrieve_message_details(self.kivy_state.mail_id) + self.get_message_details_for_draft_reply(data) + 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) From 741f9ac4c37621966a80f048d033bfd8b3212a21 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Thu, 16 Jun 2022 20:44:45 +0530 Subject: [PATCH 070/424] Refactor & Add Scan Screen --- src/bitmessagekivy/baseclass/scan_screen.py | 104 ++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 src/bitmessagekivy/baseclass/scan_screen.py diff --git a/src/bitmessagekivy/baseclass/scan_screen.py b/src/bitmessagekivy/baseclass/scan_screen.py new file mode 100644 index 00000000..86549fb8 --- /dev/null +++ b/src/bitmessagekivy/baseclass/scan_screen.py @@ -0,0 +1,104 @@ +# pylint: disable=no-member, too-many-arguments, too-few-public-methods +# pylint: disable=no-name-in-module, unused-argument, arguments-differ + +""" +QR code Scan Screen used in message composer to get recipient address + +""" + +import os +import cv2 + +from kivy.clock import Clock +from kivy.lang import Builder +from kivy.properties import ( + BooleanProperty, + ObjectProperty, + StringProperty +) +from kivy.uix.screenmanager import Screen + +from bitmessagekivy.get_platform import platform + +from debug import logger + + +class ScanScreen(Screen): + """ScanScreen is for scaning Qr code""" + # 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": + cap = cv2.VideoCapture(0) + is_cam_open = cap.isOpened() + while is_cam_open: + logger.debug('Camera is available!') + self.camera_avaialbe = True + break + else: + logger.debug("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 + """ + if platform != "android": + Clock.schedule_once(self.start_camera, 0) + + def on_leave(self): + """This method will call on leave""" + Clock.schedule_once(self.stop_camera, 0) + + def start_camera(self, *args): + """Its used for starting camera for scanning qrcode""" + # pylint: disable=attribute-defined-outside-init + self.xcam = self.children[0].ids.zbarcam.ids.xcamera + if platform == "android": + self.xcam.play = True + else: + Clock.schedule_once(self.open_cam, 0) + + def stop_camera(self, *args): + """Its used for stop the camera""" + self.xcam.play = False + if platform != "android": + self.xcam._camera._device.release() + + def open_cam(self, *args): + """It will open up the camera""" + if not self.xcam._camera._device.isOpened(): + self.xcam._camera._device.open(self.xcam._camera._index) + self.xcam.play = True From 28b43d1d5627101daf73d3191c6d6c4ef3c41c96 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Wed, 29 Jun 2022 19:46:36 +0530 Subject: [PATCH 071/424] Update helper_sent --- src/helper_sent.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/helper_sent.py b/src/helper_sent.py index 2996939e..05b17f7f 100644 --- a/src/helper_sent.py +++ b/src/helper_sent.py @@ -7,7 +7,7 @@ import uuid from addresses import decodeAddress from bmconfigparser import config from helper_ackPayload import genAckPayload -from helper_sql import sqlExecute +from helper_sql import sqlExecute, sqlQuery # pylint: disable=too-many-arguments @@ -51,3 +51,11 @@ def insert(msgid=None, toAddress='[Broadcast subscribers]', fromAddress=None, su def delete(ack_data): """Perform Delete query""" sqlExecute("DELETE FROM sent WHERE ackdata = ?", ack_data) + + +def retrieve_message_details(ack_data): + """Retrieving Message details""" + data = sqlQuery( + "select toaddress, fromaddress, subject, message, received from inbox where msgid = ?", ack_data + ) + return data From 4dbf9e6f1dfd59f87e1d5d0626141a20b123b862 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Fri, 24 Jun 2022 19:54:23 +0530 Subject: [PATCH 072/424] Update naming convention --- src/bitmessagekivy/baseclass/addressbook.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bitmessagekivy/baseclass/addressbook.py b/src/bitmessagekivy/baseclass/addressbook.py index 6aec8eef..d80c3fbe 100644 --- a/src/bitmessagekivy/baseclass/addressbook.py +++ b/src/bitmessagekivy/baseclass/addressbook.py @@ -26,7 +26,7 @@ from bitmessagekivy.baseclass.common import ( avatarImageFirstLetter, toast, empty_screen_label, ThemeClsColor, SwipeToDeleteItem, kivy_state_variables ) -from bitmessagekivy.baseclass.popup import AddbookDetailPopup +from bitmessagekivy.baseclass.popup import SavedAddressDetailPopup from bitmessagekivy.baseclass.addressbook_widgets import HelperAddressBook from debug import logger from helper_sql import sqlExecute @@ -117,7 +117,7 @@ class AddressBook(Screen, HelperAddressBook): if instance.state == 'closed': instance.ids.delete_msg.disabled = True if instance.open_progress == 0.0: - obj = AddbookDetailPopup() + obj = SavedAddressDetailPopup() self.address_label = obj.address_label = label self.address = obj.address = address width = .9 if platform == 'android' else .8 From 23422dfd7ff1efad0c3e8b245355bd1704a1d5be Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Mon, 27 Jun 2022 18:12:12 +0530 Subject: [PATCH 073/424] Add common methods for backend --- src/backend/address_generator.py | 49 ++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/backend/address_generator.py diff --git a/src/backend/address_generator.py b/src/backend/address_generator.py new file mode 100644 index 00000000..39220e01 --- /dev/null +++ b/src/backend/address_generator.py @@ -0,0 +1,49 @@ +""" +Common methods and functions for kivy and qt. +""" + +import queues +from bmconfigparser import config +from defaults import ( + networkDefaultProofOfWorkNonceTrialsPerByte, + networkDefaultPayloadLengthExtraBytes +) + + +class AddressGenerator(object): + """"Base class for address generation and validation""" + def __init__(self): + pass + + @staticmethod + def random_address_generation( + label, streamNumberForAddress=1, eighteenByteRipe=False, + nonceTrialsPerByte=networkDefaultProofOfWorkNonceTrialsPerByte, + payloadLengthExtraBytes=networkDefaultPayloadLengthExtraBytes + ): + """Start address generation and return whether validation was successful""" + + labels = [config.get(obj, 'label') + for obj in config.addresses()] + if label and label not in labels: + queues.addressGeneratorQueue.put(( + 'createRandomAddress', 4, streamNumberForAddress, label, 1, + "", eighteenByteRipe, nonceTrialsPerByte, + payloadLengthExtraBytes)) + return True + return False + + @staticmethod + def address_validation(instance, label): + """Checking address validation while creating""" + labels = [config.get(obj, 'label') for obj in config.addresses()] + if label in labels: + instance.error = True + instance.helper_text = 'it is already exist you'\ + ' can try this Ex. ( {0}_1, {0}_2 )'.format( + label) + elif label: + instance.error = False + else: + instance.error = True + instance.helper_text = 'This field is required' From 453c99a5019ae3658447179f36cd0bec4f81bbee Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Mon, 20 Jun 2022 20:16:35 +0530 Subject: [PATCH 074/424] Add & Refactor Popup screen --- src/bitmessagekivy/baseclass/popup.py | 230 ++++++++++++++++++++++++++ src/bitmessagekivy/kv/popup.kv | 6 +- 2 files changed, 233 insertions(+), 3 deletions(-) create mode 100644 src/bitmessagekivy/baseclass/popup.py diff --git a/src/bitmessagekivy/baseclass/popup.py b/src/bitmessagekivy/baseclass/popup.py new file mode 100644 index 00000000..a14b22fd --- /dev/null +++ b/src/bitmessagekivy/baseclass/popup.py @@ -0,0 +1,230 @@ +# pylint: disable=import-error, attribute-defined-outside-init +# pylint: disable=no-member, no-name-in-module, unused-argument, too-few-public-methods + +""" +All the popup are managed here. + +""" + +from datetime import datetime + +from kivy.clock import Clock +from kivy.metrics import dp +from kivy.properties import StringProperty +from kivy.uix.boxlayout import BoxLayout +from kivy.uix.popup import Popup +from kivy.app import App + +from bitmessagekivy import kivy_helper_search +from bitmessagekivy.get_platform import platform + +from bitmessagekivy.baseclass.common import toast + +from addresses import decodeAddress +from debug import logger + + +class AddressChangingLoader(Popup): + """Run a Screen Loader when changing the Identity for kivy UI""" + + def __init__(self, **kwargs): + super(AddressChangingLoader, self).__init__(**kwargs) + Clock.schedule_once(self.dismiss_popup, 0.5) + + def dismiss_popup(self, dt): + """Dismiss popups""" + self.dismiss() + + +class AddAddressPopup(BoxLayout): + """Popup for adding new address to addressbook""" + + validation_dict = { + "missingbm": "The address should start with ''BM-''", + "checksumfailed": "The address is not typed or copied correctly", + "versiontoohigh": "The version number of this address is higher than this" + " software can support. Please upgrade Bitmessage.", + "invalidcharacters": "The address contains invalid characters.", + "ripetooshort": "Some data encoded in the address is too short.", + "ripetoolong": "Some data encoded in the address is too long.", + "varintmalformed": "Some data encoded in the address is malformed." + } + valid = False + + def __init__(self, **kwargs): + super(AddAddressPopup, self).__init__(**kwargs) + + def checkAddress_valid(self, instance): + """Checking address is valid or not""" + my_addresses = ( + App.get_running_app().root.ids.content_drawer.ids.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 = True + self.ids.address.helper_text = 'This field is required' + + def checkLabel_valid(self, instance): + """Checking address label is unique or not""" + entered_label = instance.text.strip() + addr_labels = [labels[0] for labels in kivy_helper_search.search_sql( + folder="addressbook")] + if entered_label in addr_labels: + self.ids.label.error = True + self.ids.label.helper_text = 'Label name already exists.' + elif entered_label: + self.ids.label.error = False + else: + self.ids.label.error = True + self.ids.label.helper_text = 'This field is required' + + def _onSuccess(self, addressVersion, streamNumber, ripe): + pass + + def addressChanged(self, addr): + """Address validation callback, performs validation and gives feedback""" + status, addressVersion, streamNumber, ripe = decodeAddress( + str(addr)) + self.valid = status == 'success' + + if self.valid: + text = "Address is valid." + self._onSuccess(addressVersion, streamNumber, ripe) + return text + return self.validation_dict.get(status) + + +class SavedAddressDetailPopup(BoxLayout): + """Pop-up for Saved Address details for kivy UI""" + + address_label = StringProperty() + address = StringProperty() + + def __init__(self, **kwargs): + """Set screen of address detail page""" + super(SavedAddressDetailPopup, self).__init__(**kwargs) + + def checkLabel_valid(self, instance): + """Checking address label is unique of not""" + entered_label = str(instance.text.strip()) + address_list = kivy_helper_search.search_sql(folder="addressbook") + addr_labels = [labels[0] for labels in address_list] + add_dict = dict(address_list) + if self.address and entered_label in addr_labels \ + and self.address != add_dict[entered_label]: + self.ids.add_label.error = True + self.ids.add_label.helper_text = 'label name already exists.' + elif entered_label: + self.ids.add_label.error = False + else: + self.ids.add_label.error = True + self.ids.add_label.helper_text = 'This field is required' + + +class MyaddDetailPopup(BoxLayout): + """MyaddDetailPopup class for kivy Ui""" + + address_label = StringProperty() + address = StringProperty() + + def __init__(self, **kwargs): + """My Address Details screen setting""" + super(MyaddDetailPopup, self).__init__(**kwargs) + + def send_message_from(self): + """Method used to fill from address of composer autofield""" + App.get_running_app().set_navbar_for_composer() + window_obj = App.get_running_app().root.ids + window_obj.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() + + def close_pop(self): + """Pop is Cancelled""" + self.parent.parent.parent.dismiss() + toast('Cancelled') + + +class AppClosingPopup(Popup): + """AppClosingPopup class for kivy Ui""" + + def __init__(self, **kwargs): + super(AppClosingPopup, self).__init__(**kwargs) + + def closingAction(self, text): + """Action on closing window""" + exit_message = "*******************EXITING FROM APPLICATION*******************" + if text == 'Yes': + logger.debug(exit_message) + import shutdown + shutdown.doCleanShutdown() + else: + self.dismiss() + toast(text) + + +class SenderDetailPopup(Popup): + """SenderDetailPopup class for kivy Ui""" + + to_addr = StringProperty() + from_addr = StringProperty() + time_tag = StringProperty() + + def __init__(self, **kwargs): + """this metthod initialized the send message detial popup""" + super(SenderDetailPopup, self).__init__(**kwargs) + + def assignDetail(self, to_addr, from_addr, timeinseconds): + """Detailes assigned""" + self.to_addr = to_addr + self.from_addr = from_addr + time_obj = datetime.fromtimestamp(int(timeinseconds)) + self.time_tag = time_obj.strftime("%d %b %Y, %I:%M %p") + device_type = 2 if platform == 'android' else 1.5 + pop_height = 1.2 * device_type * (self.ids.sd_label.height + self.ids.dismiss_btn.height) + if len(to_addr) > 3: + self.height = pop_height + self.ids.to_addId.size_hint_y = None + self.ids.to_addId.height = 50 + self.ids.to_addtitle.add_widget(ToAddressTitle()) + frmaddbox = ToAddrBoxlayout() + frmaddbox.set_toAddress(to_addr) + self.ids.to_addId.add_widget(frmaddbox) + else: + self.ids.space_1.height = dp(0) + self.ids.space_2.height = dp(0) + self.ids.myadd_popup_box.spacing = dp(8 if platform == 'android' else 3) + self.height = pop_height / 1.2 + + +class ToAddrBoxlayout(BoxLayout): + """ToAddrBoxlayout class for kivy Ui""" + to_addr = StringProperty() + + def set_toAddress(self, to_addr): + """This method is use to set to address""" + self.to_addr = to_addr + + +class ToAddressTitle(BoxLayout): + """ToAddressTitle class for BoxLayout behaviour""" diff --git a/src/bitmessagekivy/kv/popup.kv b/src/bitmessagekivy/kv/popup.kv index fd64ee26..f2d0f292 100644 --- a/src/bitmessagekivy/kv/popup.kv +++ b/src/bitmessagekivy/kv/popup.kv @@ -1,4 +1,4 @@ -: +: separator_color: 1, 1, 1, 1 background: "White.png" Button: @@ -12,7 +12,7 @@ size: root.size -: +: id: popup_box orientation: 'vertical' # spacing:dp(20) @@ -45,7 +45,7 @@ Color: rgba: (0,0,0,1) -: +: id: addbook_popup_box size_hint_y: None height: 2.5*(add_label.height) From da26b0fc17905eef0e63e9cd321e85a1977e6d18 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Thu, 23 Jun 2022 17:52:18 +0530 Subject: [PATCH 075/424] Add Login screen --- src/bitmessagekivy/baseclass/login.py | 96 +++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 src/bitmessagekivy/baseclass/login.py diff --git a/src/bitmessagekivy/baseclass/login.py b/src/bitmessagekivy/baseclass/login.py new file mode 100644 index 00000000..0d27838d --- /dev/null +++ b/src/bitmessagekivy/baseclass/login.py @@ -0,0 +1,96 @@ +# pylint: disable=no-member, too-many-arguments, too-few-public-methods +# pylint: disable=no-name-in-module, unused-argument, arguments-differ + +""" +Login screen appears when the App is first time starts and when new Address is generated. +""" + + +from kivy.clock import Clock +from kivy.properties import StringProperty, BooleanProperty +from kivy.uix.boxlayout import BoxLayout +from kivy.uix.screenmanager import Screen +from kivy.app import App + +from backend.address_generator import AddressGenerator # pylint: disable=import-error +from kivymd.uix.behaviors.elevation import RectangularElevationBehavior +from bitmessagekivy.baseclass.common import toast +from bmconfigparser import config + + +class Login(Screen): + """Login Screeen class for kivy Ui""" + log_text1 = ( + 'You may generate addresses by using either random numbers' + ' or by using a passphrase If you use a passphrase, the address' + ' is called a deterministic; address The Random Number option is' + ' selected by default but deterministic addresses have several pros' + ' and cons:') + log_text2 = ('If talk about pros You can recreate your addresses on any computer' + ' from memory, You need-not worry about backing up your keys.dat file' + ' as long as you can remember your passphrase and aside talk about cons' + ' You must remember (or write down) your You must remember the address' + ' version number and the stream number along with your passphrase If you' + ' choose a weak passphrase and someone on the Internet can brute-force it,' + ' they can read your messages and send messages as you') + + +class Random(Screen): + """Random Screen class for Ui""" + + is_active = BooleanProperty(False) + checked = StringProperty("") + + def generateaddress(self): + """Method for Address Generator""" + entered_label = str(self.ids.add_random_bx.children[0].ids.lab.text).strip() + if not entered_label: + self.ids.add_random_bx.children[0].ids.lab.focus = True + is_address = AddressGenerator.random_address_generation( + entered_label, streamNumberForAddress=1, eighteenByteRipe=False, + nonceTrialsPerByte=1000, payloadLengthExtraBytes=1000 + ) + if is_address: + toast('Creating New Address ...') + self.parent.parent.ids.toolbar.opacity = 1 + self.parent.parent.ids.toolbar.disabled = False + App.get_running_app().loadMyAddressScreen(True) + self.manager.current = 'myaddress' + Clock.schedule_once(self.address_created_callback, 6) + + def address_created_callback(self, dt=0): + """New address created""" + App.get_running_app().loadMyAddressScreen(False) + App.get_running_app().root.ids.sc10.ids.ml.clear_widgets() + App.get_running_app().root.ids.sc10.is_add_created = True + App.get_running_app().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 config.addresses() + if config.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): + """Retrieve created labels and validate""" + entered_label = str(instance.text.strip()) + AddressGenerator.address_validation(instance, entered_label) + + def reset_address_label(self): + """Resetting address labels""" + if not self.ids.add_random_bx.children: + self.ids.add_random_bx.add_widget(RandomBoxlayout()) + + +class InfoLayout(BoxLayout, RectangularElevationBehavior): + """InfoLayout class for kivy Ui""" + + +class RandomBoxlayout(BoxLayout): + """RandomBoxlayout class for BoxLayout behaviour""" From 5fa3e37a1d96cc5cc02af30e37b8eb6eaf287853 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Thu, 7 Jul 2022 21:17:40 +0530 Subject: [PATCH 076/424] Add delete query --- src/helper_inbox.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/helper_inbox.py b/src/helper_inbox.py index d99e9544..555795df 100644 --- a/src/helper_inbox.py +++ b/src/helper_inbox.py @@ -18,6 +18,11 @@ def trash(msgid): queues.UISignalQueue.put(('removeInboxRowByMsgid', msgid)) +def delete(ack_data): + """Permanent delete message from trash""" + sqlExecute("DELETE FROM inbox WHERE msgid = ?", ack_data) + + def undeleteMessage(msgid): """Undelte the message""" sqlExecute('''UPDATE inbox SET folder='inbox' WHERE msgid=?''', msgid) From 32651fbe53312567d3dfea1fe64468284b022dc9 Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Sat, 2 Jul 2022 15:38:46 +0300 Subject: [PATCH 077/424] Fix a mistake in packaging sql files for Windows --- packages/pyinstaller/bitmessagemain.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pyinstaller/bitmessagemain.spec b/packages/pyinstaller/bitmessagemain.spec index 2ee65035..103d1ebb 100644 --- a/packages/pyinstaller/bitmessagemain.spec +++ b/packages/pyinstaller/bitmessagemain.spec @@ -76,7 +76,7 @@ a.datas += [ sql_dir = os.path.join(srcPath, 'sql') a.datas += [ - ('sql', os.path.join(sql_dir, file_), 'DATA') + (os.path.join('sql', file_), os.path.join(sql_dir, file_), 'DATA') for file_ in os.listdir(sql_dir) if file_.endswith('.sql') ] From b3495a1e78751ba4c51c86efa8f580483f7b392c Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Sun, 3 Apr 2022 12:44:29 +0300 Subject: [PATCH 078/424] Don't use libglib2.0 while building appimage and exclude it from the recipe. Closes: #1941 --- packages/AppImage/PyBitmessage.yml | 2 ++ packages/docker/Dockerfile.bionic | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/AppImage/PyBitmessage.yml b/packages/AppImage/PyBitmessage.yml index 4f0c7e3d..3156df90 100644 --- a/packages/AppImage/PyBitmessage.yml +++ b/packages/AppImage/PyBitmessage.yml @@ -16,6 +16,7 @@ ingredients: - python-six - sni-qt exclude: + - libglib2.0-0 - libmng2 - libncursesw5 - libqt4-declarative @@ -31,5 +32,6 @@ ingredients: - ../deb_dist/pybitmessage_*_amd64.deb script: + - rm -rf usr/share/glib-2.0/schemas - cp usr/share/icons/hicolor/scalable/apps/pybitmessage.svg . - mv usr/bin/python2.7 usr/bin/python2 diff --git a/packages/docker/Dockerfile.bionic b/packages/docker/Dockerfile.bionic index e2b7288c..fbb352f9 100644 --- a/packages/docker/Dockerfile.bionic +++ b/packages/docker/Dockerfile.bionic @@ -27,7 +27,6 @@ RUN apt-get install -yq --no-install-suggests --no-install-recommends \ dh-apparmor debhelper dh-python python-msgpack python-qt4 git python-stdeb \ python-all-dev python-crypto python-psutil \ fakeroot python-pytest python3-wheel \ - libglib2.0-dev \ # Code quality pylint python-pycodestyle python3-pycodestyle pycodestyle python-flake8 \ python3-flake8 flake8 python-pyflakes python3-pyflakes pyflakes pyflakes3 \ From 2b629ee0e95d87e43fca0006c984f61d94169393 Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Sat, 28 May 2022 22:53:08 +0300 Subject: [PATCH 079/424] Exclude more unused debs from appimage recipe --- packages/AppImage/PyBitmessage.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/AppImage/PyBitmessage.yml b/packages/AppImage/PyBitmessage.yml index 3156df90..a8948a3c 100644 --- a/packages/AppImage/PyBitmessage.yml +++ b/packages/AppImage/PyBitmessage.yml @@ -16,6 +16,7 @@ ingredients: - python-six - sni-qt exclude: + - libdb5.3 - libglib2.0-0 - libmng2 - libncursesw5 @@ -25,6 +26,7 @@ ingredients: - libqt4-script - libqt4-scripttools - libqt4-sql + - libqt4-test - libqt4-xmlpatterns - libqtassistantclient4 - libreadline7 From 39793dfac871f0c74e0c3b0aa27cc4f9a987cbc0 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Mon, 11 Jul 2022 17:29:27 +0530 Subject: [PATCH 080/424] Add trash query for sent box --- src/helper_sent.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/helper_sent.py b/src/helper_sent.py index 05b17f7f..aa76e756 100644 --- a/src/helper_sent.py +++ b/src/helper_sent.py @@ -59,3 +59,11 @@ def retrieve_message_details(ack_data): "select toaddress, fromaddress, subject, message, received from inbox where msgid = ?", ack_data ) return data + + +def trash(ackdata): + """Mark a message in the `sent` as `trash`""" + rowcount = sqlExecute( + '''UPDATE sent SET folder='trash' WHERE ackdata=?''', ackdata + ) + return rowcount From c203f196a5bea6e6b7b0129a52711248ad8e30fa Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Tue, 30 Nov 2021 16:28:29 +0200 Subject: [PATCH 081/424] A snapcraft recipe. Forced to add description and summary because parse-info didn't work. Used git source because snapcraft expects different directory layout. Closes: #1906 --- packages/snap/snapcraft.yaml | 61 ++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 packages/snap/snapcraft.yaml diff --git a/packages/snap/snapcraft.yaml b/packages/snap/snapcraft.yaml new file mode 100644 index 00000000..16ccd313 --- /dev/null +++ b/packages/snap/snapcraft.yaml @@ -0,0 +1,61 @@ +name: pybitmessage +base: core18 +grade: devel +confinement: strict +summary: Reference client for Bitmessage, a P2P communications protocol +description: | + Bitmessage is a P2P communication protocol used to send encrypted messages to + another person or to many subscribers. It is decentralized and trustless, + meaning that you need-not inherently trust any entities like root certificate + authorities. It uses strong authentication, which means that the sender of a + message cannot be spoofed. BM aims to hide metadata from passive + eavesdroppers like those ongoing warrantless wiretapping programs. Hence + the sender and receiver of Bitmessages stay anonymous. +adopt-info: pybitmessage + +apps: + pybitmessage: + command: desktop-launch pybitmessage + plugs: [desktop, home, network-bind, unity7] + desktop: share/applications/pybitmessage.desktop + passthrough: + autostart: pybitmessage.desktop + +parts: + pybitmessage: + # https://wiki.ubuntu.com/snapcraft/parts + after: [qt4conf, desktop-qt4, indicator-qt4] + source: https://github.com/Bitmessage/PyBitmessage.git + override-pull: | + snapcraftctl pull + snapcraftctl set-version $(git describe --tags --abbrev=0 | tr -d v) + plugin: python + python-version: python2 + build-packages: + - libssl-dev + - python-all-dev + python-packages: + - jsonrpclib + - qrcode + - pyxdg + stage-packages: + - python-qt4 + - python-sip + # parse-info: [setup.py] + cleanup: + after: [pybitmessage] + plugin: nil + override-prime: | + set -eux + sed -ie \ + 's|.*Icon=.*|Icon=${SNAP}/share/icons/hicolor/scalable/apps/pybitmessage.svg|g' \ + $SNAPCRAFT_PRIME/share/applications/pybitmessage.desktop + rm -rf $SNAPCRAFT_PRIME/lib/python2.7/site-packages/pip + for DIR in doc man icons themes fonts mime; do + rm -rf $SNAPCRAFT_PRIME/usr/share/$DIR/* + done + LIBS="libQtDeclarative libQtDesigner libQtHelp libQtScript libQtSql \ + libQtXmlPatterns libdb-5 libicu libgdk libgio libglib libcairo" + for LIBGLOB in $LIBS; do + rm $SNAPCRAFT_PRIME/usr/lib/$SNAPCRAFT_ARCH_TRIPLET/${LIBGLOB}* + done From b257accb816b6bd12f4b7bc8075acb0800079d6f Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Fri, 24 Jun 2022 00:49:38 +0300 Subject: [PATCH 082/424] Add tor --- packages/snap/snapcraft.yaml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/snap/snapcraft.yaml b/packages/snap/snapcraft.yaml index 16ccd313..cc3e54fc 100644 --- a/packages/snap/snapcraft.yaml +++ b/packages/snap/snapcraft.yaml @@ -24,7 +24,7 @@ apps: parts: pybitmessage: # https://wiki.ubuntu.com/snapcraft/parts - after: [qt4conf, desktop-qt4, indicator-qt4] + after: [qt4conf, desktop-qt4, indicator-qt4, tor] source: https://github.com/Bitmessage/PyBitmessage.git override-pull: | snapcraftctl pull @@ -38,10 +38,25 @@ parts: - jsonrpclib - qrcode - pyxdg + - stem stage-packages: - python-qt4 - python-sip # parse-info: [setup.py] + tor: + source: https://dist.torproject.org/tor-0.4.6.9.tar.gz + source-checksum: sha256/c7e93380988ce20b82aa19c06cdb2f10302b72cfebec7c15b5b96bcfc94ca9a9 + source-type: tar + plugin: autotools + build-packages: + - libssl-dev + - zlib1g-dev + after: [libevent] + libevent: + source: https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gz + source-checksum: sha256/92e6de1be9ec176428fd2367677e61ceffc2ee1cb119035037a27d346b0403bb + source-type: tar + plugin: autotools cleanup: after: [pybitmessage] plugin: nil From 7371827a8f44e488ace60ad12016d63fbeedb5a1 Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Tue, 12 Jul 2022 21:17:15 +0300 Subject: [PATCH 083/424] A buildbot scenario for building the snap --- .buildbot/snap/Dockerfile | 7 +++++++ .buildbot/snap/test.sh | 3 +++ 2 files changed, 10 insertions(+) create mode 100644 .buildbot/snap/Dockerfile create mode 100755 .buildbot/snap/test.sh diff --git a/.buildbot/snap/Dockerfile b/.buildbot/snap/Dockerfile new file mode 100644 index 00000000..ba0bfed6 --- /dev/null +++ b/.buildbot/snap/Dockerfile @@ -0,0 +1,7 @@ +FROM ubuntu:bionic + +ENV SKIPCACHE=2022-07-12 + +RUN apt-get update + +RUN apt-get install -yq --no-install-suggests --no-install-recommends snapcraft diff --git a/.buildbot/snap/test.sh b/.buildbot/snap/test.sh new file mode 100755 index 00000000..30d7e72b --- /dev/null +++ b/.buildbot/snap/test.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +cd packages && snapcraft From f720919abd223797c5164d6580a6a9033481988c Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Mon, 11 Jul 2022 21:05:33 +0530 Subject: [PATCH 084/424] Fix telenium_process --- src/bitmessagekivy/tests/telenium_process.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/bitmessagekivy/tests/telenium_process.py b/src/bitmessagekivy/tests/telenium_process.py index f9a397e8..76ce4a92 100644 --- a/src/bitmessagekivy/tests/telenium_process.py +++ b/src/bitmessagekivy/tests/telenium_process.py @@ -81,6 +81,7 @@ class TeleniumTestProcess(TeleniumTestCase): # Finally Sleep is used to make the menu button funcationlly available for the click process. # (because Transition is little bit slow) sleep(0.2) + raise AssertionError("Timeout") def drag(self, xpath1, xpath2): """this method is for dragging""" From aa512d006629c6ece97752346b6a7e4085844ca8 Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Sun, 17 Jul 2022 20:20:57 +0300 Subject: [PATCH 085/424] Call snapcraft in build.sh, not test.sh and bypass cache once more --- .buildbot/snap/Dockerfile | 2 +- .buildbot/snap/{test.sh => build.sh} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename .buildbot/snap/{test.sh => build.sh} (100%) diff --git a/.buildbot/snap/Dockerfile b/.buildbot/snap/Dockerfile index ba0bfed6..7fde093d 100644 --- a/.buildbot/snap/Dockerfile +++ b/.buildbot/snap/Dockerfile @@ -1,6 +1,6 @@ FROM ubuntu:bionic -ENV SKIPCACHE=2022-07-12 +ENV SKIPCACHE=2022-07-17 RUN apt-get update diff --git a/.buildbot/snap/test.sh b/.buildbot/snap/build.sh similarity index 100% rename from .buildbot/snap/test.sh rename to .buildbot/snap/build.sh From a38790da604fc1cb4c5b5b7943cde460a045b0cc Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Sat, 7 May 2022 15:22:18 +0300 Subject: [PATCH 086/424] Cleanup kivy test recipe: - reuse requirements, follow KivyMD doc - remove unneeded packages - explicitly run python3 in test.sh - use pip for installation --- .buildbot/kivy/Dockerfile | 20 +++++--------------- .buildbot/kivy/build.sh | 4 +++- .buildbot/kivy/test.sh | 2 +- kivy-requirements.txt | 4 ++-- 4 files changed, 11 insertions(+), 19 deletions(-) diff --git a/.buildbot/kivy/Dockerfile b/.buildbot/kivy/Dockerfile index 62c3e36d..db06eee8 100644 --- a/.buildbot/kivy/Dockerfile +++ b/.buildbot/kivy/Dockerfile @@ -1,26 +1,16 @@ # A container for buildbot FROM ubuntu:bionic AS kivy -# FROM ubuntu:20.04 AS buildbot-bionic ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update -RUN apt-get -y install sudo - -RUN apt-get install -yq python-setuptools \ - python-setuptools libssl-dev libpq-dev python-prctl python-dev \ - python-virtualenv python-pip virtualenv \ - libjpeg-dev zlib1g-dev python3-dev \ - python3-virtualenv \ - python3-pip \ - wget \ - build-essential libcap-dev libmtdev-dev xvfb xclip git python3-opencv +RUN apt-get install -yq \ + build-essential libcap-dev libssl-dev \ + libmtdev-dev libpq-dev \ + python3-dev python3-pip python3-virtualenv \ + xvfb RUN ln -sf /usr/bin/python3 /usr/bin/python -RUN pip3 install Cython Pillow pyzbar telenium - RUN pip3 install --upgrade setuptools pip - -RUN pip3 install -e git+https://github.com/kivymd/KivyMD#egg=kivymd diff --git a/.buildbot/kivy/build.sh b/.buildbot/kivy/build.sh index f07e5756..afd6b81a 100755 --- a/.buildbot/kivy/build.sh +++ b/.buildbot/kivy/build.sh @@ -1,3 +1,5 @@ #!/bin/sh -python setup.py install --user +pip3 install -r kivy-requirements.txt + +pip3 install . diff --git a/.buildbot/kivy/test.sh b/.buildbot/kivy/test.sh index caccbb9d..d01ca870 100755 --- a/.buildbot/kivy/test.sh +++ b/.buildbot/kivy/test.sh @@ -1,3 +1,3 @@ #!/bin/bash -xvfb-run python tests-kivy.py +xvfb-run python3 tests-kivy.py diff --git a/kivy-requirements.txt b/kivy-requirements.txt index 8d506a5d..c460561b 100644 --- a/kivy-requirements.txt +++ b/kivy-requirements.txt @@ -1,5 +1,5 @@ kivy-garden.qrcode --e git+https://github.com/kivymd/KivyMD#egg=kivymd +https://github.com/kivymd/KivyMD/archive/master.zip opencv-python pyzbar -telenium \ No newline at end of file +telenium From 10cf5b06fda830afbd43e27601c07dcf164ca287 Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Sun, 15 May 2022 18:56:58 +0300 Subject: [PATCH 087/424] Remove obsolete travis config for kivy --- .travis-kivy.yml | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 .travis-kivy.yml diff --git a/.travis-kivy.yml b/.travis-kivy.yml deleted file mode 100644 index 49c99f4d..00000000 --- a/.travis-kivy.yml +++ /dev/null @@ -1,19 +0,0 @@ -language: python3.7 -cache: pip3 -dist: bionic -python: - - "3.7" -addons: - apt: - packages: - - build-essential - - libcap-dev - - libmtdev-dev - - xvfb - - xclip -install: - - pip3 install -r kivy-requirements.txt - - python3 setup.py install - - export PYTHONWARNINGS=all -script: - - xvfb-run python3 tests-kivy.py From ffe70350c756f809e677a127da13a25716a108d5 Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Wed, 20 Jul 2022 03:50:35 +0300 Subject: [PATCH 088/424] Bypass the docker cache --- .buildbot/kivy/Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.buildbot/kivy/Dockerfile b/.buildbot/kivy/Dockerfile index db06eee8..58c2be08 100644 --- a/.buildbot/kivy/Dockerfile +++ b/.buildbot/kivy/Dockerfile @@ -3,6 +3,8 @@ FROM ubuntu:bionic AS kivy ENV DEBIAN_FRONTEND=noninteractive +ENV SKIPCACHE=2022-07-20 + RUN apt-get update RUN apt-get install -yq \ From 0a493eef82851033ddeee85ffbf7c534ef9e2ee5 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Sun, 31 Jan 2021 20:42:34 +0200 Subject: [PATCH 089/424] Started formatting the Protocol Specification: in index.rst README.md is splitted in two parts - References now in the bottom of the page; protocol.rst is the separate file, referenced on top. Closes: #1033 --- docs/index.rst | 17 ++- docs/protocol.rst | 341 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 356 insertions(+), 2 deletions(-) create mode 100644 docs/protocol.rst diff --git a/docs/index.rst b/docs/index.rst index 5e8a1c1a..4e647278 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,7 +1,17 @@ .. mdinclude:: ../README.md + :end-line: 20 -Documentation -------------- +Protocol documentation +---------------------- +.. toctree:: + :maxdepth: 2 + + protocol + encryption + pow + +Code documentation +------------------ .. toctree:: :maxdepth: 3 @@ -14,3 +24,6 @@ Indices and tables * :ref:`genindex` * :ref:`modindex` * :ref:`search` + +.. mdinclude:: ../README.md + :start-line: 21 diff --git a/docs/protocol.rst b/docs/protocol.rst new file mode 100644 index 00000000..5a7045fb --- /dev/null +++ b/docs/protocol.rst @@ -0,0 +1,341 @@ +Protocol specification +====================== + +.. warning:: All objects sent on the network should support protocol v3 + starting on Sun, 16 Nov 2014 22:00:00 GMT. + +.. toctree:: + :maxdepth: 2 + +Common standards +---------------- + +Hashes +^^^^^^ + +Most of the time `SHA-512 `_ hashes are +used, however `RIPEMD-160 `_ is also used +when creating an address. + +A double-round of SHA-512 is used for the Proof Of Work. Example of +double-SHA-512 encoding of string "hello": + +.. highlight:: nasm + +:: + + hello + 9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043(first round of sha-512) + 0592a10584ffabf96539f3d780d776828c67da1ab5b169e9e8aed838aaecc9ed36d49ff1423c55f019e050c66c6324f53588be88894fef4dcffdb74b98e2b200(second round of sha-512) + +For Bitmessage addresses (RIPEMD-160) this would give: + +:: + + hello + 9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043(first round is sha-512) + 79a324faeebcbf9849f310545ed531556882487e (with ripemd-160) + + +Common structures +----------------- + +All integers are encoded in big endian. (This is different from Bitcoin). + +.. list-table:: Message structure + :header-rows: 1 + :widths: auto + + * - Field Size + - Description + - Data type + - Comments + * - 4 + - magic + - uint32_t + - Magic value indicating message origin network, and used to seek to next + message when stream state is unknown + * - 12 + - command + - char[12] + - ASCII string identifying the packet content, NULL padded (non-NULL + padding results in packet rejected) + * - 4 + - length + - uint32_t + - Length of payload in number of bytes. Because of other restrictions, + there is no reason why this length would ever be larger than 1600003 + bytes. Some clients include a sanity-check to avoid processing messages + which are larger than this. + * - 4 + - checksum + - uint32_t + - First 4 bytes of sha512(payload) + * - ? + - message_payload + - uchar[] + - The actual data, a :ref:`message ` or an object_. + Not to be confused with objectPayload. + +Known magic values: + ++-------------+-------------------+ +| Magic value | Sent over wire as | ++=============+===================+ +| 0xE9BEB4D9 | E9 BE B4 D9 | ++-------------+-------------------+ + +.. _varint: + +Variable length integer +^^^^^^^^^^^^^^^^^^^^^^^ + +Integer can be encoded depending on the represented value to save space. +Variable length integers always precede an array/vector of a type of data that +may vary in length. Varints MUST use the minimum possible number of bytes to +encode a value. For example, the value 6 can be encoded with one byte therefore +a varint that uses three bytes to encode the value 6 is malformed and the +decoding task must be aborted. + ++---------------+----------------+------------------------------------------+ +| Value | Storage length | Format | ++===============+================+==========================================+ +| < 0xfd | 1 | uint8_t | ++---------------+----------------+------------------------------------------+ +| <= 0xffff | 3 | 0xfd followed by the integer as uint16_t | ++---------------+----------------+------------------------------------------+ +| <= 0xffffffff | 5 | 0xfe followed by the integer as uint32_t | ++---------------+----------------+------------------------------------------+ +| - | 9 | 0xff followed by the integer as uint64_t | ++---------------+----------------+------------------------------------------+ + +Variable length string +^^^^^^^^^^^^^^^^^^^^^^ + +Variable length string can be stored using a variable length integer followed by +the string itself. + ++------------+-------------+------------+----------------------------------+ +| Field Size | Description | Data type | Comments | ++============+=============+============+==================================+ +| 1+ | length | |var_int| | Length of the string | ++------------+-------------+------------+----------------------------------+ +| ? | string | char[] | The string itself (can be empty) | ++------------+-------------+------------+----------------------------------+ + +Variable length list of integers +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +n integers can be stored using n+1 :ref:`variable length integers ` +where the first var_int equals n. + ++------------+-------------+-----------+----------------------------+ +| Field Size | Description | Data type | Comments | ++============+=============+===========+============================+ +| 1+ | count | |var_int| | Number of var_ints below | ++------------+-------------+-----------+----------------------------+ +| 1+ | | var_int | The first value stored | ++------------+-------------+-----------+----------------------------+ +| 1+ | | var_int | The second value stored... | ++------------+-------------+-----------+----------------------------+ +| 1+ | | var_int | etc... | ++------------+-------------+-----------+----------------------------+ + +.. |var_int| replace:: :ref:`var_int ` + +Network address +^^^^^^^^^^^^^^^ + +When a network address is needed somewhere, this structure is used. Network +addresses are not prefixed with a timestamp or stream in the version_ message. + +.. list-table:: + :header-rows: 1 + :widths: auto + + * - Field Size + - Description + - Data type + - Comments + * - 8 + - time + - uint64 + - the Time. + * - 4 + - stream + - uint32 + - Stream number for this node + * - 8 + - services + - uint64_t + - same service(s) listed in version_ + * - 16 + - IPv6/4 + - char[16] + - IPv6 address. IPv4 addresses are written into the message as a 16 byte + `IPv4-mapped IPv6 address `_ + (12 bytes 00 00 00 00 00 00 00 00 00 00 FF FF, followed by the 4 bytes of + the IPv4 address). + * - 2 + - port + - uint16_t + - port number + +Inventory Vectors +^^^^^^^^^^^^^^^^^ + +Inventory vectors are used for notifying other nodes about objects they have or +data which is being requested. Two rounds of SHA-512 are used, resulting in a +64 byte hash. Only the first 32 bytes are used; the later 32 bytes are ignored. + +Inventory vectors consist of the following data format: + ++------------+-------------+-----------+--------------------+ +| Field Size | Description | Data type | Comments | ++============+=============+===========+====================+ +| 32 | hash | char[32] | Hash of the object | ++------------+-------------+-----------+--------------------+ + +Encrypted payload +^^^^^^^^^^^^^^^^^ + +Bitmessage uses `ECIES `_ to encrypt its messages. For more information see Encryption + ++------------+-------------+-----------+--------------------------------------------+ +| Field Size | Description | Data type | Comments | ++============+=============+===========+============================================+ +| 16 | IV | uchar[] | Initialization Vector used for AES-256-CBC | ++------------+-------------+-----------+--------------------------------------------+ +| 2 | Curve type | uint16_t | Elliptic Curve type 0x02CA (714) | ++------------+-------------+-----------+--------------------------------------------+ +| 2 | X length | uint16_t | Length of X component of public key R | ++------------+-------------+-----------+--------------------------------------------+ +| X length | X | uchar[] | X component of public key R | ++------------+-------------+-----------+--------------------------------------------+ +| 2 | Y length | uint16_t | Length of Y component of public key R | ++------------+-------------+-----------+--------------------------------------------+ +| Y length | Y | uchar[] | Y component of public key R | ++------------+-------------+-----------+--------------------------------------------+ +| ? | encrypted | uchar[] | Cipher text | ++------------+-------------+-----------+--------------------------------------------+ +| 32 | MAC | uchar[] | HMACSHA256 Message Authentication Code | ++------------+-------------+-----------+--------------------------------------------+ + +Unencrypted Message Data +^^^^^^^^^^^^^^^^^^^^^^^^ + + +Message Encodings +""""""""""""""""" + +Pubkey bitfield features +"""""""""""""""""""""""" + + +.. _msg-types: + +Message types +------------- + +Undefined messages received on the wire must be ignored. + +version +^^^^^^^ + +When a node creates an outgoing connection, it will immediately advertise its +version. The remote node will respond with its version. No futher communication +is possible until both peers have exchanged their version. + + +verack +^^^^^^ + +The verack message is sent in reply to version. This message consists of only a +message header with the command string "verack". The TCP timeout starts out at +20 seconds; after verack messages are exchanged, the timeout is raised to +10 minutes. + +If both sides announce that they support SSL, they MUST perform a SSL handshake +immediately after they both send and receive verack. During this SSL handshake, +the TCP client acts as a SSL client, and the TCP server acts as a SSL server. +The current implementation (v0.5.4 or later) requires the AECDH-AES256-SHA +cipher over TLSv1 protocol, and prefers the secp256k1 curve (but other curves +may be accepted, depending on the version of python and OpenSSL used). + +addr +^^^^ + +Provide information on known nodes of the network. Non-advertised nodes should +be forgotten after typically 3 hours + +inv +^^^ + +Allows a node to advertise its knowledge of one or more objects. Payload +(maximum payload length: 50000 items): + + +getdata +^^^^^^^ + +getdata is used in response to an inv message to retrieve the content of a +specific object after filtering known elements. + +Payload (maximum payload length: 50000 entries): + + +object +^^^^^^ + +An object is a message which is shared throughout a stream. It is the only +message which propagates; all others are only between two nodes. Objects have a +type, like 'msg', or 'broadcast'. To be a valid object, the Proof Of Work must +be done. The maximum allowable length of an object (not to be confused with the +objectPayload) is |2^18| bytes. + +.. |2^18| replace:: 2\ :sup:`18`\ + + +Object types +------------ + +Here are the payloads for various object types. + +getpubkey +^^^^^^^^^ + +When a node has the hash of a public key (from an address) but not the public +key itself, it must send out a request for the public key. + + +pubkey +^^^^^^ + +A version 2 pubkey. This is still in use and supported by current clients but +new v2 addresses are not generated by clients. + + +msg +^^^ + +Used for person-to-person messages. Note that msg objects won't contain a +version in the object header until Sun, 16 Nov 2014 22:00:00 GMT. + +broadcast +^^^^^^^^^ + +Users who are subscribed to the sending address will see the message appear in +their inbox. Broadcasts are version 4 or 5. + +Pubkey objects and v5 broadcast objects are encrypted the same way: The data +encoded in the sender's Bitmessage address is hashed twice. The first 32 bytes +of the resulting hash constitutes the "private" encryption key and the last +32 bytes constitute a tag so that anyone listening can easily decide if this +particular message is interesting. The sender calculates the public key from +the private key and then encrypts the object with this public key. Thus anyone +who knows the Bitmessage address of the sender of a broadcast or pubkey object +can decrypt it. + +The version of broadcast objects was previously 2 or 3 but was changed to 4 or +5 for protocol v3. Having a broadcast version of 5 indicates that a tag is used +which, in turn, is used when the sender's address version is >=4. From c0657f61dd5f8d3b3096057bb8dbe7485eb409f1 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Sun, 31 Jan 2021 20:42:34 +0200 Subject: [PATCH 090/424] Update docs copyright line --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index e3eef6b3..0e634eb0 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -19,7 +19,7 @@ import version # noqa:E402 # -- Project information ----------------------------------------------------- project = u'PyBitmessage' -copyright = u'2019, The Bitmessage Team' # pylint: disable=redefined-builtin +copyright = u'2019-2022, The Bitmessage Team' # pylint: disable=redefined-builtin author = u'The Bitmessage Team' # The short X.Y version From 2b692885407f5969146db2c8969afb9431c76cde Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Sun, 31 Jan 2021 20:42:35 +0200 Subject: [PATCH 091/424] Added PoW doc, separate pow_formula.rst and reference in protocol.rst --- docs/pow.rst | 77 ++++++++++++++++++++++++++++++++++++++++++++ docs/pow_formula.rst | 7 ++++ docs/protocol.rst | 47 +++++++++++++++++++++++++-- 3 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 docs/pow.rst create mode 100644 docs/pow_formula.rst diff --git a/docs/pow.rst b/docs/pow.rst new file mode 100644 index 00000000..3786b075 --- /dev/null +++ b/docs/pow.rst @@ -0,0 +1,77 @@ +Proof of work +============= + +This page describes Bitmessage's Proof of work ("POW") mechanism as it exists in +Protocol Version 3. In this document, hash() means SHA512(). SHA512 was chosen +as it is widely supported and so that Bitcoin POW hardware cannot trivially be +used for Bitmessage POWs. The author acknowledges that they are essentially the +same algorithm with a different key size. + +Both ``averageProofOfWorkNonceTrialsPerByte`` and ``payloadLengthExtraBytes`` +are set by the owner of a Bitmessage address. The default and minimum for each +is 1000. (This is the same as difficulty 1. If the difficulty is 2, then this +value is 2000). The purpose of ``payloadLengthExtraBytes`` is to add some extra +weight to small messages. + +Do a POW +-------- + +Let us use a ``msg`` message as an example:: + + payload = embeddedTime + encodedObjectVersion + encodedStreamNumber + encrypted + +``payloadLength`` + the length of payload, in bytes, + 8 + (to account for the nonce which we will append later) +``TTL`` + the number of seconds in between now and the object expiresTime. + +.. include:: pow_formula.rst + +:: + + initialHash = hash(payload) + +start with ``trialValue = 99999999999999999999`` + +also start with ``nonce = 0`` where nonce is 8 bytes in length and can be +hashed as if it is a string. + +:: + + while trialValue > target: + nonce = nonce + 1 + resultHash = hash(hash( nonce || initialHash )) + trialValue = the first 8 bytes of resultHash, converted to an integer + +When this loop finishes, you will have your 8 byte nonce value which you can +prepend onto the front of the payload. The message is then ready to send. + +Check a POW +----------- + +Let us assume that ``payload`` contains the payload for a msg message (the nonce +down through the encrypted message data). + +``nonce`` + the first 8 bytes of payload +``dataToCheck`` + the ninth byte of payload on down (thus it is everything except the nonce) + +:: + + initialHash = hash(dataToCheck) + + resultHash = hash(hash( nonce || initialHash )) + +``POWValue`` + the first eight bytes of resultHash converted to an integer +``TTL`` + the number of seconds in between now and the object ``expiresTime``. + +.. include:: pow_formula.rst + +If ``POWValue`` is less than or equal to ``target``, then the POW check passes. + + + diff --git a/docs/pow_formula.rst b/docs/pow_formula.rst new file mode 100644 index 00000000..16c3f174 --- /dev/null +++ b/docs/pow_formula.rst @@ -0,0 +1,7 @@ + +.. math:: + + target = \frac{2^{64}}{{\displaystyle + nonceTrialsPerByte (payloadLength + payloadLengthExtraBytes + \frac{ + TTL (payloadLength + payloadLengthExtraBytes)}{2^{16}}) + }} diff --git a/docs/protocol.rst b/docs/protocol.rst index 5a7045fb..c814ecaf 100644 --- a/docs/protocol.rst +++ b/docs/protocol.rst @@ -289,12 +289,53 @@ object An object is a message which is shared throughout a stream. It is the only message which propagates; all others are only between two nodes. Objects have a -type, like 'msg', or 'broadcast'. To be a valid object, the Proof Of Work must -be done. The maximum allowable length of an object (not to be confused with the -objectPayload) is |2^18| bytes. +type, like 'msg', or 'broadcast'. To be a valid object, the +:doc:`pow` must be done. The maximum allowable length of an object +(not to be confused with the ``objectPayload``) is |2^18| bytes. .. |2^18| replace:: 2\ :sup:`18`\ +.. list-table:: Message structure + :header-rows: 1 + :widths: auto + + * - Field Size + - Description + - Data type + - Comments + * - 8 + - nonce + - uint64_t + - Random nonce used for the :doc:`pow` + * - 8 + - expiresTime + - uint64_t + - The "end of life" time of this object (be aware, in version 2 of the + protocol this was the generation time). Objects shall be shared with + peers until its end-of-life time has been reached. The node should store + the inventory vector of that object for some extra period of time to + avoid reloading it from another node with a small time delay. The time + may be no further than 28 days + 3 hours in the future. + * - 4 + - objectType + - uint32_t + - Four values are currently defined: 0-"getpubkey", 1-"pubkey", 2-"msg", + 3-"broadcast". All other values are reserved. Nodes should relay objects + even if they use an undefined object type. + * - 1+ + - version + - var_int + - The object's version. Note that msg objects won't contain a version + until Sun, 16 Nov 2014 22:00:00 GMT. + * - 1+ + - stream number + - var_int + - The stream number in which this object may propagate + * - ? + - objectPayload + - uchar[] + - This field varies depending on the object type; see below. + Object types ------------ From d88e1301e2e97360dc28d344ae11205fa9632f88 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Sun, 31 Jan 2021 20:42:35 +0200 Subject: [PATCH 092/424] encrypted_payload.rst and encryption.rst docs, crossreferences --- docs/encrypted_payload.rst | 19 +++ docs/encryption.rst | 232 +++++++++++++++++++++++++++++++++++++ docs/protocol.rst | 22 +--- 3 files changed, 253 insertions(+), 20 deletions(-) create mode 100644 docs/encrypted_payload.rst create mode 100644 docs/encryption.rst diff --git a/docs/encrypted_payload.rst b/docs/encrypted_payload.rst new file mode 100644 index 00000000..346d370d --- /dev/null +++ b/docs/encrypted_payload.rst @@ -0,0 +1,19 @@ ++------------+-------------+-----------+--------------------------------------------+ +| Field Size | Description | Data type | Comments | ++============+=============+===========+============================================+ +| 16 | IV | uchar[] | Initialization Vector used for AES-256-CBC | ++------------+-------------+-----------+--------------------------------------------+ +| 2 | Curve type | uint16_t | Elliptic Curve type 0x02CA (714) | ++------------+-------------+-----------+--------------------------------------------+ +| 2 | X length | uint16_t | Length of X component of public key R | ++------------+-------------+-----------+--------------------------------------------+ +| X length | X | uchar[] | X component of public key R | ++------------+-------------+-----------+--------------------------------------------+ +| 2 | Y length | uint16_t | Length of Y component of public key R | ++------------+-------------+-----------+--------------------------------------------+ +| Y length | Y | uchar[] | Y component of public key R | ++------------+-------------+-----------+--------------------------------------------+ +| ? | encrypted | uchar[] | Cipher text | ++------------+-------------+-----------+--------------------------------------------+ +| 32 | MAC | uchar[] | HMACSHA256 Message Authentication Code | ++------------+-------------+-----------+--------------------------------------------+ diff --git a/docs/encryption.rst b/docs/encryption.rst new file mode 100644 index 00000000..925dd001 --- /dev/null +++ b/docs/encryption.rst @@ -0,0 +1,232 @@ +Encryption +========== + +Bitmessage uses the Elliptic Curve Integrated Encryption Scheme +`(ECIES) `_ +to encrypt the payload of the Message and Broadcast objects. + +The scheme uses Elliptic Curve Diffie-Hellman +`(ECDH) `_ to generate a shared secret used +to generate the encryption parameters for Advanced Encryption Standard with +256bit key and Cipher-Block Chaining +`(AES-256-CBC) `_. +The encrypted data will be padded to a 16 byte boundary in accordance to +`PKCS7 `_. This +means that the data is padded with N bytes of value N. + +The Key Derivation Function +`(KDF) `_ used to +generate the key material for AES is +`SHA512 `_. The Message Authentication +Code (MAC) scheme used is `HMACSHA256 `_. + +Format +------ + +(See also: :doc:`protocol`) + +.. include:: encrypted_payload.rst + +In order to reconstitute a usable (65 byte) public key (starting with 0x04), +the X and Y components need to be expanded by prepending them with 0x00 bytes +until the individual component lengths are 32 bytes. + +Encryption +---------- + + 1. The destination public key is called K. + 2. Generate 16 random bytes using a secure random number generator. + Call them IV. + 3. Generate a new random EC key pair with private key called r and public key + called R. + 4. Do an EC point multiply with public key K and private key r. This gives you + public key P. + 5. Use the X component of public key P and calculate the SHA512 hash H. + 6. The first 32 bytes of H are called key_e and the last 32 bytes are called + key_m. + 7. Pad the input text to a multiple of 16 bytes, in accordance to PKCS7. + 8. Encrypt the data with AES-256-CBC, using IV as initialization vector, + key_e as encryption key and the padded input text as payload. Call the + output cipher text. + 9. Calculate a 32 byte MAC with HMACSHA256, using key_m as salt and + IV + R + cipher text as data. Call the output MAC. + +The resulting data is: IV + R + cipher text + MAC + +Decryption +---------- + + 1. The private key used to decrypt is called k. + 2. Do an EC point multiply with private key k and public key R. This gives you + public key P. + 3. Use the X component of public key P and calculate the SHA512 hash H. + 4. The first 32 bytes of H are called key_e and the last 32 bytes are called + key_m. + 5. Calculate MAC' with HMACSHA256, using key_m as salt and + IV + R + cipher text as data. + 6. Compare MAC with MAC'. If not equal, decryption will fail. + 7. Decrypt the cipher text with AES-256-CBC, using IV as initialization + vector, key_e as decryption key and the cipher text as payload. The output + is the padded input text. + +.. highlight:: nasm + +Partial Example +--------------- + +.. list-table:: Public key K: + :header-rows: 1 + :widths: auto + + * - Data + - Comments + * - + + :: + + 04 09 d4 e5 c0 ab 3d 25 + fe 04 8c 64 c9 da 1a 24 + 2c 7f 19 41 7e 95 17 cd + 26 69 50 d7 2c 75 57 13 + 58 5c 61 78 e9 7f e0 92 + fc 89 7c 9a 1f 17 20 d5 + 77 0a e8 ea ad 2f a8 fc + bd 08 e9 32 4a 5d de 18 + 57 + - Public key, 0x04 prefix, then 32 bytes X and 32 bytes Y. + + +.. list-table:: Initialization Vector IV: + :header-rows: 1 + :widths: auto + + * - Data + - Comments + * - + + :: + + bd db 7c 28 29 b0 80 38 + 75 30 84 a2 f3 99 16 81 + - 16 bytes generated with a secure random number generator. + +.. list-table:: Randomly generated key pair with private key r and public key R: + :header-rows: 1 + :widths: auto + + * - Data + - Comments + * - + + :: + + 5b e6 fa cd 94 1b 76 e9 + d3 ea d0 30 29 fb db 6b + 6e 08 09 29 3f 7f b1 97 + d0 c5 1f 84 e9 6b 8b a4 + - Private key r + * - + + :: + + 04 02 93 21 3d cf 13 88 + b6 1c 2a e5 cf 80 fe e6 + ff ff c0 49 a2 f9 fe 73 + 65 fe 38 67 81 3c a8 12 + 92 df 94 68 6c 6a fb 56 + 5a c6 14 9b 15 3d 61 b3 + b2 87 ee 2c 7f 99 7c 14 + 23 87 96 c1 2b 43 a3 86 + 5a + - Public key R + +.. list-table:: Derived public key P (point multiply r with K): + :header-rows: 1 + :widths: auto + + * - Data + - Comments + * - + + :: + + 04 0d b8 e3 ad 8c 0c d7 + 3f a2 b3 46 71 b7 b2 47 + 72 9b 10 11 41 57 9d 19 + 9e 0d c0 bd 02 4e ae fd + 89 ca c8 f5 28 dc 90 b6 + 68 11 ab ac 51 7d 74 97 + be 52 92 93 12 29 be 0b + 74 3e 05 03 f4 43 c3 d2 + 96 + - Public key P + * - + + :: + + 0d b8 e3 ad 8c 0c d7 3f + a2 b3 46 71 b7 b2 47 72 + 9b 10 11 41 57 9d 19 9e + 0d c0 bd 02 4e ae fd 89 + - X component of public key P + +.. list-table:: SHA512 of public key P X component (H): + :header-rows: 1 + :widths: auto + + * - Data + - Comments + * - + + :: + + 17 05 43 82 82 67 86 71 + 05 26 3d 48 28 ef ff 82 + d9 d5 9c bf 08 74 3b 69 + 6b cc 5d 69 fa 18 97 b4 + - First 32 bytes of H called key_e + * - + + :: + + f8 3f 1e 9c c5 d6 b8 44 + 8d 39 dc 6a 9d 5f 5b 7f + 46 0e 4a 78 e9 28 6e e8 + d9 1c e1 66 0a 53 ea cd + - Last 32 bytes of H called key_m + +.. list-table:: Padded input: + :header-rows: 1 + :widths: auto + + * - Data + - Comments + * - + + :: + + 54 68 65 20 71 75 69 63 + 6b 20 62 72 6f 77 6e 20 + 66 6f 78 20 6a 75 6d 70 + 73 20 6f 76 65 72 20 74 + 68 65 20 6c 61 7a 79 20 + 64 6f 67 2e 04 04 04 04 + - The quick brown fox jumps over the lazy dog.0x04,0x04,0x04,0x04 + +.. list-table:: Cipher text: + :header-rows: 1 + :widths: auto + + * - Data + - Comments + * - + + :: + + 64 20 3d 5b 24 68 8e 25 + 47 bb a3 45 fa 13 9a 5a + 1d 96 22 20 d4 d4 8a 0c + f3 b1 57 2c 0d 95 b6 16 + 43 a6 f9 a0 d7 5a f7 ea + cc 1b d9 57 14 7b f7 23 + - 3 blocks of 16 bytes of encrypted data. diff --git a/docs/protocol.rst b/docs/protocol.rst index c814ecaf..2dc4c1bc 100644 --- a/docs/protocol.rst +++ b/docs/protocol.rst @@ -199,27 +199,9 @@ Inventory vectors consist of the following data format: Encrypted payload ^^^^^^^^^^^^^^^^^ -Bitmessage uses `ECIES `_ to encrypt its messages. For more information see Encryption +Bitmessage uses `ECIES `_ to encrypt its messages. For more information see :doc:`encryption` -+------------+-------------+-----------+--------------------------------------------+ -| Field Size | Description | Data type | Comments | -+============+=============+===========+============================================+ -| 16 | IV | uchar[] | Initialization Vector used for AES-256-CBC | -+------------+-------------+-----------+--------------------------------------------+ -| 2 | Curve type | uint16_t | Elliptic Curve type 0x02CA (714) | -+------------+-------------+-----------+--------------------------------------------+ -| 2 | X length | uint16_t | Length of X component of public key R | -+------------+-------------+-----------+--------------------------------------------+ -| X length | X | uchar[] | X component of public key R | -+------------+-------------+-----------+--------------------------------------------+ -| 2 | Y length | uint16_t | Length of Y component of public key R | -+------------+-------------+-----------+--------------------------------------------+ -| Y length | Y | uchar[] | Y component of public key R | -+------------+-------------+-----------+--------------------------------------------+ -| ? | encrypted | uchar[] | Cipher text | -+------------+-------------+-----------+--------------------------------------------+ -| 32 | MAC | uchar[] | HMACSHA256 Message Authentication Code | -+------------+-------------+-----------+--------------------------------------------+ +.. include:: encrypted_payload.rst Unencrypted Message Data ^^^^^^^^^^^^^^^^^^^^^^^^ From c9c91b3e7a552eeeb2e7d48fc3fc0bd26b705997 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Sun, 31 Jan 2021 20:42:35 +0200 Subject: [PATCH 093/424] Message Encodings section and extended_encoding doc --- docs/extended_encoding.rst | 55 ++++++++++++++++++++++++++++++++++++++ docs/protocol.rst | 28 +++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 docs/extended_encoding.rst diff --git a/docs/extended_encoding.rst b/docs/extended_encoding.rst new file mode 100644 index 00000000..25539ad4 --- /dev/null +++ b/docs/extended_encoding.rst @@ -0,0 +1,55 @@ +Extended encoding +================= + +Extended encoding is an attempt to create a standard for transmitting structured +data. The goals are flexibility, wide platform support and extensibility. It is +currently available in the v0.6 branch and can be enabled by holding "Shift" +while clicking on Send. It is planned that v5 addresses will have to support +this. It's a work in progress, the basic plain text message works but don't +expect anthing else at this time. + +The data structure is in msgpack, then compressed with zlib. The top level is +a key/value store, and the "" key (empty string) contains the value of the type +of object, which can then have its individual format and standards. + +Text fields are encoded using UTF-8. + +Types +----- + +You can find the implementations in the ``src/messagetypes`` directory of +PyBitmessage. Each type has its own file which includes one class, and they are +dynamically loaded on startup. It's planned that this will also contain +initialisation, rendering and so on, so that developers can simply add a new +object type by adding a single file in the messagetypes directory and not have +to change any other part of the code. + +message +^^^^^^^ + +The replacement for the old messages. Mandatory keys are ``body`` and +``subject``, others are currently not implemented and not mandatory. Proposed +other keys: + +``parents``: + array of msgids referring to messages that logically precede it in a + conversation. Allows to create a threaded conversation view + +``files``: + array of files (which is a key/value pair): + + ``name``: + file name, mandatory + ``data``: + the binary data of the file + ``type``: + MIME content type + ``disposition``: + MIME content disposition, possible values are "inline" and "attachment" + +vote +^^^^ + +Dummy code available in the repository. Supposed to serve voting in a chan +(thumbs up/down) for decentralised moderation. Does not actually do anything at +the moment and specification can change. diff --git a/docs/protocol.rst b/docs/protocol.rst index 2dc4c1bc..a9134325 100644 --- a/docs/protocol.rst +++ b/docs/protocol.rst @@ -210,6 +210,34 @@ Unencrypted Message Data Message Encodings """"""""""""""""" +.. list-table:: + :header-rows: 1 + :widths: auto + + * - Value + - Name + - Description + * - 0 + - IGNORE + - Any data with this number may be ignored. The sending node might simply + be sharing its public key with you. + * - 1 + - TRIVIAL + - UTF-8. No 'Subject' or 'Body' sections. Useful for simple strings + of data, like URIs or magnet links. + * - 2 + - SIMPLE + - UTF-8. Uses 'Subject' and 'Body' sections. No MIME is used. + :: + messageToTransmit = 'Subject:' + subject + '\n' + 'Body:' + message + * - 3 + - EXTENDED + - See :doc:`extended_encoding` + +Further values for the message encodings can be decided upon by the community. +Any MIME or MIME-like encoding format, should they be used, should make use of +Bitmessage's 8-bit bytes. + Pubkey bitfield features """""""""""""""""""""""" From 990aa36f5c50d4f0780c88fefff22dc18e1055da Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Sun, 31 Jan 2021 20:42:35 +0200 Subject: [PATCH 094/424] More text and useragent doc --- docs/protocol.rst | 116 +++++++++++++++++++++++++++++++++++++++++++++ docs/useragent.rst | 53 +++++++++++++++++++++ 2 files changed, 169 insertions(+) create mode 100644 docs/useragent.rst diff --git a/docs/protocol.rst b/docs/protocol.rst index a9134325..d74c09a3 100644 --- a/docs/protocol.rst +++ b/docs/protocol.rst @@ -241,6 +241,47 @@ Bitmessage's 8-bit bytes. Pubkey bitfield features """""""""""""""""""""""" +.. list-table:: + :header-rows: 1 + :widths: auto + + * - Bit + - Name + - Description + * - 0 + - undefined + - The most significant bit at the beginning of the structure. Undefined + * - 1 + - undefined + - The next most significant bit. Undefined + * - ... + - ... + - ... + * - 27 + - onion_router + - (**Proposal**) Node can be used to onion-route messages. In theory any + node can onion route, but since it requires more resources, they may have + the functionality disabled. This field will be used to indicate that the + node is willing to do this. + * - 28 + - forward_secrecy + - (**Proposal**) Receiving node supports a forward secrecy encryption + extension. The exact design is pending. + * - 29 + - chat + - (**Proposal**) Address if for chatting rather than messaging. + * - 30 + - include_destination + - (**Proposal**) Receiving node expects that the RIPE hash encoded in their + address preceedes the encrypted message data of msg messages bound for + them. + + .. note:: since hardly anyone implements this, this will be redesigned as + `simple recipient verification `_ + * - 31 + - does_ack + - If true, the receiving node does send acknowledgements (rather than + dropping them). .. _msg-types: @@ -256,6 +297,81 @@ When a node creates an outgoing connection, it will immediately advertise its version. The remote node will respond with its version. No futher communication is possible until both peers have exchanged their version. +.. list-table:: Payload + :header-rows: 1 + :widths: auto + + * - Field Size + - Description + - Data type + - Comments + * - 4 + - version + - int32_t + - Identifies protocol version being used by the node. Should equal 3. + Nodes should disconnect if the remote node's version is lower but + continue with the connection if it is higher. + * - 8 + - services + - uint64_t + - bitfield of features to be enabled for this connection + * - 8 + - timestamp + - int64_t + - standard UNIX timestamp in seconds + * - 26 + - addr_recv + - net_addr + - The network address of the node receiving this message (not including the + time or stream number) + * - 26 + - addr_from + - net_addr + - The network address of the node emitting this message (not including the + time or stream number and the ip itself is ignored by the receiver) + * - 8 + - nonce + - uint64_t + - Random nonce used to detect connections to self. + * - 1+ + - user_agent + - var_str + - :doc:`useragent` (0x00 if string is 0 bytes long). Sending nodes must not + include a user_agent longer than 5000 bytes. + * - 1+ + - stream_numbers + - var_int_list + - The stream numbers that the emitting node is interested in. Sending nodes + must not include more than 160000 stream numbers. + +A "verack" packet shall be sent if the version packet was accepted. Once you +have sent and received a verack messages with the remote node, send an addr +message advertising up to 1000 peers of which you are aware, and one or more +inv messages advertising all of the valid objects of which you are aware. + +.. list-table:: The following services are currently assigned + :header-rows: 1 + :widths: auto + + * - Value + - Name + - Description + * - 1 + - NODE_NETWORK + - This is a normal network node. + * - 2 + - NODE_SSL + - This node supports SSL/TLS in the current connect (python < 2.7.9 only + supports a SSL client, so in that case it would only have this on when + the connection is a client). + * - 3 + - NODE_POW + - (**Proposal**) This node may do PoW on behalf of some its peers (PoW + offloading/delegating), but it doesn't have to. Clients may have to meet + additional requirements (e.g. TLS authentication) + * - 4 + - NODE_DANDELION + - Node supports `dandelion `_ verack ^^^^^^ diff --git a/docs/useragent.rst b/docs/useragent.rst new file mode 100644 index 00000000..3523a274 --- /dev/null +++ b/docs/useragent.rst @@ -0,0 +1,53 @@ +User Agent +========== + +Bitmessage user agents are a modified browser user agent with more structure +to aid parsers and provide some coherence. The user agent strings are arranged +in a stack with the most underlying software listed first. + +Basic format:: + + /Name:Version/Name:Version/.../ + +Example:: + + /PyBitmessage:0.2.2/Corporate Mail System:0.8/ + /Surdo:5.64/surdo-qt:0.4/ + +The version numbers are not defined to any strict format, although this guide +recommends: + + * Version numbers in the form of Major.Minor.Revision (2.6.41) + * Repository builds using a date in the format of YYYYMMDD (20110128) + +For git repository builds, implementations are free to use the git commitish. +However the issue lies in that it is not immediately obvious without the +repository which version preceeds another. For this reason, we lightly +recommend dates in the format specified above, although this is by no means +a requirement. + +Optional ``-r1``, ``-r2``, ... can be appended to user agent version numbers. +This is another light recommendation, but not a requirement. Implementations +are free to specify version numbers in whatever format needed insofar as it +does not include ``(``, ``)``, ``:`` or ``/`` to interfere with the user agent +syntax. + +An optional comments field after the version number is also allowed. Comments +should be delimited by parenthesis ``(...)``. The contents of comments is +entirely implementation defined although this document recommends the use of +semi-colons ``;`` as a delimiter between pieces of information. + +Example:: + + /cBitmessage:0.2(iPad; U; CPU OS 3_2_1)/AndroidBuild:0.8/ + +Reserved symbols are therefore: ``/ : ( )`` + +They should not be misused beyond what is specified in this section. + +``/`` + separates the code-stack +``:`` + specifies the implementation version of the particular stack +``( and )`` + delimits a comment which optionally separates data using ``;`` From d273e64b7674edde7c7e6b6c392259b382760661 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Sun, 31 Jan 2021 21:37:25 +0200 Subject: [PATCH 095/424] Finish message types --- docs/protocol.rst | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/protocol.rst b/docs/protocol.rst index d74c09a3..3c159565 100644 --- a/docs/protocol.rst +++ b/docs/protocol.rst @@ -394,12 +394,30 @@ addr Provide information on known nodes of the network. Non-advertised nodes should be forgotten after typically 3 hours +Payload: + ++------------+-------------+-----------+---------------------------------------+ +| Field Size | Description | Data type | Comments | ++============+=============+===========+=======================================+ +| 1+ | count | |var_int| | Number of address entries (max: 1000) | ++------------+-------------+-----------+---------------------------------------+ +| 38 | addr_list | net_addr | Address of other nodes on the network.| ++------------+-------------+-----------+---------------------------------------+ + inv ^^^ Allows a node to advertise its knowledge of one or more objects. Payload (maximum payload length: 50000 items): ++------------+-------------+------------+-----------------------------+ +| Field Size | Description | Data type | Comments | ++============+=============+============+=============================+ +| ? | count | |var_int| | Number of inventory entries | ++------------+-------------+------------+-----------------------------+ +| 32x? | inventory | inv_vect[] | Inventory vectors | ++------------+-------------+------------+-----------------------------+ + getdata ^^^^^^^ @@ -409,6 +427,14 @@ specific object after filtering known elements. Payload (maximum payload length: 50000 entries): ++------------+-------------+------------+-----------------------------+ +| Field Size | Description | Data type | Comments | ++============+=============+============+=============================+ +| ? | count | |var_int| | Number of inventory entries | ++------------+-------------+------------+-----------------------------+ +| 32x? | inventory | inv_vect[] | Inventory vectors | ++------------+-------------+------------+-----------------------------+ + object ^^^^^^ From 08f00d7d6a84021ef0d962299305fcb6d0429edc Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Sun, 31 Jan 2021 21:50:50 +0200 Subject: [PATCH 096/424] More object types --- docs/protocol.rst | 190 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 189 insertions(+), 1 deletion(-) diff --git a/docs/protocol.rst b/docs/protocol.rst index 3c159565..967ab363 100644 --- a/docs/protocol.rst +++ b/docs/protocol.rst @@ -500,13 +500,201 @@ getpubkey When a node has the hash of a public key (from an address) but not the public key itself, it must send out a request for the public key. +.. list-table:: + :header-rows: 1 + :widths: auto + + * - Field Size + - Description + - Data type + - Comments + * - 20 + - ripe + - uchar[] + - The ripemd hash of the public key. This field is only included when the + address version is <= 3. + * - 32 + - tag + - uchar[] + - The tag derived from the address version, stream number, and ripe. This + field is only included when the address version is >= 4. pubkey ^^^^^^ A version 2 pubkey. This is still in use and supported by current clients but -new v2 addresses are not generated by clients. +*new* v2 addresses are not generated by clients. +.. list-table:: + :header-rows: 1 + :widths: auto + + * - Field Size + - Description + - Data type + - Comments + * - 4 + - behavior bitfield + - uint32_t + - A bitfield of optional behaviors and features that can be expected from + the node receiving the message. + * - 64 + - public signing key + - uchar[] + - The ECC public key used for signing (uncompressed format; + normally prepended with \x04 ) + * - 64 + - public encryption key + - uchar[] + - The ECC public key used for encryption (uncompressed format; + normally prepended with \x04 ) + +.. list-table:: A version 3 pubkey + :header-rows: 1 + :widths: auto + + * - Field Size + - Description + - Data type + - Comments + * - 4 + - behavior bitfield + - uint32_t + - A bitfield of optional behaviors and features that can be expected from + the node receiving the message. + * - 64 + - public signing key + - uchar[] + - The ECC public key used for signing (uncompressed format; + normally prepended with \x04 ) + * - 64 + - public encryption key + - uchar[] + - The ECC public key used for encryption (uncompressed format; + normally prepended with \x04 ) + * - 1+ + - nonce_trials_per_byte + - var_int + - Used to calculate the difficulty target of messages accepted by this + node. The higher this value, the more difficult the Proof of Work must + be before this individual will accept the message. This number is the + average number of nonce trials a node will have to perform to meet the + Proof of Work requirement. 1000 is the network minimum so any lower + values will be automatically raised to 1000. + * - 1+ + - extra_bytes + - var_int + - Used to calculate the difficulty target of messages accepted by this + node. The higher this value, the more difficult the Proof of Work must + be before this individual will accept the message. This number is added + to the data length to make sending small messages more difficult. + 1000 is the network minimum so any lower values will be automatically + raised to 1000. + * - 1+ + - sig_length + - var_int + - Length of the signature + * - sig_length + - signature + - uchar[] + - The ECDSA signature which, as of protocol v3, covers the object + header starting with the time, appended with the data described in this + table down to the extra_bytes. + +.. list-table:: A version 4 pubkey + :header-rows: 1 + :widths: auto + + * - Field Size + - Description + - Data type + - Comments + * - 32 + - tag + - uchar[] + - The tag, made up of bytes 32-64 of the double hash of the address data + (see example python code below) + * - ? + - encrypted + - uchar[] + - Encrypted pubkey data. + +When version 4 pubkeys are created, most of the data in the pubkey is encrypted. +This is done in such a way that only someone who has the Bitmessage address +which corresponds to a pubkey can decrypt and use that pubkey. This prevents +people from gathering pubkeys sent around the network and using the data from +them to create messages to be used in spam or in flooding attacks. + +In order to encrypt the pubkey data, a double SHA-512 hash is calculated from +the address version number, stream number, and ripe hash of the Bitmessage +address that the pubkey corresponds to. The first 32 bytes of this hash are used +to create a public and private key pair with which to encrypt and decrypt the +pubkey data, using the same algorithm as message encryption +(see :doc:`encryption`). The remaining 32 bytes of this hash are added to the +unencrypted part of the pubkey and used as a tag, as above. This allows nodes to +determine which pubkey to decrypt when they wish to send a message. + +In PyBitmessage, the double hash of the address data is calculated using the +python code below: + +.. code-block:: python + + doubleHashOfAddressData = hashlib.sha512(hashlib.sha512( + encodeVarint(addressVersionNumber) + encodeVarint(streamNumber) + hash + ).digest()).digest() + + +.. list-table:: Encrypted data in version 4 pubkeys: + :header-rows: 1 + :widths: auto + + * - Field Size + - Description + - Data type + - Comments + * - 4 + - behavior bitfield + - uint32_t + - A bitfield of optional behaviors and features that can be expected from + the node receiving the message. + * - 64 + - public signing key + - uchar[] + - The ECC public key used for signing (uncompressed format; + normally prepended with \x04 ) + * - 64 + - public encryption key + - uchar[] + - The ECC public key used for encryption (uncompressed format; + normally prepended with \x04 ) + * - 1+ + - nonce_trials_per_byte + - var_int + - Used to calculate the difficulty target of messages accepted by this + node. The higher this value, the more difficult the Proof of Work must + be before this individual will accept the message. This number is the + average number of nonce trials a node will have to perform to meet the + Proof of Work requirement. 1000 is the network minimum so any lower + values will be automatically raised to 1000. + * - 1+ + - extra_bytes + - var_int + - Used to calculate the difficulty target of messages accepted by this + node. The higher this value, the more difficult the Proof of Work must + be before this individual will accept the message. This number is added + to the data length to make sending small messages more difficult. + 1000 is the network minimum so any lower values will be automatically + raised to 1000. + * - 1+ + - sig_length + - var_int + - Length of the signature + * - sig_length + - signature + - uchar[] + - The ECDSA signature which covers everything from the object header + starting with the time, then appended with the decrypted data down to + the extra_bytes. This was changed in protocol v3. msg ^^^ From f5ba7a6dc0ddd12173ed072b8579cca25f9ae89e Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Tue, 9 Mar 2021 18:45:10 +0200 Subject: [PATCH 097/424] Unencrypted Message Data section, msg object type and refs --- docs/protocol.rst | 143 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 138 insertions(+), 5 deletions(-) diff --git a/docs/protocol.rst b/docs/protocol.rst index 967ab363..137b1b95 100644 --- a/docs/protocol.rst +++ b/docs/protocol.rst @@ -206,6 +206,103 @@ Bitmessage uses `ECIES = 3**. + * - 1+ + - extra_bytes + - var_int + - Used to calculate the difficulty target of messages accepted by this + node. The higher this value, the more difficult the Proof of Work must + be before this individual will accept the message. This number is added + to the data length to make sending small messages more difficult. + 1000 is the network minimum so any lower values will be automatically + raised to 1000. **This field is new and is only included when the + address_version >= 3**. + * - 20 + - destination ripe + - uchar[] + - The ripe hash of the public key of the receiver of the message + * - 1+ + - encoding + - var_int + - :ref:`Message Encoding ` type + * - 1+ + - message_length + - var_int + - Message Length + * - message_length + - message + - uchar[] + - The message. + * - 1+ + - ack_length + - var_int + - Length of the acknowledgement data + * - ack_length + - ack_data + - uchar[] + - The acknowledgement data to be transmitted. This takes the form of a + Bitmessage protocol message, like another msg message. The POW therein + must already be completed. + * - 1+ + - sig_length + - var_int + - Length of the signature + * - sig_length + - signature + - uchar[] + - The ECDSA signature which covers the object header starting with the + time, appended with the data described in this table down to the + ack_data. + +.. _msg-encodings: Message Encodings """"""""""""""""" @@ -702,6 +799,20 @@ msg Used for person-to-person messages. Note that msg objects won't contain a version in the object header until Sun, 16 Nov 2014 22:00:00 GMT. +.. list-table:: + :header-rows: 1 + :widths: auto + + * - Field Size + - Description + - Data type + - Comments + * - ? + - encrypted + - uchar[] + - Encrypted data. See `Encrypted payload`_. + See also `Unencrypted Message Data`_ + broadcast ^^^^^^^^^ @@ -711,12 +822,34 @@ their inbox. Broadcasts are version 4 or 5. Pubkey objects and v5 broadcast objects are encrypted the same way: The data encoded in the sender's Bitmessage address is hashed twice. The first 32 bytes of the resulting hash constitutes the "private" encryption key and the last -32 bytes constitute a tag so that anyone listening can easily decide if this -particular message is interesting. The sender calculates the public key from -the private key and then encrypts the object with this public key. Thus anyone -who knows the Bitmessage address of the sender of a broadcast or pubkey object -can decrypt it. +32 bytes constitute a **tag** so that anyone listening can easily decide if +this particular message is interesting. The sender calculates the public key +from the private key and then encrypts the object with this public key. Thus +anyone who knows the Bitmessage address of the sender of a broadcast or pubkey +object can decrypt it. The version of broadcast objects was previously 2 or 3 but was changed to 4 or 5 for protocol v3. Having a broadcast version of 5 indicates that a tag is used which, in turn, is used when the sender's address version is >=4. + +.. list-table:: + :header-rows: 1 + :widths: auto + + * - Field Size + - Description + - Data type + - Comments + * - 32 + - tag + - uchar[] + - The tag. This field is new and only included when the broadcast version + is >= 5. Changed in protocol v3 + * - ? + - encrypted + - uchar[] + - Encrypted broadcast data. The keys are derived as described in the + paragraph above. See Encrypted payload for details about the encryption + algorithm itself. + +Unencrypted data format: From 9dc6416a16fd7eec826a1723a34348187bc0b07c Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Tue, 9 Mar 2021 19:06:50 +0200 Subject: [PATCH 098/424] Finish broadcast object type --- docs/protocol.rst | 81 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/docs/protocol.rst b/docs/protocol.rst index 137b1b95..17569d09 100644 --- a/docs/protocol.rst +++ b/docs/protocol.rst @@ -853,3 +853,84 @@ which, in turn, is used when the sender's address version is >=4. algorithm itself. Unencrypted data format: + +.. list-table:: + :header-rows: 1 + :widths: auto + + * - Field Size + - Description + - Data type + - Comments + * - 1+ + - broadcast version + - var_int + - The version number of this broadcast protocol message which is equal + to 2 or 3. This is included here so that it can be signed. This is + no longer included in protocol v3 + * - 1+ + - address version + - var_int + - The sender's address version + * - 1+ + - stream number + - var_int + - The sender's stream number + * - 4 + - behavior bitfield + - uint32_t + - A bitfield of optional behaviors and features that can be expected from + the owner of this pubkey. + * - 64 + - public signing key + - uchar[] + - The ECC public key used for signing (uncompressed format; + normally prepended with \x04) + * - 64 + - public encryption key + - uchar[] + - The ECC public key used for encryption (uncompressed format; + normally prepended with \x04) + * - 1+ + - nonce_trials_per_byte + - var_int + - Used to calculate the difficulty target of messages accepted by this + node. The higher this value, the more difficult the Proof of Work must + be before this individual will accept the message. This number is the + average number of nonce trials a node will have to perform to meet the + Proof of Work requirement. 1000 is the network minimum so any lower + values will be automatically raised to 1000. This field is new and is + only included when the address_version >= 3. + * - 1+ + - extra_bytes + - var_int + - Used to calculate the difficulty target of messages accepted by this + node. The higher this value, the more difficult the Proof of Work must + be before this individual will accept the message. This number is added + to the data length to make sending small messages more difficult. + 1000 is the network minimum so any lower values will be automatically + raised to 1000. This field is new and is only included when the + address_version >= 3. + * - 1+ + - encoding + - var_int + - The encoding type of the message + * - 1+ + - messageLength + - var_int + - The message length in bytes + * - messageLength + - message + - uchar[] + - The message + * - 1+ + - sig_length + - var_int + - Length of the signature + * - sig_length + - signature + - uchar[] + - The signature which did cover the unencrypted data from the broadcast + version down through the message. In protocol v3, it covers the + unencrypted object header starting with the time, all appended with + the decrypted data. From 5ced85113ae9c1590284f86e8487ce681f20c776 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Wed, 10 Mar 2021 22:02:43 +0200 Subject: [PATCH 099/424] verack section: emphasis and reference to Message structure table --- docs/protocol.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/protocol.rst b/docs/protocol.rst index 17569d09..c4873af3 100644 --- a/docs/protocol.rst +++ b/docs/protocol.rst @@ -473,10 +473,10 @@ inv messages advertising all of the valid objects of which you are aware. verack ^^^^^^ -The verack message is sent in reply to version. This message consists of only a -message header with the command string "verack". The TCP timeout starts out at -20 seconds; after verack messages are exchanged, the timeout is raised to -10 minutes. +The *verack* message is sent in reply to *version*. This message consists of +only a :ref:`message header ` with the command string +"verack". The TCP timeout starts out at 20 seconds; after verack messages are +exchanged, the timeout is raised to 10 minutes. If both sides announce that they support SSL, they MUST perform a SSL handshake immediately after they both send and receive verack. During this SSL handshake, From 03d9663caa7f21fdd37dec755c5c369f2c615bb3 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Wed, 10 Mar 2021 22:58:18 +0200 Subject: [PATCH 100/424] Replace behavior bitfield by ref --- docs/protocol.rst | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/protocol.rst b/docs/protocol.rst index c4873af3..06e10766 100644 --- a/docs/protocol.rst +++ b/docs/protocol.rst @@ -335,6 +335,8 @@ Further values for the message encodings can be decided upon by the community. Any MIME or MIME-like encoding format, should they be used, should make use of Bitmessage's 8-bit bytes. +.. _behavior-bitfield: + Pubkey bitfield features """""""""""""""""""""""" @@ -631,7 +633,7 @@ A version 2 pubkey. This is still in use and supported by current clients but - Data type - Comments * - 4 - - behavior bitfield + - |behavior_bitfield| - uint32_t - A bitfield of optional behaviors and features that can be expected from the node receiving the message. @@ -655,7 +657,7 @@ A version 2 pubkey. This is still in use and supported by current clients but - Data type - Comments * - 4 - - behavior bitfield + - |behavior_bitfield| - uint32_t - A bitfield of optional behaviors and features that can be expected from the node receiving the message. @@ -750,7 +752,7 @@ python code below: - Data type - Comments * - 4 - - behavior bitfield + - |behavior_bitfield| - uint32_t - A bitfield of optional behaviors and features that can be expected from the node receiving the message. @@ -877,7 +879,7 @@ Unencrypted data format: - var_int - The sender's stream number * - 4 - - behavior bitfield + - |behavior_bitfield| - uint32_t - A bitfield of optional behaviors and features that can be expected from the owner of this pubkey. @@ -934,3 +936,5 @@ Unencrypted data format: version down through the message. In protocol v3, it covers the unencrypted object header starting with the time, all appended with the decrypted data. + +.. |behavior_bitfield| replace:: :ref:`behavior bitfield ` From f58fb505ea49e2bc2cedd7123cb918d5b5859f0f Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Sat, 2 Jul 2022 15:31:55 +0300 Subject: [PATCH 101/424] Add Address doc. Closes: #1727. --- docs/address.rst | 106 +++++++++++++++++++++++++++++++++++++++++++++++ docs/index.rst | 1 + 2 files changed, 107 insertions(+) create mode 100644 docs/address.rst diff --git a/docs/address.rst b/docs/address.rst new file mode 100644 index 00000000..eec4bd2c --- /dev/null +++ b/docs/address.rst @@ -0,0 +1,106 @@ +Address +======= + +Bitmessage adresses are Base58 encoded public key hashes. An address looks like +``BM-BcbRqcFFSQUUmXFKsPJgVQPSiFA3Xash``. All Addresses start with ``BM-``, +however clients should accept addresses without the prefix. PyBitmessage does +this. The reason behind this idea is the fact, that when double clicking on an +address for copy and paste, the prefix is usually not selected due to the dash +being a common separator. + +Public Key usage +---------------- + +Addresses may look complicated but they fulfill the purpose of verifying the +sender. A Message claiming to be from a specific address can simply be checked by +decoding a special field in the data packet with the public key, that represents +the address. If the decryption succeeds, the message is from the address it +claims to be. + +Length +------ + +Without the ``BM-`` prefix, an address is usually 32-34 chars long. Since an +address is a hash it can be calculated by the client in a way, that the first +bytes are zero (``\0``) and bitmessage strips these. This causes the client to do +much more work to be lucky and find such an address. This is an optional checkbox +in address generation dialog. + +Versions +-------- + + * v1 addresses used a single RSA key pair + * v2 addresses use 2 ECC key pairs + * v3 addresses extends v2 addresses to allow specifying the proof of work + requirements. The pubkey object is signed to mitigate against + forgery/tampering. + * v4 addresses protect against harvesting addresses from getpubkey and pubkey + objects + +Address Types +------------- + +There are two address types the user can generate in PyBitmessage. The resulting +addresses have no difference, but the method how they are created differs. + +Random Address +^^^^^^^^^^^^^^ + +Random addresses are generated from a randomly chosen number. The resulting +address cannot be regenerated without knowledge of the number and therefore the +keys.dat should be backed up. Generating random addresses takes slightly longer +due to the POW required for the public key broadcast. + +Usage +""""" + + * Generate unique addresses + * Generate one time addresses. + + +Deterministic Address +^^^^^^^^^^^^^^^^^^^^^ + +For this type of Address a passphrase is required, that is used to seed the +random generator. Using the same passphrase creates the same addresses. +Using deterministic addresses should be done with caution, using a word from a +dictionary or a common number can lead to others generating the same address and +thus being able to receive messages not intended for them. Generating a +deterministic address will not publish the public key. The key is sent in case +somebody requests it. This saves :doc:`pow` time, when generating a bunch of +addresses. + +Usage +""""" + + * Create the same address on multiple systems without the need of copying + keys.dat or an Address Block. + * create a Channel. (Use the *Join/create chan* option in the file menu instead) + * Being able to restore the address in case of address database corruption or + deletation. + +Address generation +------------------ + + 1. Create a private and a public key for encryption and signing (resulting in + 4 keys) + 2. Merge the public part of the signing key and the encryption key together. + (encoded in uncompressed X9.62 format) (A) + 3. Take the SHA512 hash of A. (B) + 4. Take the RIPEMD160 of B. (C) + 5. Repeat step 1-4 until you have a result that starts with a zero + (Or two zeros, if you want a short address). (D) + 6. Remove the zeros at the beginning of D. (E) + 7. Put the stream number (as a var_int) in front of E. (F) + 8. Put the address version (as a var_int) in front of F. (G) + 9. Take a double SHA512 (hash of a hash) of G and use the first four bytes as a + checksum, that you append to the end. (H) + 10. base58 encode H. (J) + 11. Put "BM-" in front J. (K) + +K is your full address + + .. note:: Bitmessage's base58 encoding uses the following sequence + (the same as Bitcoin's): + "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz". + Many existing libraries for base58 do not use this ordering. diff --git a/docs/index.rst b/docs/index.rst index 4e647278..6edb0313 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -7,6 +7,7 @@ Protocol documentation :maxdepth: 2 protocol + address encryption pow From 142514755fdaa9e76e3b75981bf18df04dd420fc Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Tue, 5 Apr 2022 01:37:17 +0300 Subject: [PATCH 102/424] Workaround recent m2r bug: https://github.com/CrossNox/m2r2/issues/40 --- docs/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/requirements.txt b/docs/requirements.txt index a62bf415..57064f48 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,3 +1,4 @@ +mistune<=0.8.4 m2r sphinxcontrib-apidoc docutils<=0.17.1 From f311d9d25c5dfdf28eaae98d1581992aed433128 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Thu, 11 Nov 2021 15:32:55 +0200 Subject: [PATCH 103/424] Add a check for sendBroadcast API command with nonexisting address --- src/tests/test_api.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/tests/test_api.py b/src/tests/test_api.py index 835b4afb..57ec196d 100644 --- a/src/tests/test_api.py +++ b/src/tests/test_api.py @@ -420,6 +420,13 @@ class TestAPI(TestAPIProto): finally: self.assertEqual(self.api.deleteAddress(addr), 'success') + # sending from an address without private key + # (Bitmessage new releases/announcements) + result = self.api.sendBroadcast( + 'BM-GtovgYdgs7qXPkoYaRgrLFuFKz1SFpsw', + base64.encodestring('test_subject'), msg) + self.assertRegexpMatches(result, r'^API Error 0013:') + def test_chan(self): """Testing chan creation/joining""" # Create chan with known address From e12e9c4155a2ddac83ad92bdf2063821cbd7d1ea Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Mon, 29 Nov 2021 17:30:39 +0200 Subject: [PATCH 104/424] Add exception type in HandleSendMessage and HandleSendBroadcast and API error 14 in HandleSendBroadcast. --- src/api.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/api.py b/src/api.py index 3300fc72..0d802d61 100644 --- a/src/api.py +++ b/src/api.py @@ -1118,9 +1118,8 @@ class BMRPCDispatcher(object): fromAddress = addBMIfNotPresent(fromAddress) self._verifyAddress(fromAddress) try: - fromAddressEnabled = self.config.getboolean( - fromAddress, 'enabled') - except BaseException: + fromAddressEnabled = self.config.getboolean(fromAddress, 'enabled') + except ConfigParser.NoSectionError: raise APIError( 13, 'Could not find your fromAddress in the keys.dat file.') if not fromAddressEnabled: @@ -1164,10 +1163,13 @@ class BMRPCDispatcher(object): fromAddress = addBMIfNotPresent(fromAddress) self._verifyAddress(fromAddress) try: - self.config.getboolean(fromAddress, 'enabled') - except BaseException: + fromAddressEnabled = self.config.getboolean(fromAddress, 'enabled') + except ConfigParser.NoSectionError: raise APIError( 13, 'Could not find your fromAddress in the keys.dat file.') + if not fromAddressEnabled: + raise APIError(14, 'Your fromAddress is disabled. Cannot send.') + toAddress = str_broadcast_subscribers ackdata = helper_sent.insert( From b25ed553bc8a021f60a461bb13c467198ccdaa5c Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Tue, 24 May 2022 23:04:55 +0300 Subject: [PATCH 105/424] Format for PEP8 test_api and samples (almost) --- src/tests/samples.py | 17 +++++---- src/tests/test_api.py | 82 ++++++++++++++++++++----------------------- 2 files changed, 50 insertions(+), 49 deletions(-) diff --git a/src/tests/samples.py b/src/tests/samples.py index e1a3e676..426bd4c1 100644 --- a/src/tests/samples.py +++ b/src/tests/samples.py @@ -21,7 +21,8 @@ sample_ripe = b'003cd097eb7f35c87b5dc8b4538c22cb55312a9f' # stream: 1, version: 2 sample_address = 'BM-onkVu1KKL2UaUss5Upg9vXmqd3esTmV79' -sample_factor = 66858749573256452658262553961707680376751171096153613379801854825275240965733 +sample_factor = \ + 66858749573256452658262553961707680376751171096153613379801854825275240965733 # G * sample_factor sample_point = ( 33567437183004486938355437500683826356288335339807546987348409590129959362313, @@ -36,9 +37,13 @@ sample_deterministic_addr4 = 'BM-2cWzSnwjJ7yRP3nLEWUV5LisTZyREWSzUK' sample_daddr3_512 = 18875720106589866286514488037355423395410802084648916523381 sample_daddr4_512 = 25152821841976547050350277460563089811513157529113201589004 -sample_statusbar_msg = "new status bar message" -sample_inbox_msg_ids = ['27e644765a3e4b2e973ee7ccf958ea20', '51fc5531-3989-4d69-bbb5-68d64b756f5b', - '2c975c515f8b414db5eea60ba57ba455', 'bc1f2d8a-681c-4cc0-9a12-6067c7e1ac24'] -# second address in sample_test_subscription_address is for the announcement broadcast -sample_test_subscription_address = ['BM-2cWQLCBGorT9pUGkYSuGGVr9LzE4mRnQaq', 'BM-GtovgYdgs7qXPkoYaRgrLFuFKz1SFpsw'] +sample_statusbar_msg = 'new status bar message' +sample_inbox_msg_ids = [ + '27e644765a3e4b2e973ee7ccf958ea20', '51fc5531-3989-4d69-bbb5-68d64b756f5b', + '2c975c515f8b414db5eea60ba57ba455', 'bc1f2d8a-681c-4cc0-9a12-6067c7e1ac24'] +# second address in sample_subscription_addresses is +# for the announcement broadcast, but is it matter? +sample_subscription_addresses = [ + 'BM-2cWQLCBGorT9pUGkYSuGGVr9LzE4mRnQaq', + 'BM-GtovgYdgs7qXPkoYaRgrLFuFKz1SFpsw'] sample_subscription_name = 'test sub' diff --git a/src/tests/test_api.py b/src/tests/test_api.py index 57ec196d..a1082383 100644 --- a/src/tests/test_api.py +++ b/src/tests/test_api.py @@ -12,8 +12,10 @@ from six.moves import xmlrpc_client # nosec import psutil from .samples import ( - sample_seed, sample_deterministic_addr3, sample_deterministic_addr4, sample_statusbar_msg, - sample_inbox_msg_ids, sample_test_subscription_address, sample_subscription_name) + sample_deterministic_addr3, sample_deterministic_addr4, sample_seed, + sample_inbox_msg_ids, sample_statusbar_msg, + sample_subscription_addresses, sample_subscription_name +) from .test_process import TestProcessProto @@ -114,43 +116,38 @@ class TestAPI(TestAPIProto): ) self.assertEqual( len(json.loads( - self.api.getInboxMessageById(hexlify(sample_inbox_msg_ids[2])))["inboxMessage"]), + self.api.getInboxMessageById( + hexlify(sample_inbox_msg_ids[2])))["inboxMessage"]), 1 ) self.assertEqual( len(json.loads( - self.api.getInboxMessagesByReceiver(sample_deterministic_addr4))["inboxMessages"]), + self.api.getInboxMessagesByReceiver( + sample_deterministic_addr4))["inboxMessages"]), 4 ) def test_message_trash(self): """Test message inbox methods""" - messages_before_delete = len(json.loads(self.api.getAllInboxMessageIds())["inboxMessageIds"]) - self.assertEqual( - self.api.trashMessage(hexlify(sample_inbox_msg_ids[0])), - 'Trashed message (assuming message existed).' - ) - self.assertEqual( - self.api.trashInboxMessage(hexlify(sample_inbox_msg_ids[1])), - 'Trashed inbox message (assuming message existed).' - ) - self.assertEqual( - len(json.loads(self.api.getAllInboxMessageIds())["inboxMessageIds"]), - messages_before_delete - 2 - ) - self.assertEqual( - self.api.undeleteMessage(hexlify(sample_inbox_msg_ids[0])), - 'Undeleted message' - ) - self.assertEqual( - self.api.undeleteMessage(hexlify(sample_inbox_msg_ids[1])), - 'Undeleted message' - ) - self.assertEqual( - len(json.loads(self.api.getAllInboxMessageIds())["inboxMessageIds"]), - messages_before_delete - ) + messages_before_delete = len( + json.loads(self.api.getAllInboxMessageIds())["inboxMessageIds"]) + for msgid in sample_inbox_msg_ids[:2]: + self.assertEqual( + self.api.trashMessage(hexlify(msgid)), + 'Trashed message (assuming message existed).' + ) + self.assertEqual(len( + json.loads(self.api.getAllInboxMessageIds())["inboxMessageIds"] + ), messages_before_delete - 2) + for msgid in sample_inbox_msg_ids[:2]: + self.assertEqual( + self.api.undeleteMessage(hexlify(msgid)), + 'Undeleted message' + ) + self.assertEqual(len( + json.loads(self.api.getAllInboxMessageIds())["inboxMessageIds"] + ), messages_before_delete) def test_clientstatus_consistency(self): """If networkStatus is notConnected networkConnections should be 0""" @@ -265,7 +262,9 @@ class TestAPI(TestAPIProto): """Testing the API commands related to subscriptions""" self.assertEqual( - self.api.addSubscription(sample_test_subscription_address[0], sample_subscription_name.encode('base64')), + self.api.addSubscription( + sample_subscription_addresses[0], + sample_subscription_name.encode('base64')), 'Added subscription.' ) @@ -273,18 +272,23 @@ class TestAPI(TestAPIProto): # check_address for sub in json.loads(self.api.listSubscriptions())['subscriptions']: # special address, added when sqlThread starts - if sub['address'] == sample_test_subscription_address[0]: + if sub['address'] == sample_subscription_addresses[0]: added_subscription = sub + self.assertEqual( + base64.decodestring(sub['label']), sample_subscription_name + ) + self.assertTrue(sub['enabled']) break self.assertEqual( - base64.decodestring(added_subscription['label']) if added_subscription['label'] else None, + base64.decodestring(added_subscription['label']) + if added_subscription['label'] else None, sample_subscription_name) self.assertTrue(added_subscription['enabled']) for s in json.loads(self.api.listSubscriptions())['subscriptions']: # special address, added when sqlThread starts - if s['address'] == sample_test_subscription_address[1]: + if s['address'] == sample_subscription_addresses[1]: self.assertEqual( base64.decodestring(s['label']), 'Bitmessage new releases/announcements') @@ -295,17 +299,16 @@ class TestAPI(TestAPIProto): 'Could not find Bitmessage new releases/announcements' ' in subscriptions') self.assertEqual( - self.api.deleteSubscription(sample_test_subscription_address[0]), + self.api.deleteSubscription(sample_subscription_addresses[0]), 'Deleted subscription if it existed.') self.assertEqual( - self.api.deleteSubscription(sample_test_subscription_address[1]), + self.api.deleteSubscription(sample_subscription_addresses[1]), 'Deleted subscription if it existed.') self.assertEqual( json.loads(self.api.listSubscriptions())['subscriptions'], []) def test_send(self): """Test message sending""" - # self.api.createDeterministicAddresses(self._seed, 1, 4) addr = self._add_random_address('random_2') msg = base64.encodestring('test message') msg_subject = base64.encodestring('test_subject') @@ -330,13 +333,6 @@ class TestAPI(TestAPIProto): break else: raise KeyError - # Find the message in inbox - # for m in json.loads( - # self.api.getInboxMessagesByReceiver( - # 'BM-2cWzSnwjJ7yRP3nLEWUV5LisTZyREWSzUK'))['inboxMessages']: - # if m['subject'] == msg_subject: - # inbox_msg = m['message'] - # break except ValueError: self.fail('sendMessage returned error or ackData is not hex') except KeyError: From 20ce69b3370f5fa5fab95c6472eece0b0734bfbf Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Tue, 26 Jul 2022 02:25:38 +0300 Subject: [PATCH 106/424] Add enableAddress API command and a check for sending from disabled address --- src/api.py | 10 ++++++++++ src/tests/test_api.py | 5 +++++ 2 files changed, 15 insertions(+) diff --git a/src/api.py b/src/api.py index 0d802d61..8e4ddda4 100644 --- a/src/api.py +++ b/src/api.py @@ -883,6 +883,16 @@ class BMRPCDispatcher(object): shared.reloadMyAddressHashes() return "success" + @command('enableAddress') + def HandleEnableAddress(self, address, enable=True): + """Enable or disable the address depending on the *enable* value""" + self._verifyAddress(address) + address = addBMIfNotPresent(address) + config.set(address, 'enabled', str(enable)) + self.config.save() + shared.reloadMyAddressHashes() + return "success" + @command('getAllInboxMessages') def HandleGetAllInboxMessages(self): """ diff --git a/src/tests/test_api.py b/src/tests/test_api.py index a1082383..fc0f4db2 100644 --- a/src/tests/test_api.py +++ b/src/tests/test_api.py @@ -413,6 +413,11 @@ class TestAPI(TestAPIProto): self.assertEqual(self.api.deleteAndVacuum(), 'done') self.assertIsNone(json.loads( self.api.getSentMessageById(sent_msgid))) + # Try sending from disabled address + self.assertEqual(self.api.enableAddress(addr, False), 'success') + result = self.api.sendBroadcast( + addr, base64.encodestring('test_subject'), msg) + self.assertRegexpMatches(result, r'^API Error 0014:') finally: self.assertEqual(self.api.deleteAddress(addr), 'success') From bb7d8018c673db25a9ca1277fbdd4c9f187b07e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20=C5=A0urda?= Date: Sun, 31 Jul 2022 19:27:05 +0800 Subject: [PATCH 107/424] Fix handling of objects from unwanted streams --- src/network/bmproto.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/network/bmproto.py b/src/network/bmproto.py index 1295bd34..009c471f 100644 --- a/src/network/bmproto.py +++ b/src/network/bmproto.py @@ -403,7 +403,7 @@ class BMProto(AdvancedDispatcher, ObjectTracker): try: self.object.checkStream() except BMObjectUnwantedStreamError: - acceptmismatch = config.get( + acceptmismatch = config.getboolean( "inventory", "acceptmismatch") BMProto.stopDownloadingObject( self.object.inventoryHash, acceptmismatch) From 69e540b504697501cd1aa51da0a4925500fd4775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20=C5=A0urda?= Date: Sun, 31 Jul 2022 19:29:19 +0800 Subject: [PATCH 108/424] Define stream number validity --- src/network/bmobject.py | 9 +++++++++ src/network/bmproto.py | 7 ++++++- src/protocol.py | 5 +++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/network/bmobject.py b/src/network/bmobject.py index 12b997d7..c6684f3c 100644 --- a/src/network/bmobject.py +++ b/src/network/bmobject.py @@ -36,6 +36,12 @@ class BMObjectUnwantedStreamError(Exception): errorCodes = ("Object in unwanted stream") +class BMObjectInvalidStreamError(Exception): + """Exception indicating the object is in a stream + outside of specification.""" + errorCodes = ("Object in invalid stream") + + class BMObjectInvalidError(Exception): """The object's data does not match object specification.""" errorCodes = ("Invalid object") @@ -106,6 +112,9 @@ class BMObject(object): # pylint: disable=too-many-instance-attributes 'The streamNumber %i isn\'t one we are interested in.', self.streamNumber) raise BMObjectUnwantedStreamError() + if self.streamNumber < protocol.MIN_VALID_STREAM \ + or self.streamNumber > protocol.MAX_VALID_STREAM: + raise BMObjectInvalidStreamError() def checkAlreadyHave(self): """ diff --git a/src/network/bmproto.py b/src/network/bmproto.py index 009c471f..ee43523d 100644 --- a/src/network/bmproto.py +++ b/src/network/bmproto.py @@ -22,7 +22,8 @@ from network.advanceddispatcher import AdvancedDispatcher from network.bmobject import ( BMObject, BMObjectAlreadyHaveError, BMObjectExpiredError, BMObjectInsufficientPOWError, BMObjectInvalidDataError, - BMObjectInvalidError, BMObjectUnwantedStreamError + BMObjectInvalidError, BMObjectUnwantedStreamError, + BMObjectInvalidStreamError ) from network.constants import ( ADDRESS_ALIVE, MAX_MESSAGE_SIZE, MAX_OBJECT_COUNT, @@ -409,6 +410,10 @@ class BMProto(AdvancedDispatcher, ObjectTracker): self.object.inventoryHash, acceptmismatch) if not acceptmismatch: raise + except BMObjectInvalidStreamError: + BMProto.stopDownloadingObject( + self.object.inventoryHash) + raise try: self.object.checkObjectByType() diff --git a/src/protocol.py b/src/protocol.py index 291d4ebd..4cab2dd6 100644 --- a/src/protocol.py +++ b/src/protocol.py @@ -90,6 +90,11 @@ def isBitSetWithinBitfield(fourByteString, n): x, = unpack('>L', fourByteString) return x & 2**n != 0 +# Streams + + +MIN_VALID_STREAM = 1 +MAX_VALID_STREAM = 2**63 - 1 # IP addresses From eb5f791cb627713d270fb27ff29b38fbfb40aa7e Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Mon, 1 Aug 2022 00:19:40 +0300 Subject: [PATCH 109/424] Finalize invalid stream handling: - prioritize the check for invalid stream - use BMObjectInvalidError exception, remove unused classes --- src/network/bmobject.py | 20 +++++--------------- src/network/bmproto.py | 12 ++++-------- 2 files changed, 9 insertions(+), 23 deletions(-) diff --git a/src/network/bmobject.py b/src/network/bmobject.py index c6684f3c..5d7fdcbd 100644 --- a/src/network/bmobject.py +++ b/src/network/bmobject.py @@ -19,12 +19,6 @@ class BMObjectInsufficientPOWError(Exception): 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") @@ -36,12 +30,6 @@ class BMObjectUnwantedStreamError(Exception): errorCodes = ("Object in unwanted stream") -class BMObjectInvalidStreamError(Exception): - """Exception indicating the object is in a stream - outside of specification.""" - errorCodes = ("Object in invalid stream") - - class BMObjectInvalidError(Exception): """The object's data does not match object specification.""" errorCodes = ("Invalid object") @@ -107,14 +95,16 @@ class BMObject(object): # pylint: disable=too-many-instance-attributes def checkStream(self): """Check if object's stream matches streams we are interested in""" + if self.streamNumber < protocol.MIN_VALID_STREAM \ + or self.streamNumber > protocol.MAX_VALID_STREAM: + logger.warning( + 'The object has invalid stream: %s', self.streamNumber) + raise BMObjectInvalidError() if self.streamNumber not in state.streamsInWhichIAmParticipating: logger.debug( 'The streamNumber %i isn\'t one we are interested in.', self.streamNumber) raise BMObjectUnwantedStreamError() - if self.streamNumber < protocol.MIN_VALID_STREAM \ - or self.streamNumber > protocol.MAX_VALID_STREAM: - raise BMObjectInvalidStreamError() def checkAlreadyHave(self): """ diff --git a/src/network/bmproto.py b/src/network/bmproto.py index ee43523d..9e6d5044 100644 --- a/src/network/bmproto.py +++ b/src/network/bmproto.py @@ -21,9 +21,8 @@ from inventory import Inventory from network.advanceddispatcher import AdvancedDispatcher from network.bmobject import ( BMObject, BMObjectAlreadyHaveError, BMObjectExpiredError, - BMObjectInsufficientPOWError, BMObjectInvalidDataError, - BMObjectInvalidError, BMObjectUnwantedStreamError, - BMObjectInvalidStreamError + BMObjectInsufficientPOWError, BMObjectInvalidError, + BMObjectUnwantedStreamError ) from network.constants import ( ADDRESS_ALIVE, MAX_MESSAGE_SIZE, MAX_OBJECT_COUNT, @@ -130,8 +129,6 @@ class BMProto(AdvancedDispatcher, ObjectTracker): 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: @@ -410,9 +407,8 @@ class BMProto(AdvancedDispatcher, ObjectTracker): self.object.inventoryHash, acceptmismatch) if not acceptmismatch: raise - except BMObjectInvalidStreamError: - BMProto.stopDownloadingObject( - self.object.inventoryHash) + except BMObjectInvalidError: + BMProto.stopDownloadingObject(self.object.inventoryHash) raise try: From 36c2a8006048b11fc7589040954c2b022135eed8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20=C5=A0urda?= Date: Mon, 1 Aug 2022 17:07:20 +0800 Subject: [PATCH 110/424] Mock multiqueue - kivy mock now uses a mock multiqueue, as the existing code didn't handle all problems. For example, trying to load OpenSSL currently crashes on my M1 Mac, so I can't even add an exception handler to fall back. With this patch, the kivy_mock simply forces a fallback to Queue --- src/mock/kivy_main.py | 4 ++++ src/mock/multiqueue.py | 7 +++++++ src/queues.py | 8 +------- 3 files changed, 12 insertions(+), 7 deletions(-) create mode 100644 src/mock/multiqueue.py diff --git a/src/mock/kivy_main.py b/src/mock/kivy_main.py index badc1dc1..7fe330f6 100644 --- a/src/mock/kivy_main.py +++ b/src/mock/kivy_main.py @@ -1,7 +1,11 @@ """Mock kivy app with mock threads.""" +# pylint: disable=unused-import +# flake8: noqa:E401 from pybitmessage import state from pybitmessage.bitmessagekivy.mpybit import NavigateApp + +import multiqueue from class_addressGenerator import FakeAddressGenerator diff --git a/src/mock/multiqueue.py b/src/mock/multiqueue.py new file mode 100644 index 00000000..8ec76920 --- /dev/null +++ b/src/mock/multiqueue.py @@ -0,0 +1,7 @@ +""" +Mock MultiQueue (just normal Queue) +""" + +from six.moves import queue + +MultiQueue = queue.Queue diff --git a/src/queues.py b/src/queues.py index d86d36fa..4a9b98d2 100644 --- a/src/queues.py +++ b/src/queues.py @@ -8,13 +8,7 @@ from six.moves import queue try: from multiqueue import MultiQueue except ImportError: - try: - from .multiqueue import MultiQueue - except ImportError: - import sys - if 'bitmessagemock' not in sys.modules: - raise - MultiQueue = queue.Queue + from .multiqueue import MultiQueue class ObjectProcessorQueue(queue.Queue): From fd7a6f8fee5f1d18061d976ec470986c74864d16 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Mon, 1 Aug 2022 17:50:38 +0530 Subject: [PATCH 111/424] Update kivy loader --- src/bitmessagekivy/kv/popup.kv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bitmessagekivy/kv/popup.kv b/src/bitmessagekivy/kv/popup.kv index f2d0f292..0a000440 100644 --- a/src/bitmessagekivy/kv/popup.kv +++ b/src/bitmessagekivy/kv/popup.kv @@ -6,7 +6,7 @@ disabled: True background_disabled_normal: "White.png" Image: - source: app.image_path + '/loader.zip' + source: app.image_path + '/loader.gif' anim_delay: 0 #mipmap: True size: root.size From 60fd56748f6ddd574bd96e34933e3d265b72c591 Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Tue, 5 Apr 2022 01:35:35 +0300 Subject: [PATCH 112/424] Update the script for testing in docker container: - don't require bash - mark appimage stage - use --no-cache if docker build fails - remove container after running the tests - use tty for colorful output --- run-tests-in-docker.sh | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/run-tests-in-docker.sh b/run-tests-in-docker.sh index 9fa9bfcc..174cb754 100755 --- a/run-tests-in-docker.sh +++ b/run-tests-in-docker.sh @@ -1,5 +1,13 @@ -#!/bin/bash +#!/bin/sh -docker build --target travis -t pybm -f packages/docker/Dockerfile.bionic . -docker run pybm +DOCKERFILE=packages/docker/Dockerfile.bionic +# explicitly mark appimage stage because it builds in any case +docker build --target appimage -t pybm/appimage -f $DOCKERFILE . + +if [ $? -gt 0 ]; then + docker build --no-cache --target appimage -t pybm/appimage -f $DOCKERFILE . +fi + +docker build --target tox -t pybm/tox -f $DOCKERFILE . +docker run --rm -t pybm/tox From 4b8d04c42048f67f50d8fd9987be51e67d6cc5f6 Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Tue, 5 Apr 2022 02:20:52 +0300 Subject: [PATCH 113/424] Rewrite Dockerfile.bionic: - install needed dependencies separately in each stage - make appimage the first stage because it needs minimum packages - replace travis2bash by tox --- packages/docker/Dockerfile.bionic | 95 ++++++++++++------------------- 1 file changed, 37 insertions(+), 58 deletions(-) diff --git a/packages/docker/Dockerfile.bionic b/packages/docker/Dockerfile.bionic index fbb352f9..60b18c86 100644 --- a/packages/docker/Dockerfile.bionic +++ b/packages/docker/Dockerfile.bionic @@ -1,61 +1,48 @@ FROM ubuntu:bionic AS base ENV DEBIAN_FRONTEND noninteractive -ENV TRAVIS_SKIP_APT_UPDATE 1 - -ENV LANG en_US.UTF-8 -ENV LANGUAGE en_US:en -ENV LC_ALL en_US.UTF-8 RUN apt-get update +# Common apt packages RUN apt-get install -yq --no-install-suggests --no-install-recommends \ - software-properties-common + software-properties-common build-essential libcap-dev libssl-dev \ + python-all-dev python-setuptools wget xvfb -RUN dpkg --add-architecture i386 +############################################################################### -RUN add-apt-repository ppa:deadsnakes/ppa - -RUN apt-get -y install sudo +FROM base AS appimage RUN apt-get install -yq --no-install-suggests --no-install-recommends \ - # travis xenial bionic - python-setuptools libssl-dev python-prctl \ - python-dev python-virtualenv python-pip virtualenv \ - # dpkg - python-minimal python-all python openssl libssl-dev \ - dh-apparmor debhelper dh-python python-msgpack python-qt4 git python-stdeb \ - python-all-dev python-crypto python-psutil \ - fakeroot python-pytest python3-wheel \ - # Code quality - pylint python-pycodestyle python3-pycodestyle pycodestyle python-flake8 \ - python3-flake8 flake8 python-pyflakes python3-pyflakes pyflakes pyflakes3 \ - curl \ - # Wine - python python-pip wget wine-stable winetricks mingw-w64 wine32 wine64 xvfb \ - # Buildbot - python3-dev libffi-dev python3-setuptools \ - python3-pip \ - # python 3.7 - python3.7 python3.7-dev \ - # .travis.yml - build-essential libcap-dev tor \ - language-pack-en + debhelper dh-apparmor dh-python python-stdeb fakeroot +COPY . /home/builder/src -# cleanup -RUN rm -rf /var/lib/apt/lists/* +WORKDIR /home/builder/src -##################################################################################################### +RUN VERSION=$(python setup.py -V) \ + && python setup.py sdist \ + && python setup.py --command-packages=stdeb.command bdist_deb \ + && dpkg-deb -I deb_dist/pybitmessage_${VERSION}-1_amd64.deb -FROM base AS travis +RUN buildscripts/appimage.sh +RUN VERSION=$(python setup.py -V) \ + && out/PyBitmessage-${VERSION}.glibc2.15-x86_64.AppImage \ + --appimage-extract-and-run -t -# travis2bash -RUN wget -O /usr/local/bin/travis2bash.sh https://git.bitmessage.org/Bitmessage/buildbot-scripts/raw/branch/master/travis2bash.sh -RUN chmod +x /usr/local/bin/travis2bash.sh +############################################################################### + +FROM base AS tox + +RUN apt-get install -yq --no-install-suggests --no-install-recommends \ + language-pack-en \ + libffi-dev python3-dev python3-pip python3.8 python3.8-dev python3.8-venv \ + python-msgpack python-pip python-qt4 python-six qt5dxcb-plugin tor + +RUN python3.8 -m pip install setuptools wheel +RUN python3.8 -m pip install --upgrade pip tox virtualenv RUN useradd -m -U builder -RUN echo 'builder ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers # copy sources COPY . /home/builder/src @@ -63,14 +50,21 @@ RUN chown -R builder.builder /home/builder/src USER builder +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en +ENV LC_ALL en_US.UTF-8 + WORKDIR /home/builder/src -ENTRYPOINT /usr/local/bin/travis2bash.sh +ENTRYPOINT ["tox"] -##################################################################################################### +############################################################################### FROM base AS buildbot +# cleanup +RUN rm -rf /var/lib/apt/lists/* + # travis2bash RUN wget -O /usr/local/bin/travis2bash.sh https://git.bitmessage.org/Bitmessage/buildbot-scripts/raw/branch/master/travis2bash.sh RUN chmod +x /usr/local/bin/travis2bash.sh @@ -86,22 +80,7 @@ USER buildbot ENTRYPOINT /entrypoint.sh "$BUILDMASTER" "$WORKERNAME" "$WORKERPASS" -################################################################################################# - -FROM base AS appimage - -COPY . /home/builder/src - -WORKDIR /home/builder/src - -RUN VERSION=$(python setup.py -V) \ - && python setup.py sdist \ - && python setup.py --command-packages=stdeb.command bdist_deb \ - && dpkg-deb -I deb_dist/pybitmessage_${VERSION}-1_amd64.deb - -RUN buildscripts/appimage.sh -RUN VERSION=$(python setup.py -V) \ - && out/PyBitmessage-${VERSION}.glibc2.15-x86_64.AppImage --appimage-extract-and-run -t +############################################################################### FROM base AS appandroid From e048905b8b7382f05b5cd3ab2d5b3b5f865e6112 Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Thu, 7 Apr 2022 11:41:00 +0300 Subject: [PATCH 114/424] Move appimage dry run into buildscripts/appimage.sh like in winbuild --- buildscripts/appimage.sh | 6 ++++-- packages/docker/Dockerfile.bionic | 8 ++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/buildscripts/appimage.sh b/buildscripts/appimage.sh index 1b190de0..3a89d941 100755 --- a/buildscripts/appimage.sh +++ b/buildscripts/appimage.sh @@ -20,7 +20,9 @@ fi if [ -f "out/PyBitmessage-${VERSION}.glibc2.15-x86_64.AppImage" ]; then echo "Build Successful"; - echo "Run out/PyBitmessage-${VERSION}.glibc2.15-x86_64.AppImage" + echo "Run out/PyBitmessage-${VERSION}.glibc2.15-x86_64.AppImage"; + out/PyBitmessage-${VERSION}.glibc2.15-x86_64.AppImage -t else - echo "Build Failed" + echo "Build Failed"; + exit 1 fi diff --git a/packages/docker/Dockerfile.bionic b/packages/docker/Dockerfile.bionic index 60b18c86..3cd24d8f 100644 --- a/packages/docker/Dockerfile.bionic +++ b/packages/docker/Dockerfile.bionic @@ -23,12 +23,8 @@ WORKDIR /home/builder/src RUN VERSION=$(python setup.py -V) \ && python setup.py sdist \ && python setup.py --command-packages=stdeb.command bdist_deb \ - && dpkg-deb -I deb_dist/pybitmessage_${VERSION}-1_amd64.deb - -RUN buildscripts/appimage.sh -RUN VERSION=$(python setup.py -V) \ - && out/PyBitmessage-${VERSION}.glibc2.15-x86_64.AppImage \ - --appimage-extract-and-run -t + && dpkg-deb -I deb_dist/pybitmessage_${VERSION}-1_amd64.deb \ + && buildscripts/appimage.sh ############################################################################### From 6b25a5481fad869e12a554739aaec172fce1ea71 Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Mon, 11 Apr 2022 00:56:36 +0300 Subject: [PATCH 115/424] Move the appimage build to CMD, packages will be in /dist/ (run -v) --- packages/docker/Dockerfile.bionic | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/docker/Dockerfile.bionic b/packages/docker/Dockerfile.bionic index 3cd24d8f..1d321387 100644 --- a/packages/docker/Dockerfile.bionic +++ b/packages/docker/Dockerfile.bionic @@ -20,10 +20,11 @@ COPY . /home/builder/src WORKDIR /home/builder/src -RUN VERSION=$(python setup.py -V) \ - && python setup.py sdist \ +CMD python setup.py sdist \ && python setup.py --command-packages=stdeb.command bdist_deb \ - && dpkg-deb -I deb_dist/pybitmessage_${VERSION}-1_amd64.deb \ + && dpkg-deb -I deb_dist/*.deb \ + && cp deb_dist/*.deb /dist/ \ + && ln -s /dist out \ && buildscripts/appimage.sh ############################################################################### From 3cbd77f1943487649e85212a7869df9b67925b75 Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Sat, 16 Apr 2022 21:58:12 +0300 Subject: [PATCH 116/424] Add a stage winebuild for building 32bit exe --- packages/docker/Dockerfile.bionic | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/docker/Dockerfile.bionic b/packages/docker/Dockerfile.bionic index 1d321387..c601c6b3 100644 --- a/packages/docker/Dockerfile.bionic +++ b/packages/docker/Dockerfile.bionic @@ -57,6 +57,24 @@ ENTRYPOINT ["tox"] ############################################################################### +FROM base AS winebuild + +RUN dpkg --add-architecture i386 +RUN apt-get update + +RUN apt-get install -yq --no-install-suggests --no-install-recommends \ + mingw-w64 wine-stable winetricks wine32 wine64 + +COPY . /home/builder/src + +WORKDIR /home/builder/src + +# xvfb-run -a buildscripts/winbuild.sh +CMD xvfb-run -a i386 buildscripts/winbuild.sh \ + && cp packages/pyinstaller/dist/*.exe /dist/ + +############################################################################### + FROM base AS buildbot # cleanup From eb43f2c0685a2cb14d029483b48e7d9a88cb6271 Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Sun, 29 May 2022 06:47:45 +0300 Subject: [PATCH 117/424] Add a container for building snap --- packages/docker/Dockerfile.bionic | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/docker/Dockerfile.bionic b/packages/docker/Dockerfile.bionic index c601c6b3..ff53e4e7 100644 --- a/packages/docker/Dockerfile.bionic +++ b/packages/docker/Dockerfile.bionic @@ -57,6 +57,18 @@ ENTRYPOINT ["tox"] ############################################################################### +FROM base AS snap + +RUN apt-get install -yq --no-install-suggests --no-install-recommends snapcraft + +COPY . /home/builder/src + +WORKDIR /home/builder/src + +CMD cd packages && snapcraft && cp *.snap /dist/ + +############################################################################### + FROM base AS winebuild RUN dpkg --add-architecture i386 From d41503ee8a7058b16bf8a96f797afda03026b3de Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Wed, 15 Jun 2022 16:46:02 +0300 Subject: [PATCH 118/424] Redefine VERSION_EXPANDED from pkg2appimage functions.sh --- buildscripts/appimage.sh | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/buildscripts/appimage.sh b/buildscripts/appimage.sh index 3a89d941..a5691783 100755 --- a/buildscripts/appimage.sh +++ b/buildscripts/appimage.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # Cleanup rm -rf PyBitmessage @@ -18,10 +18,18 @@ fi ./pkg2appimage packages/AppImage/PyBitmessage.yml -if [ -f "out/PyBitmessage-${VERSION}.glibc2.15-x86_64.AppImage" ]; then +./pkg2appimage --appimage-extract + +. ./squashfs-root/usr/share/pkg2appimage/functions.sh + +GLIBC=$(glibc_needed) + +VERSION_EXPANDED=${VERSION}.glibc${GLIBC}-${SYSTEM_ARCH} + +if [ -f "out/PyBitmessage-${VERSION_EXPANDED}.AppImage" ]; then echo "Build Successful"; - echo "Run out/PyBitmessage-${VERSION}.glibc2.15-x86_64.AppImage"; - out/PyBitmessage-${VERSION}.glibc2.15-x86_64.AppImage -t + echo "Run out/PyBitmessage-${VERSION_EXPANDED}.AppImage"; + out/PyBitmessage-${VERSION_EXPANDED}.AppImage -t else echo "Build Failed"; exit 1 From a3214b8eea94bf4fcedc3154377b97423ae39eb9 Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Sun, 17 Apr 2022 15:09:07 +0300 Subject: [PATCH 119/424] Define TestSocketInet class doing helper_startup.fixSocket() in setUpClass - to be inherited by any test case using protocol.encodeHost(). --- src/tests/test_packets.py | 5 +++-- src/tests/test_protocol.py | 8 ++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/tests/test_packets.py b/src/tests/test_packets.py index 65ee0d44..3a0c43e9 100644 --- a/src/tests/test_packets.py +++ b/src/tests/test_packets.py @@ -1,14 +1,15 @@ +"""Test packets creation and parsing""" -import unittest from binascii import unhexlify from struct import pack from pybitmessage import addresses, protocol from .samples import magic +from .test_protocol import TestSocketInet -class TestSerialize(unittest.TestCase): +class TestSerialize(TestSocketInet): """Test serializing and deserializing packet data""" def test_varint(self): diff --git a/src/tests/test_protocol.py b/src/tests/test_protocol.py index d285d1df..e3137b25 100644 --- a/src/tests/test_protocol.py +++ b/src/tests/test_protocol.py @@ -9,14 +9,18 @@ from pybitmessage import protocol, state from pybitmessage.helper_startup import fixSocket -class TestProtocol(unittest.TestCase): - """Main protocol test case""" +class TestSocketInet(unittest.TestCase): + """Base class for test cases using protocol.encodeHost()""" @classmethod def setUpClass(cls): """Execute fixSocket() before start. Only for Windows?""" fixSocket() + +class TestProtocol(TestSocketInet): + """Main protocol test case""" + def test_checkIPv4Address(self): """Check the results of protocol.checkIPv4Address()""" token = 'HELLO' From 722f2751722f763081bef7c55e6235b1cc0e9632 Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Mon, 18 Apr 2022 17:16:52 +0300 Subject: [PATCH 120/424] Skip TestProcess.test_listening() on wine --- src/tests/test_process.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tests/test_process.py b/src/tests/test_process.py index c968c0ae..37b34541 100644 --- a/src/tests/test_process.py +++ b/src/tests/test_process.py @@ -205,6 +205,8 @@ class TestProcess(TestProcessProto): self.assertEqual( self.process.environ().get('BITMESSAGE_HOME'), self.home) + @unittest.skipIf( + os.getenv('WINEPREFIX'), "process.connections() doesn't work on wine") def test_listening(self): """Check that pybitmessage listens on port 8444""" for c in self.process.connections(): From 8652fef620b9f58b7d606b10b51a4d464bc22b51 Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Wed, 20 Jul 2022 20:35:51 +0300 Subject: [PATCH 121/424] Fix trying to decode str on windows --- src/paths.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/paths.py b/src/paths.py index e2f8c97e..8cde08af 100644 --- a/src/paths.py +++ b/src/paths.py @@ -49,11 +49,8 @@ def lookupAppdataFolder(): sys.exit( 'Could not find home folder, please report this message' ' and your OS X version to the BitMessage Github.') - elif 'win32' in sys.platform or 'win64' in sys.platform: - dataFolder = os.path.join( - os.environ['APPDATA'].decode( - sys.getfilesystemencoding(), 'ignore'), APPNAME - ) + os.path.sep + elif sys.platform.startswith('win'): + dataFolder = os.path.join(os.environ['APPDATA'], APPNAME) + os.path.sep else: try: dataFolder = os.path.join(os.environ['XDG_CONFIG_HOME'], APPNAME) From 6310fc8919d752499f74ab71c4ffbb9d5811edd2 Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Wed, 27 Jul 2022 23:55:42 +0300 Subject: [PATCH 122/424] Dry run both windows executables - downgrade PyInstaller to 3.5 for win64 --- buildscripts/winbuild.sh | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/buildscripts/winbuild.sh b/buildscripts/winbuild.sh index c26766f6..fab0b3e0 100755 --- a/buildscripts/winbuild.sh +++ b/buildscripts/winbuild.sh @@ -96,16 +96,18 @@ function install_openssl(){ function install_pyinstaller() { - cd "${BASE_DIR}" || exit 1 - echo "Installing PyInstaller" - if [ "${MACHINE_TYPE}" == 'x86_64' ]; then - # 3.6 is the last version to support python 2.7 - wine python -m pip install -I pyinstaller==3.6 - else - # 3.2.1 is the last version to work on XP - # see https://github.com/pyinstaller/pyinstaller/issues/2931 - wine python -m pip install -I pyinstaller==3.2.1 - fi + cd "${BASE_DIR}" || exit 1 + echo "Installing PyInstaller" + if [ "${MACHINE_TYPE}" == 'x86_64' ]; then + # 3.6 is the last version to support python 2.7 + # but the resulting executable cannot run in wine + # see https://github.com/pyinstaller/pyinstaller/issues/4628 + wine python -m pip install -I pyinstaller==3.5 + else + # 3.2.1 is the last version to work on XP + # see https://github.com/pyinstaller/pyinstaller/issues/2931 + wine python -m pip install -I pyinstaller==3.2.1 + fi } function install_pip_depends() @@ -169,11 +171,14 @@ function build_exe(){ } function dryrun_exe(){ - cd "${BASE_DIR}" || exit 1 - if [ ! "${MACHINE_TYPE}" == 'x86_64' ]; then - local VERSION=$(python setup.py --version) - wine packages/pyinstaller/dist/Bitmessage_x86_$VERSION.exe -t - fi + cd "${BASE_DIR}" || exit 1 + local VERSION=$(python setup.py --version) + if [ "${MACHINE_TYPE}" == 'x86_64' ]; then + EXE=Bitmessage_x64_$VERSION.exe + else + EXE=Bitmessage_x86_$VERSION.exe + fi + wine packages/pyinstaller/dist/$EXE -t } # prepare on ubuntu From 78e16e61a086d60611269834c7aaeb3657f565b8 Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Thu, 28 Jul 2022 01:35:48 +0300 Subject: [PATCH 123/424] Build tests into the windows bundle if DEBUG=True is set in pyinstaller spec --- packages/pyinstaller/bitmessagemain.spec | 11 ++++++++--- src/bitmessagemain.py | 7 +++++-- src/pyelliptic/tests/__init__.py | 8 ++++++++ src/tests/__init__.py | 13 +++++++++++++ src/tests/core.py | 16 +++++++++++++++- 5 files changed, 49 insertions(+), 6 deletions(-) diff --git a/packages/pyinstaller/bitmessagemain.spec b/packages/pyinstaller/bitmessagemain.spec index 103d1ebb..fb2b572d 100644 --- a/packages/pyinstaller/bitmessagemain.spec +++ b/packages/pyinstaller/bitmessagemain.spec @@ -7,6 +7,7 @@ import time from PyInstaller.utils.hooks import copy_metadata +DEBUG = False site_root = os.path.abspath(HOMEPATH) spec_root = os.path.abspath(SPECPATH) arch = 32 if ctypes.sizeof(ctypes.c_voidp) == 4 else 64 @@ -25,6 +26,10 @@ snapshot = False hookspath = os.path.join(spec_root, 'hooks') +excludes = ['bsddb', 'bz2', 'tcl', 'tk', 'Tkinter', 'tests'] +if not DEBUG: + excludes += ['pybitmessage.tests', 'pyelliptic.tests'] + a = Analysis( [os.path.join(srcPath, 'bitmessagemain.py')], datas=[ @@ -39,7 +44,7 @@ a = Analysis( 'plugins.menu_qrcode', 'plugins.proxyconfig_stem' ], runtime_hooks=[os.path.join(hookspath, 'pyinstaller_rthook_plugins.py')], - excludes=['bsddb', 'bz2', 'tcl', 'tk', 'Tkinter', 'tests'] + excludes=excludes ) @@ -122,10 +127,10 @@ exe = EXE( a.zipfiles, a.datas, name=fname, - debug=False, + debug=DEBUG, strip=None, upx=False, - console=False, icon=os.path.join(srcPath, 'images', 'can-icon.ico') + console=DEBUG, icon=os.path.join(srcPath, 'images', 'can-icon.ico') ) coll = COLLECT( diff --git a/src/bitmessagemain.py b/src/bitmessagemain.py index 4118926b..a0a074fe 100755 --- a/src/bitmessagemain.py +++ b/src/bitmessagemain.py @@ -313,8 +313,11 @@ class Main(object): # pylint: disable=relative-import from tests import core as test_core except ImportError: - self.stop() - return + try: + from pybitmessage.tests import core as test_core + except ImportError: + self.stop() + return test_core_result = test_core.run() self.stop() diff --git a/src/pyelliptic/tests/__init__.py b/src/pyelliptic/tests/__init__.py index e69de29b..62bffa9a 100644 --- a/src/pyelliptic/tests/__init__.py +++ b/src/pyelliptic/tests/__init__.py @@ -0,0 +1,8 @@ +import sys + +if getattr(sys, 'frozen', None): + from test_arithmetic import TestArithmetic + from test_blindsig import TestBlindSig + from test_openssl import TestOpenSSL + + __all__ = ["TestArithmetic", "TestBlindSig", "TestOpenSSL"] diff --git a/src/tests/__init__.py b/src/tests/__init__.py index e69de29b..1e5fb7b6 100644 --- a/src/tests/__init__.py +++ b/src/tests/__init__.py @@ -0,0 +1,13 @@ +import sys + +if getattr(sys, 'frozen', None): + from test_addresses import TestAddresses + from test_crypto import TestHighlevelcrypto + from test_l10n import TestL10n + from test_packets import TestSerialize + from test_protocol import TestProtocol + + __all__ = [ + "TestAddresses", "TestHighlevelcrypto", "TestL10n", + "TestProtocol", "TestSerialize" + ] diff --git a/src/tests/core.py b/src/tests/core.py index 1dca92c0..a7247971 100644 --- a/src/tests/core.py +++ b/src/tests/core.py @@ -40,6 +40,7 @@ try: except (OSError, socket.error): tor_port_free = False +frozen = getattr(sys, 'frozen', None) knownnodes_file = os.path.join(state.appdata, 'knownnodes.dat') @@ -298,6 +299,7 @@ class TestCore(unittest.TestCase): return self.fail('Failed to find at least 3 nodes to connect within 360 sec') + @unittest.skipIf(frozen, 'skip fragile test') def test_udp(self): """check default udp setting and presence of Announcer thread""" self.assertTrue( @@ -370,6 +372,7 @@ class TestCore(unittest.TestCase): '''select typeof(msgid) from sent where ackdata=?''', result) self.assertEqual(column_type[0][0] if column_type else '', 'text') + @unittest.skipIf(frozen, 'not packed test_pattern into the bundle') def test_old_knownnodes_pickle(self): """Testing old (v0.6.2) version knownnodes.dat file""" try: @@ -411,10 +414,21 @@ class TestCore(unittest.TestCase): def run(): - """Starts all tests defined in this module""" + """Starts all tests intended for core run""" loader = unittest.defaultTestLoader loader.sortTestMethodsUsing = None suite = loader.loadTestsFromTestCase(TestCore) + if frozen: + try: + from pybitmessage import tests + suite.addTests(loader.loadTestsFromModule(tests)) + except ImportError: + pass + try: + from pyelliptic import tests + suite.addTests(loader.loadTestsFromModule(tests)) + except ImportError: + pass try: import bitmessageqt.tests from xvfbwrapper import Xvfb From 66239e1aa3456fe8930360d8fc41c74e9e90af6f Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Thu, 4 Aug 2022 16:16:26 +0530 Subject: [PATCH 124/424] Add kivy screens_data.json --- src/bitmessagekivy/screens_data.json | 78 ++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 src/bitmessagekivy/screens_data.json diff --git a/src/bitmessagekivy/screens_data.json b/src/bitmessagekivy/screens_data.json new file mode 100644 index 00000000..8ecaf966 --- /dev/null +++ b/src/bitmessagekivy/screens_data.json @@ -0,0 +1,78 @@ +{ + "Inbox": { + "kv_string": "inbox", + "name_screen": "inbox", + "Import": "from pybitmessage.bitmessagekivy.baseclass.inbox import Inbox", + }, + "Sent": { + "kv_string": "sent", + "name_screen": "sent", + "Import": "from pybitmessage.bitmessagekivy.baseclass.sent import Sent", + }, + "Draft": { + "kv_string": "draft", + "name_screen": "draft", + "Import": "from pybitmessage.bitmessagekivy.baseclass.draft import Draft", + }, + "Trash": { + "kv_string": "trash", + "name_screen": "trash", + "Import": "from pybitmessage.bitmessagekivy.baseclass.trash import Trash", + }, + "All Mails": { + "kv_string": "allmails", + "name_screen": "allmails", + "Import": "from pybitmessage.bitmessagekivy.baseclass.allmail import Allmails", + }, + "Address Book": { + "kv_string": "addressbook", + "name_screen": "addressbook", + "Import": "from pybitmessage.bitmessagekivy.baseclass.addressbook import AddressBook", + }, + "Settings": { + "kv_string": "settings", + "name_screen": "set", + "Import": "from pybitmessage.bitmessagekivy.baseclass.settings import Setting", + }, + "Payment": { + "kv_string": "payment", + "name_screen": "payment", + "Import": "from pybitmessage.bitmessagekivy.baseclass.payment import Payment", + }, + "Network status": { + "kv_string": "network", + "name_screen": "networkstat", + "Import": "from pybitmessage.bitmessagekivy.baseclass.network import NetworkStat", + }, + "My addresses": { + "kv_string": "myaddress", + "name_screen": "myaddress", + "Import": "from pybitmessage.bitmessagekivy.baseclass.myaddress import MyAddress", + }, + "MailDetail": { + "kv_string": "maildetail", + "name_screen": "mailDetail", + "Import": "from pybitmessage.bitmessagekivy.baseclass.maildetail import MailDetail", + }, + "Create": { + "kv_string": "msg_composer", + "name_screen": "create", + "Import": "from pybitmessage.bitmessagekivy.baseclass.msg_composer import Create", + }, + "Login": { + "kv_string": "login", + "Import": "from pybitmessage.bitmessagekivy.baseclass.login import *", + }, + "Scanner": { + "kv_string": "scan_screen", + "Import": "from pybitmessage.bitmessagekivy.baseclass.scan_screen import ScanScreen", + }, + "Popups": { + "kv_string": "popup", + "Import": "from pybitmessage.bitmessagekivy.baseclass.popup import *", + }, + "Qrcode": { + "kv_string": "qrcode", + "Import": "from pybitmessage.bitmessagekivy.baseclass.qrcode import ShowQRCode", + }, +} From d85c62a64b2fbcecade15a7465c08bb1ed40b588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20=C5=A0urda?= Date: Mon, 8 Aug 2022 14:47:56 +0800 Subject: [PATCH 125/424] Test snap upload --- .buildbot/snap/build.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.buildbot/snap/build.sh b/.buildbot/snap/build.sh index 30d7e72b..1375b5f4 100755 --- a/.buildbot/snap/build.sh +++ b/.buildbot/snap/build.sh @@ -1,3 +1,7 @@ #!/bin/sh cd packages && snapcraft + +cd .. +mkdir -p ../out +mv packages/pybitmessage*.snap ../out From e5e9955cd9429a5e9d4e116752f48a5f1687f90e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20=C5=A0urda?= Date: Tue, 9 Aug 2022 15:17:48 +0800 Subject: [PATCH 126/424] Record telenium/kivy video - add a ffmpeg process to kivy tests if it detects running inside docker - this approach can be reused for other tests that may benefit from a video - it may be possible to do this in bash, outside of python, but that would exacerbate the process synchronisation issues, as outlined here: - X server must be running before ffmpeg starts, otherwise ffmpeg can't record and quits - ffmpeg must end before the X server ends, as otherwise it segfaults and video will be incomplete - ffmpeg must start before the actual tests start and stop after the tests stop, otherwise a part of the tests will be missing from the recording - this approach I chose here avoids most synchronisation issues. If ffmpeg takes more than 2 seconds to start recording (unlikely), a part of the video will be missing. Also if for some reason ffmpeg is too slow, then also we may end up with a truncated video, at the end. - I chose lossless vp9 codec with webm format, this appears to work on firefox and chrome. Safari refuses to show the video even though it should be supported. --- .buildbot/kivy/Dockerfile | 2 +- tests-kivy.py | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/.buildbot/kivy/Dockerfile b/.buildbot/kivy/Dockerfile index 58c2be08..94c0545f 100644 --- a/.buildbot/kivy/Dockerfile +++ b/.buildbot/kivy/Dockerfile @@ -11,7 +11,7 @@ RUN apt-get install -yq \ build-essential libcap-dev libssl-dev \ libmtdev-dev libpq-dev \ python3-dev python3-pip python3-virtualenv \ - xvfb + xvfb ffmpeg RUN ln -sf /usr/bin/python3 /usr/bin/python diff --git a/tests-kivy.py b/tests-kivy.py index 92b10b86..a2d2130d 100644 --- a/tests-kivy.py +++ b/tests-kivy.py @@ -4,6 +4,10 @@ import random # noseq import sys import unittest +from os import environ, mkdir +from subprocess import Popen, TimeoutExpired +from time import sleep + def unittest_discover(): """Explicit test suite creation""" @@ -13,5 +17,27 @@ def unittest_discover(): if __name__ == "__main__": + with open("/proc/self/cgroup", "rt", encoding='ascii', errors='replace') as f: + in_docker = "docker" in f.read() + + if in_docker: + try: + mkdir("../out") + except FileExistsError: # flake8: noqa:F821 + pass + + ffmpeg = Popen([ # pylint: disable=consider-using-with + "ffmpeg", "-y", "-nostdin", "-f", "x11grab", "-video_size", "vga", + "-draw_mouse", "0", "-i", environ['DISPLAY'], + "-codec:v", "libvpx-vp9", "-lossless", "1", "-r", "30", + "../out/test.webm" + ]) + sleep(2) # let ffmpeg start result = unittest.TextTestRunner(verbosity=2).run(unittest_discover()) + if in_docker: + ffmpeg.terminate() + try: + ffmpeg.wait(10) + except TimeoutExpired: + ffmpeg.kill() sys.exit(not result.wasSuccessful()) From 8214c7448755b7171558fdc3213f45ee7654f21c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20=C5=A0urda?= Date: Wed, 10 Aug 2022 07:54:27 +0800 Subject: [PATCH 127/424] Code quality issues with kivy upload --- tests-kivy.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tests-kivy.py b/tests-kivy.py index a2d2130d..f4e21e8c 100644 --- a/tests-kivy.py +++ b/tests-kivy.py @@ -1,11 +1,11 @@ #!/usr/bin/env python """Custom tests runner script for tox and python3""" +import os import random # noseq +import subprocess import sys import unittest -from os import environ, mkdir -from subprocess import Popen, TimeoutExpired from time import sleep @@ -22,22 +22,22 @@ if __name__ == "__main__": if in_docker: try: - mkdir("../out") - except FileExistsError: # flake8: noqa:F821 + os.mkdir("../out") + except FileExistsError: # noqa:F821 pass - ffmpeg = Popen([ # pylint: disable=consider-using-with - "ffmpeg", "-y", "-nostdin", "-f", "x11grab", "-video_size", "vga", - "-draw_mouse", "0", "-i", environ['DISPLAY'], - "-codec:v", "libvpx-vp9", "-lossless", "1", "-r", "30", - "../out/test.webm" - ]) + ffmpeg = subprocess.Popen([ # pylint: disable=consider-using-with + "ffmpeg", "-y", "-nostdin", "-f", "x11grab", "-video_size", "vga", + "-draw_mouse", "0", "-i", os.environ['DISPLAY'], + "-codec:v", "libvpx-vp9", "-lossless", "1", "-r", "60", + "../out/test.webm" + ]) sleep(2) # let ffmpeg start result = unittest.TextTestRunner(verbosity=2).run(unittest_discover()) if in_docker: ffmpeg.terminate() try: ffmpeg.wait(10) - except TimeoutExpired: + except subprocess.TimeoutExpired: ffmpeg.kill() sys.exit(not result.wasSuccessful()) From 8124e6f3b8b03bd5816650c192525e837540c7a4 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Tue, 9 Aug 2022 17:58:17 +0530 Subject: [PATCH 128/424] Change screen id to make it readable --- src/bitmessagekivy/main.kv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bitmessagekivy/main.kv b/src/bitmessagekivy/main.kv index e9c89796..9599fa58 100644 --- a/src/bitmessagekivy/main.kv +++ b/src/bitmessagekivy/main.kv @@ -231,7 +231,7 @@ MDNavigationLayout: Setting: id:sc9 MyAddress: - id:sc10 + id:id_myaddress AddressBook: id:sc11 Payment: From c1e514d13c3001f2480410f045d4522df75d4138 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Tue, 9 Aug 2022 15:33:32 +0530 Subject: [PATCH 129/424] Add kivy uisignaler --- src/bitmessagekivy/uikivysignaler.py | 34 ++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/bitmessagekivy/uikivysignaler.py diff --git a/src/bitmessagekivy/uikivysignaler.py b/src/bitmessagekivy/uikivysignaler.py new file mode 100644 index 00000000..3acac823 --- /dev/null +++ b/src/bitmessagekivy/uikivysignaler.py @@ -0,0 +1,34 @@ +""" + UI Singnaler for kivy interface +""" + +from threading import Thread +from kivy.app import App +import queues +import state + +from debug import logger +from bitmessagekivy.baseclass.common import kivy_state_variables + + +class UIkivySignaler(Thread): + """Kivy ui signaler""" + + def __init__(self, *args, **kwargs): + super(UIkivySignaler, self).__init__(*args, **kwargs) + self.kivy_state = kivy_state_variables() + + def run(self): + self.kivy_state.kivyui_ready.wait() + while state.shutdown == 0: + try: + command, data = queues.UISignalQueue.get() + if command == 'writeNewAddressToTable': + address = data[1] + App.get_running_app().variable_1.append(address) + elif command == 'updateSentItemStatusByAckdata': + App.get_running_app().status_dispatching(data) + elif command == 'writeNewpaymentAddressToTable': + pass + except Exception as e: + logger.debug(e) From cb72b65264ebe5c7e85fbe497ed2090aa75ae3dc Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Mon, 8 Aug 2022 19:52:25 +0530 Subject: [PATCH 130/424] Add Kivy default image path to kivy_state.py --- src/bitmessagekivy/kivy_state.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/bitmessagekivy/kivy_state.py b/src/bitmessagekivy/kivy_state.py index 6ce16610..2f74cf89 100644 --- a/src/bitmessagekivy/kivy_state.py +++ b/src/bitmessagekivy/kivy_state.py @@ -5,6 +5,9 @@ Kivy State variables are assigned here, they are separated from state.py ================================= """ +import os +import threading + class KivyStateVariables(object): """This Class hold all the kivy state variables""" @@ -33,4 +36,5 @@ class KivyStateVariables(object): self.available_credit = 0 self.in_sent_method = False self.in_search_mode = False - self.image_dir = None + self.image_dir = os.path.abspath(os.path.join('images', 'kivy')) + self.kivyui_ready = threading.Event() From 5efd072debd5ef61f426b762dc24f345e7180c28 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Wed, 10 Aug 2022 13:53:10 +0530 Subject: [PATCH 131/424] Add identity_list instead of variable_1 --- src/bitmessagekivy/uikivysignaler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bitmessagekivy/uikivysignaler.py b/src/bitmessagekivy/uikivysignaler.py index 3acac823..71bed933 100644 --- a/src/bitmessagekivy/uikivysignaler.py +++ b/src/bitmessagekivy/uikivysignaler.py @@ -25,7 +25,7 @@ class UIkivySignaler(Thread): command, data = queues.UISignalQueue.get() if command == 'writeNewAddressToTable': address = data[1] - App.get_running_app().variable_1.append(address) + App.get_running_app().identity_list.append(address) elif command == 'updateSentItemStatusByAckdata': App.get_running_app().status_dispatching(data) elif command == 'writeNewpaymentAddressToTable': From 4711dfdd938b92b63a7d99065a828f66ecc7131d Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Fri, 12 Aug 2022 05:30:18 +0300 Subject: [PATCH 132/424] Use $APPIMAGE to determine exe folder and enable portable mode for AppImage --- src/paths.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/paths.py b/src/paths.py index 8cde08af..e0d43334 100644 --- a/src/paths.py +++ b/src/paths.py @@ -21,14 +21,15 @@ def lookupExeFolder(): if frozen: exeFolder = ( # targetdir/Bitmessage.app/Contents/MacOS/Bitmessage - os.path.dirname(sys.executable).split(os.path.sep)[0] + os.path.sep - if frozen == "macosx_app" else - os.path.dirname(sys.executable) + os.path.sep) + os.path.dirname(sys.executable).split(os.path.sep)[0] + if frozen == "macosx_app" else os.path.dirname(sys.executable)) + elif os.getenv('APPIMAGE'): + exeFolder = os.path.dirname(os.getenv('APPIMAGE')) elif __file__: - exeFolder = os.path.dirname(__file__) + os.path.sep + exeFolder = os.path.dirname(__file__) else: - exeFolder = '' - return exeFolder + return '' + return exeFolder + os.path.sep def lookupAppdataFolder(): From 746e25c752227ade3764b415533f204d2246afc7 Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Sun, 14 Aug 2022 01:14:06 +0300 Subject: [PATCH 133/424] Add support for startonlogon in appimage --- packages/AppImage/PyBitmessage.yml | 1 + src/plugins/desktop_xdg.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/packages/AppImage/PyBitmessage.yml b/packages/AppImage/PyBitmessage.yml index a8948a3c..3eeaef64 100644 --- a/packages/AppImage/PyBitmessage.yml +++ b/packages/AppImage/PyBitmessage.yml @@ -14,6 +14,7 @@ ingredients: - python-setuptools - python-sip - python-six + - python-xdg - sni-qt exclude: - libdb5.3 diff --git a/src/plugins/desktop_xdg.py b/src/plugins/desktop_xdg.py index 3dbd212f..7e1c3586 100644 --- a/src/plugins/desktop_xdg.py +++ b/src/plugins/desktop_xdg.py @@ -11,6 +11,9 @@ class DesktopXDG(object): menu_entry = Menu.parse().getMenu('Office').getMenuEntry( 'pybitmessage.desktop') self.desktop = menu_entry.DesktopEntry if menu_entry else None + appimage = os.getenv('APPIMAGE') + if appimage: + self.desktop.set('Exec', appimage) def adjust_startonlogon(self, autostart=False): """Configure autostart according to settings""" From bd33361b0bd41f2a85ced31cb60b9c5b31410320 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Tue, 16 Aug 2022 13:17:58 +0530 Subject: [PATCH 134/424] Fix Screen_data.json --- src/bitmessagekivy/screens_data.json | 34 ++++++++++++++-------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/bitmessagekivy/screens_data.json b/src/bitmessagekivy/screens_data.json index 8ecaf966..95ae228b 100644 --- a/src/bitmessagekivy/screens_data.json +++ b/src/bitmessagekivy/screens_data.json @@ -2,77 +2,77 @@ "Inbox": { "kv_string": "inbox", "name_screen": "inbox", - "Import": "from pybitmessage.bitmessagekivy.baseclass.inbox import Inbox", + "Import": "from pybitmessage.bitmessagekivy.baseclass.inbox import Inbox" }, "Sent": { "kv_string": "sent", "name_screen": "sent", - "Import": "from pybitmessage.bitmessagekivy.baseclass.sent import Sent", + "Import": "from pybitmessage.bitmessagekivy.baseclass.sent import Sent" }, "Draft": { "kv_string": "draft", "name_screen": "draft", - "Import": "from pybitmessage.bitmessagekivy.baseclass.draft import Draft", + "Import": "from pybitmessage.bitmessagekivy.baseclass.draft import Draft" }, "Trash": { "kv_string": "trash", "name_screen": "trash", - "Import": "from pybitmessage.bitmessagekivy.baseclass.trash import Trash", + "Import": "from pybitmessage.bitmessagekivy.baseclass.trash import Trash" }, "All Mails": { "kv_string": "allmails", "name_screen": "allmails", - "Import": "from pybitmessage.bitmessagekivy.baseclass.allmail import Allmails", + "Import": "from pybitmessage.bitmessagekivy.baseclass.allmail import Allmails" }, "Address Book": { "kv_string": "addressbook", "name_screen": "addressbook", - "Import": "from pybitmessage.bitmessagekivy.baseclass.addressbook import AddressBook", + "Import": "from pybitmessage.bitmessagekivy.baseclass.addressbook import AddressBook" }, "Settings": { "kv_string": "settings", "name_screen": "set", - "Import": "from pybitmessage.bitmessagekivy.baseclass.settings import Setting", + "Import": "from pybitmessage.bitmessagekivy.baseclass.settings import Setting" }, "Payment": { "kv_string": "payment", "name_screen": "payment", - "Import": "from pybitmessage.bitmessagekivy.baseclass.payment import Payment", + "Import": "from pybitmessage.bitmessagekivy.baseclass.payment import Payment" }, "Network status": { "kv_string": "network", "name_screen": "networkstat", - "Import": "from pybitmessage.bitmessagekivy.baseclass.network import NetworkStat", + "Import": "from pybitmessage.bitmessagekivy.baseclass.network import NetworkStat" }, "My addresses": { "kv_string": "myaddress", "name_screen": "myaddress", - "Import": "from pybitmessage.bitmessagekivy.baseclass.myaddress import MyAddress", + "Import": "from pybitmessage.bitmessagekivy.baseclass.myaddress import MyAddress" }, "MailDetail": { "kv_string": "maildetail", "name_screen": "mailDetail", - "Import": "from pybitmessage.bitmessagekivy.baseclass.maildetail import MailDetail", + "Import": "from pybitmessage.bitmessagekivy.baseclass.maildetail import MailDetail" }, "Create": { "kv_string": "msg_composer", "name_screen": "create", - "Import": "from pybitmessage.bitmessagekivy.baseclass.msg_composer import Create", + "Import": "from pybitmessage.bitmessagekivy.baseclass.msg_composer import Create" }, "Login": { "kv_string": "login", - "Import": "from pybitmessage.bitmessagekivy.baseclass.login import *", + "Import": "from pybitmessage.bitmessagekivy.baseclass.login import *" }, "Scanner": { "kv_string": "scan_screen", - "Import": "from pybitmessage.bitmessagekivy.baseclass.scan_screen import ScanScreen", + "Import": "from pybitmessage.bitmessagekivy.baseclass.scan_screen import ScanScreen" }, "Popups": { "kv_string": "popup", - "Import": "from pybitmessage.bitmessagekivy.baseclass.popup import *", + "Import": "from pybitmessage.bitmessagekivy.baseclass.popup import *" }, "Qrcode": { "kv_string": "qrcode", - "Import": "from pybitmessage.bitmessagekivy.baseclass.qrcode import ShowQRCode", - }, + "Import": "from pybitmessage.bitmessagekivy.baseclass.qrcode import ShowQRCode" + } } From c0ae18e452d0da282c1f5fff9bbe04e815943aba Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Tue, 16 Aug 2022 18:21:58 +0530 Subject: [PATCH 135/424] Update widget id names in login screen --- src/bitmessagekivy/baseclass/login.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/bitmessagekivy/baseclass/login.py b/src/bitmessagekivy/baseclass/login.py index 0d27838d..6da79acf 100644 --- a/src/bitmessagekivy/baseclass/login.py +++ b/src/bitmessagekivy/baseclass/login.py @@ -61,9 +61,9 @@ class Random(Screen): def address_created_callback(self, dt=0): """New address created""" App.get_running_app().loadMyAddressScreen(False) - App.get_running_app().root.ids.sc10.ids.ml.clear_widgets() - App.get_running_app().root.ids.sc10.is_add_created = True - App.get_running_app().root.ids.sc10.init_ui() + App.get_running_app().root.ids.id_myaddress.ids.ml.clear_widgets() + App.get_running_app().root.ids.id_myaddress.is_add_created = True + App.get_running_app().root.ids.id_myaddress.init_ui() self.reset_address_spinner() toast('New address created') @@ -71,10 +71,10 @@ class Random(Screen): """reseting spinner address and UI""" addresses = [addr for addr in config.addresses() if config.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 + self.manager.parent.ids.content_drawer.ids.identity_dropdown.values = [] + self.manager.parent.ids.sc3.children[1].ids.identity_dropdown.values = [] + self.manager.parent.ids.content_drawer.ids.identity_dropdown.values = addresses + self.manager.parent.ids.sc3.children[1].ids.identity_dropdown.values = addresses @staticmethod def add_validation(instance): From c2281409e0d56a56c055fe70386b83a019acbf92 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Wed, 6 Jul 2022 17:00:37 +0530 Subject: [PATCH 136/424] Add common method for all mail folder to get mail detail screen --- src/bitmessagekivy/baseclass/common.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/bitmessagekivy/baseclass/common.py b/src/bitmessagekivy/baseclass/common.py index 25f52b04..bf204464 100644 --- a/src/bitmessagekivy/baseclass/common.py +++ b/src/bitmessagekivy/baseclass/common.py @@ -1,4 +1,5 @@ -# pylint: disable=no-name-in-module, attribute-defined-outside-init, import-error +# pylint: disable=no-name-in-module, attribute-defined-outside-init, import-error, unused-argument + """ All Common widgets of kivy are managed here. """ @@ -182,3 +183,19 @@ def mdlist_message_content(queryreturn, data): for mail in queryreturn: mdlist_data = set_mail_details(mail) data.append(mdlist_data) + + +def mail_detail_screen(screen_name, msg_id, instance, folder, *args): + """Common function for all screens to open Mail detail.""" + kivy_state = kivy_state_variables() + if instance.open_progress == 0.0: + kivy_state.detailPageType = folder + kivy_state.mail_id = msg_id + if screen_name.manager: + src_mng_obj = screen_name.manager + else: + src_mng_obj = screen_name.parent.parent + src_mng_obj.screens[11].clear_widgets() + from bitmessagekivy.baseclass.maildetail import MailDetail + src_mng_obj.screens[11].add_widget(MailDetail()) + src_mng_obj.current = "mailDetail" From 328d5af3ecc8e405205ecebbc455b8520dc19f6c Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Mon, 11 Jul 2022 15:40:57 +0530 Subject: [PATCH 137/424] Separate common_detail_screen() to avoid the circular import --- src/bitmessagekivy/baseclass/common.py | 16 -------------- .../baseclass/common_mail_detail.py | 22 +++++++++++++++++++ 2 files changed, 22 insertions(+), 16 deletions(-) create mode 100644 src/bitmessagekivy/baseclass/common_mail_detail.py diff --git a/src/bitmessagekivy/baseclass/common.py b/src/bitmessagekivy/baseclass/common.py index bf204464..80dc1672 100644 --- a/src/bitmessagekivy/baseclass/common.py +++ b/src/bitmessagekivy/baseclass/common.py @@ -183,19 +183,3 @@ def mdlist_message_content(queryreturn, data): for mail in queryreturn: mdlist_data = set_mail_details(mail) data.append(mdlist_data) - - -def mail_detail_screen(screen_name, msg_id, instance, folder, *args): - """Common function for all screens to open Mail detail.""" - kivy_state = kivy_state_variables() - if instance.open_progress == 0.0: - kivy_state.detailPageType = folder - kivy_state.mail_id = msg_id - if screen_name.manager: - src_mng_obj = screen_name.manager - else: - src_mng_obj = screen_name.parent.parent - src_mng_obj.screens[11].clear_widgets() - from bitmessagekivy.baseclass.maildetail import MailDetail - src_mng_obj.screens[11].add_widget(MailDetail()) - src_mng_obj.current = "mailDetail" diff --git a/src/bitmessagekivy/baseclass/common_mail_detail.py b/src/bitmessagekivy/baseclass/common_mail_detail.py new file mode 100644 index 00000000..773d60ca --- /dev/null +++ b/src/bitmessagekivy/baseclass/common_mail_detail.py @@ -0,0 +1,22 @@ +# pylint: disable=no-name-in-module, attribute-defined-outside-init, import-error +""" + All Common widgets of kivy are managed here. +""" + +from bitmessagekivy.baseclass.maildetail import MailDetail +from bitmessagekivy.baseclass.common import kivy_state_variables + + +def mail_detail_screen(screen_name, msg_id, instance, folder, *args): # pylint: disable=unused-argument + """Common function for all screens to open Mail detail.""" + kivy_state = kivy_state_variables() + if instance.open_progress == 0.0: + kivy_state.detailPageType = folder + kivy_state.mail_id = msg_id + if screen_name.manager: + src_mng_obj = screen_name.manager + else: + src_mng_obj = screen_name.parent.parent + src_mng_obj.screens[11].clear_widgets() + src_mng_obj.screens[11].add_widget(MailDetail()) + src_mng_obj.current = "mailDetail" From c6a004367ade67de8b5030244aeaa55dc39a0bc3 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Wed, 13 Jul 2022 19:58:27 +0530 Subject: [PATCH 138/424] Add method content lenght function --- src/bitmessagekivy/baseclass/common.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/bitmessagekivy/baseclass/common.py b/src/bitmessagekivy/baseclass/common.py index 80dc1672..db95e565 100644 --- a/src/bitmessagekivy/baseclass/common.py +++ b/src/bitmessagekivy/baseclass/common.py @@ -1,5 +1,4 @@ -# pylint: disable=no-name-in-module, attribute-defined-outside-init, import-error, unused-argument - +# pylint: disable=no-name-in-module, attribute-defined-outside-init, import-error """ All Common widgets of kivy are managed here. """ @@ -183,3 +182,13 @@ def mdlist_message_content(queryreturn, data): for mail in queryreturn: mdlist_data = set_mail_details(mail) data.append(mdlist_data) + + +def msg_content_length(body, subject, max_length=50): + """This function concatinate body and subject if len(subject) > 50""" + continue_str = '........' + if len(subject) >= max_length: + subject = subject[:max_length] + continue_str + else: + subject = ((subject + ',' + body)[0:50] + continue_str).replace('\t', '').replace(' ', '') + return subject From af6953b91674e59e0364a7a93ea14700e3049eb7 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Fri, 29 Jul 2022 13:09:01 +0530 Subject: [PATCH 139/424] Add images and icons used in Kivy App --- src/images/kivy/down-arrow.png | Bin 0 -> 2384 bytes src/images/kivy/draft-icon.png | Bin 0 -> 14300 bytes src/images/kivy/loader.gif | Bin 0 -> 16167 bytes src/images/kivy/right-arrow.png | Bin 0 -> 3145 bytes src/images/kivy/search.png | Bin 0 -> 2982 bytes src/images/kivy/text_images/!.png | Bin 0 -> 5035 bytes src/images/kivy/text_images/0.png | Bin 0 -> 7321 bytes src/images/kivy/text_images/1.png | Bin 0 -> 5027 bytes src/images/kivy/text_images/2.png | Bin 0 -> 6916 bytes src/images/kivy/text_images/3.png | Bin 0 -> 7265 bytes src/images/kivy/text_images/4.png | Bin 0 -> 5891 bytes src/images/kivy/text_images/5.png | Bin 0 -> 6831 bytes src/images/kivy/text_images/6.png | Bin 0 -> 7644 bytes src/images/kivy/text_images/7.png | Bin 0 -> 5941 bytes src/images/kivy/text_images/8.png | Bin 0 -> 7777 bytes src/images/kivy/text_images/9.png | Bin 0 -> 7733 bytes src/images/kivy/text_images/A.png | Bin 0 -> 6857 bytes src/images/kivy/text_images/B.png | Bin 0 -> 6533 bytes src/images/kivy/text_images/C.png | Bin 0 -> 7662 bytes src/images/kivy/text_images/D.png | Bin 0 -> 6381 bytes src/images/kivy/text_images/E.png | Bin 0 -> 5009 bytes src/images/kivy/text_images/F.png | Bin 0 -> 4972 bytes src/images/kivy/text_images/G.png | Bin 0 -> 7593 bytes src/images/kivy/text_images/H.png | Bin 0 -> 5054 bytes src/images/kivy/text_images/I.png | Bin 0 -> 4713 bytes src/images/kivy/text_images/J.png | Bin 0 -> 5841 bytes src/images/kivy/text_images/K.png | Bin 0 -> 6719 bytes src/images/kivy/text_images/L.png | Bin 0 -> 4878 bytes src/images/kivy/text_images/M.png | Bin 0 -> 6569 bytes src/images/kivy/text_images/N.png | Bin 0 -> 6509 bytes src/images/kivy/text_images/O.png | Bin 0 -> 7912 bytes src/images/kivy/text_images/P.png | Bin 0 -> 5914 bytes src/images/kivy/text_images/Q.png | Bin 0 -> 8271 bytes src/images/kivy/text_images/R.png | Bin 0 -> 6404 bytes src/images/kivy/text_images/S.png | Bin 0 -> 7826 bytes src/images/kivy/text_images/T.png | Bin 0 -> 4793 bytes src/images/kivy/text_images/U.png | Bin 0 -> 6113 bytes src/images/kivy/text_images/V.png | Bin 0 -> 6761 bytes src/images/kivy/text_images/W.png | Bin 0 -> 7805 bytes src/images/kivy/text_images/X.png | Bin 0 -> 7367 bytes src/images/kivy/text_images/Y.png | Bin 0 -> 6459 bytes src/images/kivy/text_images/Z.png | Bin 0 -> 6166 bytes src/images/kivymd_logo.png | Bin 42074 -> 0 bytes src/images/me.jpg | Bin 31355 -> 0 bytes src/images/ngletteravatar/1.png | Bin 1194 -> 0 bytes src/images/ngletteravatar/12.png | Bin 1914 -> 0 bytes src/images/ngletteravatar/14.png | Bin 1249 -> 0 bytes src/images/ngletteravatar/3.png | Bin 1335 -> 0 bytes src/images/ngletteravatar/5.png | Bin 1379 -> 0 bytes src/images/ngletteravatar/56.png | Bin 3247 -> 0 bytes src/images/ngletteravatar/65.png | Bin 822 -> 0 bytes src/images/ngletteravatar/8.png | Bin 1316 -> 0 bytes src/images/ngletteravatar/90.png | Bin 9685 -> 0 bytes .../Galleryr_rcirclelogo_Small.jpg | Bin 36701 -> 0 bytes src/images/ngletteravatar/a.png | Bin 2378 -> 0 bytes src/images/ngletteravatar/b.png | Bin 4732 -> 0 bytes src/images/ngletteravatar/c.png | Bin 1637 -> 0 bytes src/images/ngletteravatar/d.png | Bin 1705 -> 0 bytes ...ustration-letter-l-sign-design-template.jpg | Bin 243858 -> 0 bytes src/images/ngletteravatar/e.png | Bin 631 -> 0 bytes src/images/ngletteravatar/g.png | Bin 1491 -> 0 bytes src/images/ngletteravatar/h.png | Bin 537 -> 0 bytes src/images/ngletteravatar/i.png | Bin 483 -> 0 bytes src/images/ngletteravatar/j.png | Bin 656 -> 0 bytes src/images/ngletteravatar/k.png | Bin 938 -> 0 bytes src/images/ngletteravatar/l.png | Bin 806 -> 0 bytes src/images/ngletteravatar/m.png | Bin 1314 -> 0 bytes src/images/ngletteravatar/n.png | Bin 6277 -> 0 bytes src/images/ngletteravatar/o.png | Bin 2112 -> 0 bytes src/images/ngletteravatar/p.png | Bin 783 -> 0 bytes src/images/ngletteravatar/r.png | Bin 1378 -> 0 bytes src/images/ngletteravatar/s.png | Bin 2466 -> 0 bytes src/images/ngletteravatar/t.jpg | Bin 4733 -> 0 bytes src/images/ngletteravatar/u.png | Bin 972 -> 0 bytes src/images/ngletteravatar/v.png | Bin 2284 -> 0 bytes src/images/ngletteravatar/w.png | Bin 5622 -> 0 bytes src/images/ngletteravatar/x.jpg | Bin 9071 -> 0 bytes src/images/ngletteravatar/z.png | Bin 750 -> 0 bytes 78 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/images/kivy/down-arrow.png create mode 100644 src/images/kivy/draft-icon.png create mode 100644 src/images/kivy/loader.gif create mode 100644 src/images/kivy/right-arrow.png create mode 100644 src/images/kivy/search.png create mode 100644 src/images/kivy/text_images/!.png create mode 100644 src/images/kivy/text_images/0.png create mode 100644 src/images/kivy/text_images/1.png create mode 100644 src/images/kivy/text_images/2.png create mode 100644 src/images/kivy/text_images/3.png create mode 100644 src/images/kivy/text_images/4.png create mode 100644 src/images/kivy/text_images/5.png create mode 100644 src/images/kivy/text_images/6.png create mode 100644 src/images/kivy/text_images/7.png create mode 100644 src/images/kivy/text_images/8.png create mode 100644 src/images/kivy/text_images/9.png create mode 100644 src/images/kivy/text_images/A.png create mode 100644 src/images/kivy/text_images/B.png create mode 100644 src/images/kivy/text_images/C.png create mode 100644 src/images/kivy/text_images/D.png create mode 100644 src/images/kivy/text_images/E.png create mode 100644 src/images/kivy/text_images/F.png create mode 100644 src/images/kivy/text_images/G.png create mode 100644 src/images/kivy/text_images/H.png create mode 100644 src/images/kivy/text_images/I.png create mode 100644 src/images/kivy/text_images/J.png create mode 100644 src/images/kivy/text_images/K.png create mode 100644 src/images/kivy/text_images/L.png create mode 100644 src/images/kivy/text_images/M.png create mode 100644 src/images/kivy/text_images/N.png create mode 100644 src/images/kivy/text_images/O.png create mode 100644 src/images/kivy/text_images/P.png create mode 100644 src/images/kivy/text_images/Q.png create mode 100644 src/images/kivy/text_images/R.png create mode 100644 src/images/kivy/text_images/S.png create mode 100644 src/images/kivy/text_images/T.png create mode 100644 src/images/kivy/text_images/U.png create mode 100644 src/images/kivy/text_images/V.png create mode 100644 src/images/kivy/text_images/W.png create mode 100644 src/images/kivy/text_images/X.png create mode 100644 src/images/kivy/text_images/Y.png create mode 100644 src/images/kivy/text_images/Z.png delete mode 100644 src/images/kivymd_logo.png delete mode 100644 src/images/me.jpg delete mode 100644 src/images/ngletteravatar/1.png delete mode 100644 src/images/ngletteravatar/12.png delete mode 100644 src/images/ngletteravatar/14.png delete mode 100644 src/images/ngletteravatar/3.png delete mode 100644 src/images/ngletteravatar/5.png delete mode 100644 src/images/ngletteravatar/56.png delete mode 100644 src/images/ngletteravatar/65.png delete mode 100644 src/images/ngletteravatar/8.png delete mode 100644 src/images/ngletteravatar/90.png delete mode 100644 src/images/ngletteravatar/Galleryr_rcirclelogo_Small.jpg delete mode 100644 src/images/ngletteravatar/a.png delete mode 100644 src/images/ngletteravatar/b.png delete mode 100644 src/images/ngletteravatar/c.png delete mode 100644 src/images/ngletteravatar/d.png delete mode 100644 src/images/ngletteravatar/depositphotos_142729281-stock-illustration-letter-l-sign-design-template.jpg delete mode 100644 src/images/ngletteravatar/e.png delete mode 100644 src/images/ngletteravatar/g.png delete mode 100644 src/images/ngletteravatar/h.png delete mode 100644 src/images/ngletteravatar/i.png delete mode 100644 src/images/ngletteravatar/j.png delete mode 100644 src/images/ngletteravatar/k.png delete mode 100644 src/images/ngletteravatar/l.png delete mode 100644 src/images/ngletteravatar/m.png delete mode 100644 src/images/ngletteravatar/n.png delete mode 100644 src/images/ngletteravatar/o.png delete mode 100644 src/images/ngletteravatar/p.png delete mode 100644 src/images/ngletteravatar/r.png delete mode 100644 src/images/ngletteravatar/s.png delete mode 100644 src/images/ngletteravatar/t.jpg delete mode 100644 src/images/ngletteravatar/u.png delete mode 100644 src/images/ngletteravatar/v.png delete mode 100644 src/images/ngletteravatar/w.png delete mode 100644 src/images/ngletteravatar/x.jpg delete mode 100644 src/images/ngletteravatar/z.png diff --git a/src/images/kivy/down-arrow.png b/src/images/kivy/down-arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..bf3e864c8093738d0ab553d60af018d11f9a2ebb GIT binary patch literal 2384 zcmcImX;_l!8h()xN+DWYQtIH)Ch3T$CarTC#Thega!n1VQgTa2##|~)ktNG_lwLKnj9wHj(X*Q9KkO6;uM?Y zxf)s?kX)88LBQD*m*ptLsRtCECkkQp06Hx}6hPQ&6oh@WkWs|dD6L%J_%}y2SNL3( zC%yC9#7LkwwvA)8wL-o6AICy;_;?Jtei{a>fh!=vfDH%(EGu9Kc;J71B8&c7x(27$ zw}a^MOag1Gq4spqb%In^lQ$!>8rK;)-;@`*UszO$muJp$Vb#8~ME}I$g}dtbwog*< zHu8tjB;9HJmhVksbwt|?2BwHMAtgznoADLDwJq#_U&OS{;J6`~?4oI|L?d)%+2n3w6OC5V+6P+*{gGESC8e7e$U8rncC$ z{f?CRmKRYu>UUhUBFL7a9$6&&4+YCUe1o-rO4>GzF1lSM(HpL6oSQC+VdXkau0-JYa^!t4ak7*bQ>E@vtC z)_Vr?>;_v35wcay3DOzT+z)}rKOasaD;wx5Z6)WOtiy);O#EHWz2oH{J(BH7qv<3i zckew#+&UpBAqss@mMeFjdqndTgOWy}1Xj+cy^lt)jBVSA*5>LC|w+Qyp?WPcXHQjkm9WJsCqgNZzCDhAA{GxHb46?cM7PyWZyS7yoa5tO#y}76(3S87Pn@3yX@nOKQmQSsA-e?yn>~>P1)DM ziLfCUo9%m{K@9;^gnEpZO zTW>=Nm_jHUP(llLy*D(!HP4|Yc-VKG8b)_rL*20eiO>LAEXltf@zbqxO?Aq$*gBiT zS#asmoOCX*42rj63h}zK4*Gxjm=|T)qv;eh0bAGEIGesXpY?>ZGN)Eg?dc;_@nRX) z8F(5zA$gmnLn$u>8Qz`|J%1e0Gf(T+^dnOS$C|e6{I=)G_)HhG_6o;`^nMsUT@&>= z3*5U?PHH4n)ykxy1O3Pkk0u4&gR&iZ{9^xYT>HQ)!`g8TqTd*m&%3v*>gwL6bMA-VeHm(aJ}VBISwUa8gb;=UE?S-C~{^N(?7R&YG&Vzh8QDOVJ3f zDAOq>vmn@zc>f4AlzGgJn*}^bQ6y0aC>Uw3}7DY1sDu6dGJ-z*hQzr_N($yc5Fx_y4LEvzsS! zuCk>otY6$n`?tF1;|+w5uN_IBYe~qSh7AtZ+|v`c>9MnIVW6}1fYxE-*kL$#l?{db zl>$AeR_~n$)G=|eqRV=p*J`QPM;Q+k?q~G+$W-kI*GnHVn3wvIl(ZYeW-fUisFZZ& zTQn#5aVeU<%$4Ogk#0f9R|kz;J%JFEVR6ILes$JBCSaR!jw6Jdp<}$zVU<@ z>snP9O-Ekz0i~Rdfd;AeqXt9F2M*zR-V_OQ17z>-j`2dM6X7Y3Y3AdnmD^L3TBs{y z(Qs<#f#g=8|scm7i6~YUrf|f_8=Y-~5(x>c0{SPHX@G literal 0 HcmV?d00001 diff --git a/src/images/kivy/draft-icon.png b/src/images/kivy/draft-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..9fc38f3107db53490a92e8a3fe4708fac312ed03 GIT binary patch literal 14300 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 literal 0 HcmV?d00001 diff --git a/src/images/kivy/loader.gif b/src/images/kivy/loader.gif new file mode 100644 index 0000000000000000000000000000000000000000..34ab194314bc93f8c9b3d0f9fb4e636deb630e4d GIT binary patch literal 16167 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 literal 0 HcmV?d00001 diff --git a/src/images/kivy/text_images/!.png b/src/images/kivy/text_images/!.png new file mode 100644 index 0000000000000000000000000000000000000000..bac2f2461db6556140398a6cd73595b7e30d603a GIT binary patch literal 5035 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 literal 0 HcmV?d00001 diff --git a/src/images/kivy/text_images/0.png b/src/images/kivy/text_images/0.png new file mode 100644 index 0000000000000000000000000000000000000000..2b8b63e3a79f5359fe81085211f5bd2cb84aa784 GIT binary patch literal 7321 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 SCbXY#0Ha%`H>-6WBmWD_4en|H literal 0 HcmV?d00001 diff --git a/src/images/kivy/text_images/2.png b/src/images/kivy/text_images/2.png new file mode 100644 index 0000000000000000000000000000000000000000..0cf202e95b89b345d8363939a8ba195b05cfb542 GIT binary patch literal 6916 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 KMNUMnLSTX>U6tto literal 0 HcmV?d00001 diff --git a/src/images/kivy/text_images/3.png b/src/images/kivy/text_images/3.png new file mode 100644 index 0000000000000000000000000000000000000000..f9d612dd8b00cd754006110d0365087fdb3b32a5 GIT binary patch literal 7265 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ñU+Fza+h~R5vpg8_LIVMa5TU~B_MS$~w*1qN_?($zvY%RD*Nq|uP0|UrN3ZDTv z(Cn0g;-{LQ^BS>jY4`1M5HHP6Ai!^+sq;2jK*Gn8a7 zzT6~1N?@280ZlMFrJ`Waof@}*UY%?*kA5@pZJXQqm`|}XX@<_)7w!^8IlMeO9Olo+ zV0+yuL}t4miuV{LTG{Qu$^l`Ebgm0TN?oi0C$^1H9KbPe?N?k-FxXuCvYR--{nZ(c zBtzlj#0%$)xT)TF>`QahJrkPs{qTPR&;e#{%RuKlypLMqGrUsL%tSf&>lL@GH%GG1 zeoL?d2bO^Y-#-^P7o;fPZ54T8lAsP5RvbAs?azCW`ylOk_Hul>`2Gd7dN}%U(lUaA z;_a?q4Zd*8oO9QIVBXM%W&G`@NkjnSc88^kCtZiim~Zn(ckAsYvgrRuLR1N^(9D1g za;C33YzXCm?{yC~Iv5xJM4`7U-tKzO@jq~F@3h^%DFTmKauy}{#pV*iA^wauF30kB zdTFe{|&MDJ_KWO$Ar9e`rECPIYWy@Pv1iELwx4f z`IW)$X59 znGE zE%w_++WE$>P=Q>U6=HH>g30nq^;g}8@^p6C$ncZee2)0qn@;|5U_~2tEI75Dc1nvk z7V?m)J;to-<`mpFZJ_@Xxmw@(u&+Mz2IKw3$|Ao65tFN75{L17F_(K8yEjQ_L7;6^CIp1+!zjbOy>nX=yemXP$m-BmEeEl91hv4;Bl zWuTEnFAf*qXU_(nkcIkUbWN|8Si|dN3rx>U1Mbrqk50(LT9YXMjAmyPd@CZ6`{X2V z<1ukh9M8g{{(F9ETVok2;}-fGc{Lrt`MgkaUJ|S`!q*_@~*%Dm^i4oUhTzvH}{>Kslsk5Bw;$wbBmsl z#^AVNI=l1uViI2Pkceo3nJ;ahD2<}z=ht>A&7NzLIneY%p=w>=&5tO6u9m)Lt%hyb F{{ionjlKW? literal 0 HcmV?d00001 diff --git a/src/images/kivy/text_images/6.png b/src/images/kivy/text_images/6.png new file mode 100644 index 0000000000000000000000000000000000000000..e385a954ba4c14f366fe1ce4c8c848d5351207d3 GIT binary patch literal 7644 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 E2Ua8%FaQ7m literal 0 HcmV?d00001 diff --git a/src/images/kivy/text_images/7.png b/src/images/kivy/text_images/7.png new file mode 100644 index 0000000000000000000000000000000000000000..55fc4f77d428bb159e85a6516fc53ddea20c8c3a GIT binary patch literal 5941 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 T^j#y4E-5M%r zm?VX0jeJNSEgU@zE7!1;6?se|Rdw-h@TGT;q{d&lA`LG0Br7}4`dC8r%DsnlqyBkv z=YZXw3&^jWq<6l%a%k4)8=|jx=FRiS9|kgB0JztAN`lLZggAM?xv#j!{Hrs>DoYXl zMl)DWm34Q92~Yg3;qr9L%bDvr#`9EDK_dS#Ckv=KuUOuE-61W*-;GhrSkelLjOsU@ zO-yZEE8W6i0en)o!!t0a_Ahq^GJDzMgJZ*SSO)ZeoN*odWedgDwb+zL<66yWGzBUW zyC2#6>@fD)frV`2!VP`NLT39*fZ8Rv4zy!=aCNs@9fld=)M~XBDf;EFoTu%$ZA`eK zQPa;r2D+n!F%`5sl~F>LentF4x^GGoId;eg4(gK8V0BKB#XrE?U^G>q7|60#cip~* zMiI&q`#aT=6-RnE3#i2b!3QQZ;Drt|80@&;WU;-cuADKQ_>V%j=u36hn&UB}sQ-YltPJsS2JF;-`>rv@{&42Wk_N zNZ-5IT|lg8iqDQ%Tk>s;&&-g%0j|nv1OQjZdc+{#oe?|TOuhJ44>G&gs$Oe$|GeCj zZ1cKtb6#;(!3(!j_(z;>M>SbvT{6x@0{UuqzBYhc#p-rVjaf4qW-8+w3%$9%)4~7T zwmRX$-mDbH^GR>2a_MveMAdaNN_^HlaSh|9der zQSapz6*)9Cg4lc78m4|(n>JQoL^^%W0@gudv~&_)7eNw3*)Xvxkw+3;3JT$NkT(ow z{`U){)vtt#7vqF=jK-!3QEo`af9!^Di)ej>a2U#e{bvi2H~f&kYZvra0<;}iO$94&J#h#f-%1J z`i-0HSql2>MNKIdL+Huy4PimMNoh9rJ9yRoBh?*rVD(aqG7w6#lRp!WCED zf11xSr=@Q<-|F)ivThdpb(t+TeAVYn)i5UGl_Wn?lj8-<&6lc+chyucL!UWLe33TS z-%MfcR%&fP8%|AJy038@;_ydsw?9cB72aY2^#GQJ*rtZ_m@A`y)`ice)&08C^_1g? z(b#wHAG^;q&m7ri8E^8cC$Dq-;|-mpDKVRXw$7Aa9JYy$OB(X3;FG^q?%d+WhuQmf zCZe^&)1L44nj;X~7*M7eW8)aLI&ZYl*dKX3t3~GN`lD*g22zXuX?8;&Cih9b^HccG z#9t8vJN<=Qiv^nYcJrkwhu2qLZy>H`a9%FT3s6x`XY#8=!|M!4Dy$-ORC0 z#9>SN-U4*3;|^o0mN~sp(A6e`ku1TDtirx>bFhbh`-4tVRB?eq(f$B&LqM@IEkdw5 zl&MK2)g5Xsa_qEqmDI-zQCA-{ACE#Z;GudYds>GIuzPeT(Q@g7fGy;em*cdA$7*8* zSu&&}5#eVYUli`G?FH}j6x*Xj!9unC&JFQ#Ik*vN08kpZAyY2HpLgv-9=u&O--Xev zY?QS-sldnW+0QI+TGnRT(fRm=jZ9|naDNE=uYl5zX!E=vbhax1 zz1rPe%PLDuH#lRISTW_6eN&9Bb(jXD&;K-F{{q*AKj&h9A35d%Gt$EX<%4Wuo^`Ij zreF|vQ-1fqlGAY(;S_V}G%zP*`O`P9Psss~TNHD|Dr+hhCBSRMuJ(K@mKbeH9*1;I zN>CzE3A`MP{PPqDKfKT`B2XuNdM+*B5n6Nz7F2x;gcc6D2)M%;M* zelD<1p``qibJ0uFup$5T3FVnRZI;Vb5JY3-@2k9w_pqk=MNoyHdyESyBh>tv!27#*1?P^t??QQ zLMLNiB&wDhs_u&beqP!>oy+5v4pqjFxAzpigtseRe}&mtH56ndG@RKifvnJ&!L#{%`KOhh zwNY{Dl+p4;&i*>cUvpdWPYO*2z^1i}TIU7dxYim8z#EseOUq3E)@c(8ba&il_b^+XimWwYM z`o5{a=L55hF)u^1@%>Tk-`na|?~7Aw_zo4v!jdfPQsp@hHDZ-;;qNq;E2wg6 zFc7}-QqE|;8MWqjT%9`$5U#&tW6Rn`W@1Gy%{X-g_Ed+059OW=8qL?P<3>?O+LXDX z`jxjI$o3(LDgtRnk{97Uec3KzlK5 zz5de{Q0IZZEk_&lJ(RQ>d^Znq`Q+zlD@Xd@EN_f#P-->NEH-jCW^+0W$F)D*RlIak zUvHfrMf8W~a06B^+BVVFKh?8-M*pt@eWb^WL3VL&ppr@dl*NdLGpYf?Hn>xvBFUK|ptnyaB|c}lP4-H&baW_Q zYI?kjvc6lvI+#|sqdf_dIkiO=fNd8+h8+aU>XFv=!ENiZ44)mdRe#JovHRdahgan1+#2(!CYnVQrwKpx<2^zgKiQN#rO9{*kq4y)U*B>?b z9yG4y-uC#ns?3jCrtUv#?$D5)@hLjm!teq^=uT2S;X{okUvgr0pUt9GKl`S;{U5zc zN@QpGT!t>Rdx<7@DMEEmoSd0-;$QSiXq~!WG`8xE(!i3?iXeR+>A^_v4uVZ9zqq~HQ;(1X{|1X0dCL<1D0u^>3lCxL*b+TIayjY8?2*(e!==O{qmLm> z?hS8z-%eEYH|#*JZ}fe=**PSGc8_WDf|npr^v^0zeXm4D(~v>_I>nV|4cib6%L9hU zFZ5I5rVrqv7NpuBs@oFMaXTg_gMik3gwMDwlvCdICS7Y1!d4Xr>73OIWBXzCz?w3c zMue4*KaD&vt-(t3x9>kJj7u`BY(8`=5Ra+=&|JU%F&x60d!6cWejA#T1`6v4w)Unk zL6=0Q3GV{iWtON#UX6;jYI5;1w5KXMhq-GAxKixF1rd9OLIfu*RLNyC5r;$dCeeEF z#Z)GBe-XdU>$c|+C^;1FT>>yG7XGE#j-qIKKI1Rc#1`Yto#BP_vz-nG?Tw{iEVT!~ zf-dIyP}j`u7Nn$i`KKOCPx&ZQ^6&MwN_u{G8ed=vQ9TfHGEt_DUG?x->VMH>omd(Q zQBi%+ZQrjD)7SdRI)-yM^1EKMJg4To=&V7Ds>o4lX`5?V&rA|vlE4!Kp{T`vPPDGvJ1t{h|-b( zrJBJ`2z3RwmP-#DzFQp=>FK}WB}OQ0Eci`QGzqpbe}&0O9w*WBfl@=(0!*?E?q4wIIv9eo(x(eds# z`-gR6>?SO+RXRsqKOABL92U|!Y1B_#UkqD2T0X>-0s~}eFgBTWIV!^oW5*4kMKk)% z`ClO>|7`g%11X^UOTE=m*iE5URUWfSb|X>OiWIF*>RRt$F>Aqq$JfnM2XkU( ze!I12E~$AQ(^BX9=}0zPMLl!I{Ws$rANV&WMu_j zBM|^vuP9m>S~7P`{?dQ6esr|!mw{HwcmZ(C1qRUuI@42%w-y; zxvD@qZ}K^pCh$xX-u}vCEa}h?mI^@dT&n8BSr;x@a@pt9ZFXBX?F=%*!{I!!NCL_f6;hboDDV9*N4NgqQUj6mk&#(URt={&aUTUqo?3ObiftS;+P95SJ+ z7i5v@9d#y^O3mhXc7@i)AB>)14(39HLpH$}fP2cQI%Ceo#Hr`6VKE4QnJs1ReO!D% z+J|6Z5lv@)%cy35BQ+hz^2L&dL$+3j=%`~evwrg~rGH0Y;@h_oDnoASRO+!y`j&`n zRmHR)ev0M>{3TaaalJRG%%>dtaW;!YPbXNPnAF9^X4Tq@+e`s@fINVE&oJ>~oYh_* zO=Rpn_pB*vgL3oJH%7{=DoPP=pttWlOYs2Q6PHlil!`h^)8uH_F{X&L2;{SKHu@&t zIa)1kVG22VW_>woQdR0hy$Ch=c;kozz~ddr4oksFgN zL4U*WkoU!in!{j|5&oz>R2T_w(PXU5gn?A5`DTAOwf0Z#j>3j{o7-7KYSNyVs@M9b za{e9OQ=IYrDysOV)x5H?7I?(*pAQ`l_6KRtPhPdi3U63FcoFa5y$mJ3@nU!e1UT~u z2W{y1pPCQ=(sz-+koMyHBFGRPNwd%w`j|`CV#Bi?@3B-k-OOnR`D961G26y$hwZL~z)}_;7%nQ?aul*nCrGb?a6a)YNMJjRPKfCo& zZ6tHF_H^cOTZLIeh-9`V1sG3Ia2XSUb~Q2B-^a4*ulEzEb~J98V(li`&_buz>ZjC*d)uQkZL*o8-Z#VJZJCX~yIw9*IKRJK(%SRp2nG_=^S?C9M7uW$ zAr(}da&*H1{K@r7!9>jUf{N$F zSMSunuQE4ZBo0z?xJ49-O*{f3XIF3%#z8jMp!q}9)`I{*oKfR$G;~ZaNR_y1{ zw^F8gc<^>M`Gt)yp{+8pf!Z|bZl#g(`TKZ~v-m|Y*oI}=p0%xN0QS9aA^V0T=dD}K zl`#DU{~Vz>2%R_r=M@+EyX)B4mbd!VmG}gBunHHqrstnk?Lv2X7wbpUnr#NtgK7KA z#kT5Z$F_cQmDWM}^!J|R%d`SjsKRZUE)MT?wLZGjgq9RZHdo7mOzNOok%Iz?>JeO;Af**cY8o;tu zjvXS(2Hx{zH;xve43TUs)bfWD(wz)Z5tG|u9Ql=*e6CEm4$jrJery(y?ZMC8skEQcaeo2|FdLKOica0%x)S|hiYrKto7Yu}-?%`RdJ+TE@5kvRxVl*B z`#B#sY&WX}chl;AF9mmKxM2OBHU`|z-%JQEd>*iQU&(9=;i<^(^W}r4CcW#cuCk}T zS(j4=E^{h2^Bd64E=f?g+8@DqAhAh2LX>4-Z>j_)PKh(7cE10Z*}DTOmu=&H+1|AD z&jX+M4okiCN3LE0vTqi*8+#rZ4WCUMyzAB&X`Ci@iPw?xWmc?>XYWs(f$&@u9kEQ9 z_8fk#;jBv@E!Iucc)eg4(iXWryyvwX?B95FsP|ne!RzG#mMO(mSUY?pS}sR4b}H@m zzswC4%9Y+E8Bw8&6Zh@G%n4urgO1X?kmQs5U5-J*pu}$SIAfhYh($Jxi|J-^6%!~=_ewVXWAA4Odfhzq~%MVQsi`%IUN!r8T_wu4o{ect(0iU@dBq*gRL>yQ29!_lc7_#ZHL52n7>0Q z>^;pnCYUwF&KnoS>kt=t2V|^OJ-0QhY_wOMeOi-1TR64{0reJCTwd%gASiMEy}G%C zgyv|>l5B1Z2+{YX4Mq_cLH#POMAvb-_A{LwrWuQ}Z~dhKM$qY9mUu-*1LJ5kCHm%9 zc*6CYS~YV*BrTGHJ`jzOeZUS_D3AAV3+mdm=jEDI=Jzy(DFww&M-KOyS>QaK``GW-=p!ji3m zswBFZ(Ns1e)_|chIVGhlTqM2d}-wzhL0xNTEirk~Vr@IAg0Kaz4#l+}fS)d%6gImb4bOt}zVcZ@m+ zZBe+jg~!a>j_=GzjBLe3g|e#DK8Vl7(bYAzF~-GrT*Z=@F%wh{i6qX?m{_7SG@a#K zSBtLZhV6J)-gFG}8!E2enB|0(L_+pD{hmfd!^Cn|hHA|rwqXo^M1ypCGHooBW6zpc zHnYoBIfeii+LdMLTL^*5^c!vGpl^P{$lm;7!@PaW`h5)sPFdC5Mc)o~D6f_$jBFkH z8PE*hWN@(nW@fY91uzz$$@vB{T}_sNdKNZaVD8ok*HwngkI;R(8P&5~(SE5W4()^b zJI3ZSLsmY@l6sLsBUU!}&a#eW>q?=!&4c^hWAGVc@{z9@;Ge??H47pRjQodK9+>QP zn$a5!DRfS}tCFbgb6I1KSLGV`O5L{rv+>k1$tx7?UMyO+IjFXF3@cX!@L}76ahx6QNAd~Br%z=@LDF_$4t@Ho6XwonBxDm4wpYz zEmtPKV3L@G18+33d->LXFAEcrVPBwoAMx*8zn1c zo8z_pE|?sWT}_s31I+bSl-jhK-^A#uSQ^K?r5tp-AWJ0Yn^>b*K6w!Jz9^mPBbCIk za#d9YtgKX8St?(U9g@1R)@RZ~vgXub=OSf#vq~6`mwYb%h|VlkL8Eu3V8_JyN(hKC z`zO)MGwG_ZdfPm5S9xtE`|*nx*$SQ5CcqnO#FB5Tx5Q}@V0MhTdM0ph8zFpU;kC#6 zhLls~b-#nd0`aU*5?j7$Txm@#E+VBujUuhA_+`&^W|fAw9dGR`Z7UU}C~!irj=Rm& zvYJ@)8IHfI^4+^WGoaeMb4>#{#Y|`x?8Fx|(A(K^GO@Udaz&5|V2e=I3S^IBN!qo8 zAp^;_l%`VV@Yr>h5$Fa6pjmCzkP!3z*APCCw}fe@qI?suJ!p@VE}6UyY* z=~iH!eQc6dcz1qfg?|Bw+!ZB?B^#u2R6j-SAKhnj{(XZ3>JyS&ztsI>^Df|C4c}Cv zh2pE2?b}{J66MW`K8MmaG)mNocbR!p<|XK=tLA*v|8jc)`KqewJV}i9x$7azofO2y z^q~TUm}@@AbDebMO>(CKmvbVwb&9rNd<8Ue{}nqizQ|>8mA3B~vbR1z$RQ76i>{P1Tdt~#&eDGENyzix*~AiJ z{--ubx4E*dzT9V01e3p!TOLFQ+wp}Dhr=6wk+jzRa4|A?>UV@t( zsCgi-LE&5c+Os6S$8*;zb{?dXW!(T*J4j&~G$!75+!Ww)PB`T+Jj+*Db%=eA=g`T- zQdbaHGeOl@+CkHbAPvXdRdMLbLS?(~oH;_dDewhRv9^kM z9aJ?Bi4rjAb1fCVAPT(C@{oBDCnd3&7A~fT>U84zqgUYmDT>Yx5Fq?@ad3B%MwnbM;*}2@9xm_(AdHzH5AZvby z$^YQA)#k5ozJ-o=>#N=z9h>ip)B+6&^Xy1*xg^>Z#5DL;%^B?{&(wi!3t!3?Uh89)qGL(Xx1|8NzYV(NI? z@ni53wtg4BUtZRkxjMQ!m@fF(gU#fBnCgzf7o-X63c{EKm1e*3SQR$&$cN`<2Az+R zGOw#&_~?AY%u=rhcWoNq2jK>#PLS_KrowFl+n0CkS8BYg!W8ra+nDmrgLF0&hO*tK zhFpJF;&2|vYu`!dV)SyK%zJdPqt!O(e=4I3pF0xFC*;CcDzCGitUiePYDUf+GtGe1 zeBjK9P(F90W)kMct99s@t}4sRt5>%GwDTT9xH;46Hg)V{mSf_obAS)Lv7fe0kUvL| zeS+|N4+1}W9(eRw;Qn2}-A@8f@7J_xnN${>uY{PNZmyp76Kpo&CfFS7*yUI_;V9tM z$M@5AEWGIz^X&@215W|BJOX_6d%&)J?GU#q53uMwpdU-KkUWMBi)Idu1p(mfO~4<# z9r*Uuz~5XBY}+(;ukwH~QK&lSCMVu-Ec+LeFKA*WfXiPCeB*t<=Pmqr&s++8>Js2(>*o6t4!@4UH)^WmYNl&QXYTkyZG-+_SqbM0-0+?=fg9cf z9Cu`0R%c6H4q%aZT^(yCePv~Sq^emi3PYm@Tr?q+FL1|OHrGmW;!*Q`uBLLXyjsW0 z2NA1}({f2dS)N(c%VZ+OE*H4r%#FYouK<4Ph`Q`D$>q!|l3)zy7tlJa!&pY#x|Lw1DQTd6rbK zox;SqAcXc-!ZJFWd_pJltKz$1Vi69;=TKQu(>_ zUOotIh%;|ok5fBBw?p~dq5F-y_5q)`9r*d{f$u%tt?lJ?z@NOcoBSMk)mfKiZjtU~ zUe;-GEPl}-XS;`=0eX($x8y+*hf5rP??K>0HvhG7mc5Josq$(a7ayde2bDa? zxbTVd@7xRg;jR8M)-MBZJ2_nb6nV}EfuaXm6N@=0xN}8G+(AuTSN>lg1#W%JU*7p! z;>e#GZyW#Rd5{e8m*4U~=B)Dk2#b;jNfce;_!5uo2JYMCC-1Zk@#IgHckw~A4RPj; z>v3vF=yoWdJ6s%04mWz<{&MoMz!8k2-c|k-dDU5$W!25@bw1WcjOB+-zQtS8*N*S8v{!0ZlBm4jt1*A7pO-x0OSdOCF?ubdB>10Lz|Vpxl2b zp8TosoDZU+yca7`&}a(%tv3F+BY}0E=V#A>c=D&j1NyNf^C0X{4;_Fx58%|X`56CW z_PiLs9}`DqTtTR(@8g4*+NF+v2%oDGs&=p`f~Vsz*y<;5_fLRll-HMr%AX?7`5@YN z^U{V~_v?6@xfROiimp!RJ5KSFbMKRJjr)+s~a>$5qLLB#tg|{tun&=b%>rZhFYQ{Tz8+ostJh9KI&RnHz!ko#iLvuE&AL zpN}JdYP?#f04EXCy`FpBNxb~hn@~6ntF-phPQdODn!LqFow$2qL>4q+G z{y)D6*!FTiIk)eqpCNH+KUW^mkEQZFNCsG%0iV3c-?TdL67Y%Jdg)R&D0z@XK|_u@ z{tX*}zxY|;&9C&A@yE9T-+$KJeoA?YzWI^|nIKwbCF(aUZr=j@!s~%|o(6O|71uui z-0w(|90QlCG^*2F|R-RMm>~5NcUNl}s z?+T(DgxvnFyh(P!9Lr04YGpRv{bDZ!WX#V#G`=EUIzTt z#lVrPUT7b_;1qbepDN72J_ui?`zUAQZSrkFf<;xlU)@&wCnqTH!=ip7U8VEq8}u+M zNSEc$6n=JTGgSVf;H*u+b?*j_Ke8S?LWss7QYVlcd09t?-ow1Qfv$$xs*2ZSH3Vnk zUFCP-ZFu+@;FEW*_DQy>scYhO*GOo%R~}v8=5qcP;8n-D^;L&$F9-hq-M|$$2pl?G z2UFJMH$tMK@jnONW<4a62jS{)rj<*(L;5n$AEUL}n;QW(O-f9x+b5LI9S5)4V=dkg<@NM5kt}aL+Ia&{>72~u230!s^@ZeMazhfrq zd`V~?VXC|!7o)0b1?kmAoz3QhKpha=AnC%(?SeV}Z#VGXuLAdI-gRZd+qVL*J28Hr zrixKbtQeM24$7flMY^A$7Y+dLzp>k=p+E4(&SEDGZbctNL1^y6h38BR9q%f?3-5xE z_Dwu@YmQHxfA2xyx4!}Wgt@)lgbS6oGKR>X8qZyek_Yi@Yv}evJr6^>;`HO| zx5LMfpE@t==rG8=Sn4(ntI?2~%FD8ica`6T4~2_-Q=R7dYsc|j_<#6r9qIDHxtrt3 zPnow_4};!|#hFE2Oe)hpojf1D>+cJZZvs^y-}2!K_}qWF{T7K+H^h^la@jUPl{`qI za9JIFaLXh7qqaJ1SdJ%qDlp|idXEM9_!Pc;$%EMW{fB^mf86~yaXv_h{3-BE9z^uM zf|NW+I?#Osgu{j-`|HP)Q1T!NK);H_H65SW>t6Tx1ym4jMK5`fKnGp6(TDudJP75V z-VbN2U-oZ*>O6NXN*=_st)bg*P6}apfDOz3GeJ!GDe|(84yuCmVyx&wYIDb^xvlcD zY~x+!ci}_fBHvV}dHzJ>zc0Uez59L3&q^*^D0;p^iEAMGDOL$a#MCAKTMQHzo&a zVnrWnt}|g*epWY`1-?f*8oc$J7x4an zs^7o}q?xA}C=a3?1tkx%ptxfP@Zp<*{V%1Jml(Qi%Y(R>SV42_5Q8zt=Rx|!7w!c< zcoVSi(AYgm0&C;DJP+c?=Za@v1U`N%@XwFT_obAF7@IH8gDfy^dI6!;I4PH)(V#=Qa4ju-cdlA_EBJjUY1NS{y;lFnZ z+`E%sn$=P4${i$0zQvB7%j=|HDpHR@bb-@=7=m7gkas?$7wqVd1*&b-j^ zHVY(%lP&;r^3cT(35`a$%9{dD(T>`cvPW5%csgNIl<$Ki3YXQfATIjMgIHG)s-l%i5s?Lb+YhoD`DIGo-?cA>WG?3_8$h zSdBW@)VFZqUFE0Bo9YadCs4Z(u z=L#ZBf@2~_XVztPENCG3Y>n|e$l6LmwU&Rt-l_euo(BVcy`-`ca%RlzT`oS zys+@z?Q_>+=-NO><-f;>e#YFEm!%Ibv6VXT`K1tL0FIXbg0tK)(p>X_k>;(M_K z5S}L~d64XRVd105PxZZ64JBcCb|3Xc3bMthS2vaCK{}s=9F#-9;^b#KdScM4%bB$) zy%$Sxb#=#6&PD#z_yx~{h@s_k*CMs=qzh)B)!d3Ge-z$?=A@8J9-?nv^xS^y9P5zc z^B_XIj!s&R&aBJom=T<>b)QHv58}cL0F~+Zk_VYI-#S}k^)J;t2)mE)a_Oxo zFMW}M$zc*#EtNcoD_SQt2j$SOIQf~5t4Q*yab|5wc@TD+*(ZQ`&cwwbwkc_@t zp@wi%>mPS5Qp*$bT9%PM|Wdl$BDx>^V zc~hOf^8f?!BGvxcERa<5Agz4{UjEw3RM)jXDex5SsNH1BBJlX$%XGq~NI4JUA)^AY z7E4dk0rZ7!Wl0#5y$(~&gIIYc57JsyfcC6GF&1xIJxyG|${U*(7JlshS@|jFLA1Q| zRWuNQ`NKX1Q82mH!WC1?gS6mah9tk->sOrQbvyZU>jTcL?Oj1uR;q?0zOquSZQFQR zHt}j0Pof$3Za9ps3(6gz3eV+1+I0r!8|N{>{(18f+_e}W%qw~>w`H0XM(%>5C}W3u zZ5TVMMkbiCkU_#ZB z!p6_rGxA0g%;9)vex#Ko`AJ}G43;SDJNZN5GCOpbVJUCyiA(z-is5OVwt6$ReV;lH r45%Q0eL#frBY;zZ6na=zhDi8-98saX9CvrU00000NkvXXu0mjfvlD1$6PN;h&p;f9ha)#71W0NKi1G+DDXCH@5>;uNRt=h3~zpst$dwsTl?45gO=X~?po!yJi~n=s@Q6tB*T56nrVOHx zs8&W@S_fYfMsAZ5Qdz@!;-jqfQ~mb<>dH16Ck0xn!Qm?+^(XUk88K~wjcH`!NBa_W zmC>1pj(nF^@L7KWXf#ag-0UkE@p}frC7Cl#)RSMoz-q=^Uu26)cffaIf z+)bvI(Zm{E;pkE2@7{G;0mbUAb80{dW>+;i>FNU7=YDNlBvb`t&<(IIjyuk}iQpsM-S0RaBTurj zO(>mTr_#VG`&idj!`t(n4*dcWe#*-(7OzllNAy#~{>kS|&cEyCfcS(Y)i3dU*}M&? zSHmZTsHgbIXZ@xZka&5sqDxV^4uue<;%!#mka-SS>xwZS@jpNAK|YG2I8NfDerh~; zI+cLfm_BlYjEzb@#Z#TM<#p{=1vb}&AFBkd$M^_H1{1T^Pa(4gu{0AB-_daCpbJm+ zOI1EJ-sPG!7(oQ&d+$~|RyEy@ipc~pofnR^!!1q}lM}1rZ8b<}Leanryb6iD8|%eM z=k^?tj>L0$ff7ugnyC!FffKx4O?iW7I1ZxT<}%yG(XbQLCCFW|MOz!&h1bEon8v4b*nlSapz%2fe2*Mt)Oh9~(5 z!w#`ac?zjaEK!5lnhB!B${o~cBpaSOE?Ygj@-Bncpg8!9gCrbtTfvbvi`=#iPnjb! zR|UR-SBz*8r$SM(n6EfUB(H!fo31FNHP=Gn8+d{9RW33PVkIOd)56B|5S=cU_Cral zd^#DjquKC&d7ei+_7Mk(xR6oikl3ikxzCiA`$grCq6n=P3z8Sl@nh5ON|UxuCsuA- z%Z6Y7tZ|SLzeDGLP||AhS18{?#oP5&ZH|`Br$uNR4FU7yOtBdfr3TSWzF~7l`N=bJ zVw<)v`3tXAyvvrbtBDp`+Lq(I#TYfba0-$pQI!|ZTfe-TIyR{%u@kDgvhw0NQJa}G zT1b3@aG}&7h5_vQfQ_!a>_p3&B`a^!4@#&_LSG~=awZ3|;ZySw9IRc&ESz*xWwJv~ zae4o65tLx6cvJZ@cn(v)8$Ms2=V{qGx+>@fyzI%Q^FMTX%itS?38MxfOoB*pURzd# z$vkr5sg*(HqlC<>@;AJ9K4E5w<3Wv0;`<=fq*OKJI>>V4rity!+x9Co-d1253 zat#!wHWZ4o-K&J@|08AMNd@u+M3FRoS z7x4T>;Hh=Muhsw?2ZG)?3YCw5im+d=@8Ijz}a(wD^7|9V@wW0p~Bz+VCgF0w&#HLJL7Yv3DE0245pS+gGiO6@;ySF zz3`3vAvUpOei}YC-H#p7C=O*qmD<9+R z)-2$w9|ul3tn8j7v2!2rFTVmFcrEA&S~jm25R(Rpgm1;U_#isd%Ve6l@MUVKac$tn z#lRO%GwA7(@%S3x%a0|s2}ju`P9Qv^_$adQLEuSyn9QxLe9`#OVZiN6fTJdt-4i>! zw;TA&p93$y7nGA63eV(xJdf#l5O1cH%E|Y|7m0J{0snJZb%XQoQ@*B zmhLe=kyD&kEmcM0d4BA&VlP+`Op_1V z3%n|iF_JERqRPbw;dxZ{pxB8le3AI<$-vjo_FwPbA>f@Iz`C8l+U>xe!FV!HJP4RI z379tpm^IN~dtX@`+Z1=aV7{Jp=fymf52E{sC(lkcB<*q;J16J$oz5HQ91Z;YC;ip8 zViWMlo4|7$fLGs-w`O%t=Ctv^;@QA?^WyE~tUIFK3kt|d>puE zWvSb&!SMVrab6JziEK1PFJQt!z#~@yM<0^Dj-d|lgB9^=y5Jo16;M#PB>JsaYEPc1g?AlxaAq7+Lu3U0`MOS|0-`MIV4`Tni!+wAh{S- zR7d5`W#N5e`gq`9&rVl%+um3(zPlXgU8Dv!1?py71b51w*i;k2dvrduP?TytcYK(2EMw?y{*}k zfU8c)-hP#MF^@c_DIX+~3sFHro^Bb$OJ^eaPW!@`KH#7K$i3X^ZNO*m2j1V~w?8(E z5QTeQ0q$Pu-rfyoB%fhP+kRDe$_IfU2TBu*D3rN#RS=SE765Y(aVxWBPrOy?Q#fQ3 zHx2)9zm82l7aTSLxOl#w_N%~~=6}nip0c0_bI2zq_5;^{)UC|m0pQw)rPOqhtQ{CxI6>`t1kMy``O&|IIq!(YM^% zK6!fl@RyJFtHj&*Aj*m;^TxKE*b(_SEAI;v`++Zh*saX78-Sm@OhPCm$}{m(|yGGCWC z$Q38K<-dF7=#71O!)6h(;X6_Mayb`#{3w6zhsskvh=`t#gG}lN&Y5eUKSH_yoGX0o z^NakWtKIr=^7Q!n_-a2y-ZcN)!>WbMB6{>`kcTA#gyV?^#qZbZQAEm@R|DPBCHn*NIk~I~Cc$&zvkIZ>Jy-YrE?axH;J-`d zcctMw5petR?r{|Jie-JbA387p{wUHslP*_edtSxM9rewpL4O~x=t%qgod{UAHha4+ zaJ|kApPrY6#%dSK7j#nWgHMA+N5+q>+G52fVCx6j+VlmJ?cDI`d2wF3d>^E%uno90 zQvc;P((qY?GxuNWTspG6`TCEZHtghkUH{W9?A#l z**Hiai{GzniwN<1ZHq?Pae}(@@*I`tRa>>iOI47jbD;{!F;m@MANuz8Vz)cRYfEX) zhWEz<>bcaj&x6dH;+Fr7t@-q$LD=ZoILN$1-14v6k*=MzkhdHgK7YQ6g9vcdAnSH! zYcDOzc4B*pmxYG09*%>!3ds7B1Vn|9;|Nvf#d&3ngXjvoKsMFnI7U|96|Nt@*|s-b zJ6T~GhlcmVE1h&snzp-il~fH3z?5-rWy;#Y;-LJ$hWE$w1y| zi;+7j9Mmpe!~5e!C z6fdX~Zz@+v9E92(Q02?Q+hX64dztQA@*Y*VK$@7@#|ME5mWA6wKahyoNv(Xh`F zHM}RD=TV^$A4EK+9BT1ccCc-D+x9uOr-+JAQ|x^Cu#Ii~sL^M0Si^hbO%_NnageOL z5w`6Cz~%wqK;#xIoF-_HyzONf;v&(c{ifvwTSlQiNPca&HF6*V-rw!k#&nH|l?QCJ zV*|gS{F6fCAe33ZDW-bFjgTvpB7@T12}8ki`G zHAt@ORI4>QA;eXYA5-UCcz*1P^^PxcK6Z*ZPlNg)cMW1%3A36JY0Izr190JS_W9>b z2HLH3K}HiOjR}nIy5xalrn=={zcXF@s7j#xk-`K-Wdf;qHxtXQeG$KV-~KlMv2YxH z2=J!#YNtjFSF78lu;_=thMj)e50$6Jq9@}ZfV+SIz(-W)HGPW1 z3-f^P7v}lyPnl`c0@ago5RU(Q+b#dBNwFVN1Z=cpTAb=AAj{v)ryrq#ii2Ep@eaWKbl8ZvNIKU+* zM3~fX-}dWUfR*q0Yd=Ju=TSivkX#&f)t1_*W0X7=d7d`$w(YC<2VS%PJwW_b-0K$F z*Kgu=d6L(UCH}kc{cYfH&U72%yI%I!eyF@gI2h35aS;A@@2kLo@ZKvMeBm@;{!~Bh zo6JO6fYPL|SpdwMX!qNI_o|azAkIrE~z%rF|J$;voO{G2rhU z{SE;5=93Zj44Uh&BTtP*&&NSz{*LE?&5kd00OGfv+XI7Qw(&!Yk*AYRQZ)t1_*W0X7=d7d`$w(Z;Sdj^58{aRl}g=yn} z2d+ro&SBHOE>H6MvBZCwpK&n5y_W%heyU5aV+`yEZhS1+j%)i>Q7lBn< z=ypx$W5A->z!fLO&!x08>i-S9fQx?W{+7uaqibg91O)J?Nyy40J2S$iKESVlFaWnN z0WNTOhn5Mew*im61w69>cn;c`+!UDi{B5{ z0D$1Ti(`?yfT&EYu8w3dv2+?l*S;-3whi2WIdJL>^Y2ZA-GjjP4}cxRpLiQT2AI_E zPZ(tE83aDJ6nJ4{_q7Z8y;V8$_a>Z`*zre&;^m&z1s@uhHiZ8N2oY zpMMYu;mAAA5IV0D4xovHIPrUjfNLKD?s>)kHOd?B?T&qpXE%fuhbTL3T4ZemB$F1& za|dg*jRG1O#!x4YIA8ZLur0-xs>;Aq>wqQqG!D4}BlFuO4k9D<3{;s?S>qfg-qaT# z{Pgj_x6cDUF_+F?HikODcb);hw;bpcw}w;=S_P21fKZd!CrnPZoHq~tkBWYVj|+?_EjHHynt|gVs3mF?MG%ByP6xG+Ymp`v21O5 zeW(x;BPlPmKmh`xi!;cvjI<_}X`5EneqQ`BQ{z_yU3@%n)MVt2f&IXvtAQWA1gzK? zY@8uPo(v9(a1jt7g^=h>puE$j_KV<8nGT$JB(QL1tXXC{d>2uL2=V*8mcI);u_o3Q z`wxr`en@eKfO!U(T>w`B0X&&n^2#g!9(m&Fc&biQn`QBb9~{4B7~wku&;)Im7Z8mE4yW699Y7Rsz`-isA5 zpXI$+4e!oVW06Hbtk-VSDxcNd%A@@vc^#6ALU`|$su0h7y%);`#-W!dy1cC>JJ+&r zVZ+U6WR%nQM7@!IqJy24T8}eWH%etc`YTkU-|ERmSrmBbr2( zT6s=q;hQ)}&-2W{Th(9I^B^pWMulXwGu?U9@Z1+^(6h*O^oxpvkUzqQWmE^@P-~p} znU0>}b@VB-w#er}n7HLe@Z_-#Z`pq3_$Cgb<(YwZuAdr2dj?Gtc`Z;F?gLb6heG276Y=!Nt!rF?RjS4y|f?dd$AHiLh$4{ z*%xV0Ta-Gwp*#gdecXvZ4C zdaUzAh;a}bo&kt-<(oK2rTMO_RaXB(jf0TqB%Z6@NandO(x7sv#HfW52eC!hMXiHy zs5MUgOvhmaIck(y8&VvEJSNUDU^Hf8;}DyKG*rHZaS$eM;fB^fYAiyFgOKMGy!9#+ z!d7ltBo~Na;uL9k^7|ZR_mAgMA;dVy2+z|RUiPcB?T5lk4FoOupvEDG4>WjUw2+tdjRKLtTh%HHZVq3m5XX@M!L{oHsRa`!>< z@H=#QTsTNu;wj2|<5h!L^5h{uf>~hurSrK9NKrpfrAi%m##pzIc&y(_+XaN$_!e1E z$gL(KOpa$(J|md)7c@L;x31q_w&__m4-{--#apuKXE(C)B24^feOg{?f?1Te=1bBf z!7l=3qp(0>-oYOkHmgI56;|f8J+Y}ji(ojKr(L}%+rAH-2MQDrz&s(c@+E*0fe?Dw cXa-OCe^iKI6T~y*UH||907*qoM6N<$f}u_|djJ3c literal 0 HcmV?d00001 diff --git a/src/images/kivy/text_images/D.png b/src/images/kivy/text_images/D.png new file mode 100644 index 0000000000000000000000000000000000000000..2549ffc2e8bdc6e63dad4ba8d03e20e1ad08b4c3 GIT binary patch literal 6381 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 literal 0 HcmV?d00001 diff --git a/src/images/kivy/text_images/E.png b/src/images/kivy/text_images/E.png new file mode 100644 index 0000000000000000000000000000000000000000..5d6316114ef0eff37bd721612d3c9112f89b5c4b GIT binary patch literal 5009 zcmZ{oc{mhaxW@-E#x^8NmN6(xsR-G|$dV;lLiQylyRyqLjD6qNtdl5ZO{tKjY$MB{ z$dbk~#$<^Z%8ZCRzx({|z0Y&+bN@K!{PF(re%|kSm-C!db5jFOHX$|u0KjQvsAoYx zhyGctEcCt6wy6>T;I%c<)3ypP|LLSr&uqlg1b3F?;gRIwGf`4jP@cuOc`Uie^Pf_g z!$_nEK1xlEjZN+Ff=Z;Mv-rLY__kCIudRL7QU6ET>5HY9dO!PL1iHqgt}FFiW-(NcL+$Mfl>pmhuVL zl$Gj3pYz^3gajQAx?VEw%$4$YZsYth;s@5nz zEhAO=TkpB=Msc$h1dob)4!=b)qZC$}`*5N@9m zKWd4(aVZ|9uVqz-!u#N|g^!=2tnFqS;9Nk0N{% ztVYZacu-eDpYRuHR@(TjEUEQ3Bd+xb7VX}~uiWs-v<;NebWe1*wyeRgX?Ja(J?d4B zTHRzZuk7&SB56n11+lgY+l;n|>#@6Er9rSo)Hz znD6ZNtY+i;gTv?`3feJwSPV7{#|oRKTB?%mchc=$&cn#o#wIjwPl$0i9E4pH_9SpZi^+$INS#N#}V}v_Y&(IvZRBq-0 zcC!@jTmf!;)92nf3!m#&s%a=R>*8V)STVxXP-*Q^g-R{YQ>-#n(|#R07ipT6+yPbk zj^p^KPjAR@NN|KMs9B~fw>V3Sbk!}te`sv_p)E3Sj6>$5RS26kC5bah^HOf=jXNAd z;;aCHst$H;u>rQ$khn|2sg_Uehpn6qH}srS&`}+KVkq%0hy1)RSOk9o9$=ZVYVUX= z3aESCBm`Ni?7j~1sH1?zbPn6zl2N?3v-3+JC&9t#iEWiG%yg@^f3J_KO6pNk)9mh(IjDxAq1(ElJ6$~_ zqljN-jRrC{pp^b!ADPstd8o8nDOqALp^`bz^K_GCT$lo$$uB>(@vo)VSE!Dn>z;Fh6@9#kD}Q zAC|>%F=>qQ$E~|eu%x*yoOE{Dq zZJ$H|LRIhXK^tNz8pj8Ad7t;E?0@6pY38Q)$>DK=?I!tz_At+w)e`ewf7qwUaMk|v zKV_{-Y+G1zOF=FDE1)%{yoA~&&w7$Y_q|CDY?UYE9^sKOGtieG_BNq*lxMUeRwZP> zGX0G5?l+IFT_^+-BU`pDGgazqAFeBKMvEx92bBz_+BxFO7c54kU?*Q;Ltl1= z4#WytgO1sV4mII(j$hV(_;vHoGjgUOgK7~qVT;S;IOVJG4rasrnt3dBz{T0bDR@J+Qm?Mr8s077t7uy7y}}uhI|UtO+>ZVr zH3QL@w0+&Wn};;l83X;zMU4h>)ljB8lNo(y)ME2-p-G}aX~KVd5$16vEA9J=G{6J= zU^Y>=qHJo?MEu>-YZV9F$R~j( zV_;LeacqsM{oJHP=y^ykWocpXGV$r&=RsrUL3x&i$_r!hR>EusbK9PMbByL>5O9>RnFJ_KUGuRiKmO#Z95)ppj4FSBUQ? zL_k}q+C5va9p~xtJ@d;H?FgcpDn22A!-JH_wTkhu&IQM5LH)_0gX4t z<;Z(qZk|j%>26Xc%5sgy_aiL<;d(7WJPk#DZ=P|&gwV&QmOOjzZ%PuW%aPT{KK4A= z5bt?-3Q>DLMmK9Pn{){rq%#`k*4&S%3e3XAbKGuKlimL4!a2OldXZA5vSts_Q)^uX zS(E_TtG{vTft%7AC{P996;l{sEQE6d;{%PX@*Lr3Cel8ay;H&At1MC}6Ie9$Wi zv6=xY)!&is9*g^DUm56jShR>0*tJ*`2_znge{D&mt4-Rxo zz|bU((n-`dO_?*@Dh!uX!YLn_M#u(PEX4N+DUo<^IPBr@E_Ar+yxB{2OdSHrO`NzF z*D!>o$4Qq2M-PFZnMR#1qaIN21`5TMm4l2#|I6JcIM_}8-d!a3(+}bX1ngcBP)*!J zqs3;WWg_K?ZC6g-O1YHGvL_*!Yl$7>zo}bSXgU70hzcODd;Si#%go+sLFPCTPT#fU zBq`V*%+Zz{0O+I1ExW1ZFXMpj!^!>1saLj%X6oy)tG!O`rW_>TQ}3q!WF~=Ne_MHr zinng9mq7B)mzp^`6`HI~1Cwdc&!2v+7)Tde1Brd+QrmT?jAei81u4JF-FqCheIIYj z6&K7xr&BWJ|o6Q)a_hxp(M6!mNmJ}?1 z+gz+4j?WNAXi78s-s%fCKmJsOCVBPCQ*CA zogK#Bk{2dz-+yFIUIa`0zQK_sbW6D#F+%$oGF2#TTU`6KcNdx%HU0SM$$wVt ze#srp@R4}I#dzsm=!3RYcO-XTi7S$u+0eKA_br35$D{Tm@vMKmv`1+4Tr2t?5cK6N z`n(j|sNrTG%DEq>qRv(CN)kHcKkcj@&AG_-iPo#Q?omU*APNb38de&lB8OIzyWY-PnWb(i!?nVoe&h9ZVul ze|2MC{ud}^be@!E3(=b}V7DK92ODaEz2Pqc%5K%OT>2Xvh$4KWnM*U@yx#FZpmQ?N zmQ!P9_FWG7GsTx?Z8$hST~Z-!TOpl*cr8R{TKfN(kU9Er_Ce3#-!4x?6=v#?nP{it zRc(>$7_hk=q(4AnE6l<)K7{A=_Bp@r3-$?wG8abLGIe`KStu>q6rzs3rAPaQ21Ng* zEcfA(s*iG)rK~U54^Z5tiu{g*oYQtt%uCW*UYm$)wa+K@J)Ngc`ImBE;IvJx<&qtV zKeHdIJc_f-SktXIqsrioR}Zg7qz;xc{3|Uwlx`F$02*yGj>KD$N0{ylm>|>fLhmSjbQq3$SlEmT{2Il+KGETqS zwyWf4lSjxb>&kid^Nbcz)(()>&PNrsfhbZQ8@Ka!ik+j-GjOC7Ak zM$bu({0VoP*`1n;*Z;dJ_xW?aTln_{L{=G6mhF2`=<)_!$MV3`9M znaH&EFStq9Knonw>BQ>ej}spnL|?I1B>!(-kpk&9IXY@JzM(B}_I$LVuaa)ii5zSY z?&C?+n=5>a1zsC82ni_SLl3xVut|+ z;(HQSl}n<&3&Cj;Nh5AQHpB{n;x2mN5wCyi1&jO9@u$Epa+Rd*C=w(YzH8shj@1(N zN9M@w2zIxjeJ*7kM{!%MIE*#MQSR)j`GJ}FkSv^?o73p$`MnviA*qhU3g0aU!|-*Q z{efe}Br=9~_dz0Th$S!O7M)!ke>OZLY6>^pHB5;$cKuq$CmIs+(mKTB{5-|&M~A13 z*O9XvaH3MW1va$XiJ)C3`2KZn$jsq}A;E_dN$v`vp>I%-nm^(VP!aFU*OlwGDY`br zSACVEt`t>oJexmJz~MXdBBf~vziOXr@b@xFgZJ=q@{2i<76o?Fs9YjsK&r8%zI5?|Mq+Lk*1cY zz_Vhrf~7qIBe4@k4wEH?Uf4va^JebEBVaC>3xI0sqEGR$oyxBb2i{}xIaF>jt7a^U ze1ksNWUWn%A$n=wb)B?TKMQ$kexWcF*6WwNDcyyxDhG};bOuSCPJYB|(RdVxd;an0{4(S2@wQ49?eIO?IT$5FqN!#Gyv&UB7a9B> zXYun5*$Q&cg%l9IC&GtoeguB}btOi=S=bSuUBde)fL%hnM0A5FuThu8^~jC@CIky^ z$7hf=fdPGZ80Lh87=EtV;BC!c3CkVea&tJNIp~Gs;+OaC7b`zsU%(jA-wq~ldT3ch!+FPjE zg;=SgNQxkM`TgEI@11wfdw<+}&pqFBzu(XIv%crtL{nn~52pwx6B84Uk>OQy#0916Mvs^O{Z~@%0>TBgkrs=-p_)UM7|F{%23?_u%@+yP(#Cr-IU*G#XwYThH$etide`0-1jxLAO3uM?5$Zi`221(gbW?S1{J) zuhi@9&~USf?pAwFo3i^-#^V9Dq-d9p5RO4}nafNwq1p4+;@=*lhU9R`_{Q4;UnqCa zpvUElJj<6&1k;A@K0!?>vm1&Ru#GsbJU|tbBkco%QZWXtc{PU;kDvSM*9F5N`w$^z zvNf|ufA!|wEPv1qv%12066WKfa|_b7Sk~OPl(Gr;Ivrm$u&sjJO1B;COuTQZsd@{B z&p^k^s==13zRiEAm#Chi*qvQemoAwZ{Nyar*Gs6;8^cY%whnPNG>Nxv=Z=Vp%*n7j zTkF4O2D+)*Sk>;wRx#qE=rJqVol-X?r-L1($5uA&#l~R}+57n#CuK_9{^>o_~svM?KqGs^iA@hhEl9D#zU{Gp*fqY5TPJ=Ek9N=Ec~^ zcyF;~KP|+OSb*!5ITP1kcJ8H28|jBG+x18NHLQ5ZMzo)ios2wvuwd%-`Mb@vwc&bp zbn(eH3IM;UzTVEPFETVds|+s317C_LdX%SSf0)Jwc(yp z&v)~A3@agfJ-!UIfv4!hhl_nT{0|PU)z|`SxHcaoIV{}Vo>4L4jC%B}GDNiE;n@>m z0_acS-iBWVRn+WM0RBxPvt4DqYBxoyY5C6FS%WQ^tkeVEo^`M5M zYcD8`?fn|kN1nV*?*SaKO7B^|lC%87hr6?Xeu+M()3;a7!<&Xr>0Gv$!4IJ#!`11Q z2|itgxra9$^}j4-okC@{>#|EgJ5k0C17U3A4Q|7QbbR^4-oi96<~jHfxtcl<&vF&H z|M>(m%t*Hz`C^>%qs}U7Ib#uZW5lPskguk%bMi<<4aN5^qhe1iDU)MzbGqrn&Ll0< zV;(ITtGv^EZv$vScyw19q~G9ZM;Ng85$sERhc0ESlo7-!{<VV@?I0k#j3TWt1gYf^$}wj{Rj~Q z!~rp=e^o~IkRbRetH8liy%<^htFt(R&@pW%k9g&)N<1AFmaOyz?eFU{Y92 z^!wjyb0LP*FE-_K72zov7{2rr!}qb>f8}z@GPg?8&f|HRp_#+GO945Q1_W5zO`>Bk z&)>AW{^5lVjPv%L8>DrG%*BDwJ=-T`XdC;P>>suRGJ408u->taIXdu`?YRh6s)C%u zWvg$ZIYZ*H)%sXx@Xzy^=OonGYxi`AgekJb{v?H1CF&W6J7KpC>n5)fEGPXltV&X; zU8PKF91|@~wtEzZk3-T)?un14SwF&KSbrQw@=SrDqo#x^N+!n1 zK!M{hBdpJew}2YICwQUcr_d?mhH>G*UKO9eTuM;Za?G9c}ND><3U}x1=e~XGrb>9g8f0)&se0w?;>v;uggP(~Sl4MgdY~KTL_NgPR_8 z6W`PmU0`p8axa4{Nq740OuqPqO>-Pd$Uy$!U>^buW_vDK#jC&YRc|Rdxf^(+{gqXO zLoDA8CRr$GI1s}lc96R?O8c5@KqEcs#-%zMv+T|%(yfj;NIk8JXp(7zCf+4Xhqok` z272&iPE@+5;Sol<<#34a@n$KtJ1bV{m`Vd)S+oO_euuIDbqGQAAr0%Yv=*&_C2W1e z7{w_Xq?sZruwkIdMxYwVQcNxt_XDED?r+?o+hF(*j)TE(iHmurU26pe%E3<89nLKFP}6Wq1nwp6YI+TVT4=+v*@8hcu&8)^@cyp20(?+d@7Aq zy8-l360N7p2zeIApf|X0%N_TJRVE9_aHS>2UN6Jl`hzo|)G8U!)*ao6^Fl|5tvwMp zF;75Lh}(=>%%*VHyri|lSFhr;XtksLYwUW*;o;i-8`$09IWeFo1!6KzgX9A4pXAnp z>gDdjdRbMzCpLqCu`<_Tz%L5B@Y^w2Kg^DNlwTsO6K9)Lrl^o^=W`|%P?L!*Sc0Fm zD%^5$0GbKwm1#Pledj_bhJsyT?qm*2G6{SruXk^c41d;cp6e8(%Mzl*coF4g%LS-# z3i$^(LURgJarVd z;2+F=RLJ!cS8&70WF4SCd@$H$qw`8$VkPO(7gU>(AMnVhA{$W$b^LRYh!u)Yhqe9w zSBGxrJ{Fr)rByU|G5U25W8U~s!9T^jX|!D@BN2nlY_2{P>wZwlaJ>G;xno#DN89KB zYZOd7&+NAuJuuKGuELU#*G zg}iUZDLW{Xvk7s&tJ6+9|3^^#s&wd@4V+TOVO|K-$ zj%rH80dpK|VflC=#QSl>3&q=S)iUaW1L(V_NZwVITaesgNSXH@~Ao#P6s$21_bwE zJ4rGx8L6>~Bg45P{~H%0u!{^Vi~t4v4-l+i596pL2Fax{t9DvPofV6lcoV=A&c#zM z7QvzfAJen)2xbH^mmen-O@{MsNea4K@OaJdJCt4QIC-|6Vcxt^9Ij=Ph*hI_@nvQG zZoRH;P-a1e9yh$#1*!_>0{rZRM>>DEOoV)!7qaBzwaqW}fZASnp47GKq(YLKVqP9G z@}-zu8cT*lH%k`+Qs!yHwOjSPthe+VL=^s|Ca^5;Vcvgo>A6q-Jvht=kW_o64G$43 zI(JQOut;RQbGzBjDh{9#!>A5}d@-?b5+>!k1CL9?lL~pg11KDv(9g`wtfCrjl zz@2^)Yrz5@E3Si5J8`md%y_Yw_&}#u>KuW z<#a@yh&Un)d^Ni6@BgCe-cmjEJNeAz;~Xn$>ijvbg7_EUD|0X6Mf3{PMe@WpIANW- zUgV`G{)=d|(-qpTi*W39ZRoy>EBNZTq^TDv*N4RLIIOKFb0?n)`H+GJcdBsM2ge>8 zH=Gy|@z5C?RaGX`)JCqo3^^P3or|@LjN&1}i;x)%kj}cCuy5ddPx(b$Wu*}YdFsnA zH6>2w{m7pNbH1l?Thh?WyDsV{eFB9nvBT6QtSc-y@zsiSP` zifz7d=7AMSV#iEd3AAMW9n6qdO5Jdg?N8#V9Gj%zzy~ArD)%t(;zy%5>+ZjgM z2@^zH920bb0(U|<`cSF|4G7xWQ%#?zdBqWyV3u(H|Eds8Mup^z25qYlb_R*qPSwS@ z&0p1VR=5bQ87f3xoVBGP;(@~OQ8?e-`Flg9PquDTL~G)s6Jc0Z-JRchQ+di#z)y)j zaI(n$X%PqQ7K_~COHiOMT(>L*@Kqm_dOykCv!{_6Seg6{Q*Tud^HmuPA-HghiIx0| zyh*s>k1+S?)-xC5Msj7hV7-(uL7L*iy+enRq!b7&1h_AWb5jWlDrIbL^NKZG(=&;X zyJDSq$Lh=_ltQ?YW#@16b%{dZV=wv;6|%LqRasrJqrJjkFPnK!tOws7z+l>)!k53N zS$F*N(tD>ZDnx}^uRC{gv3lVjXO#LKaZ&84q5?`b5KG1wleaZ!W%RZEOH4xexX)Mr z2M-+W|B3k0M%NSgN1Jm+tL+e4TCr!w}&@PnW$jn)tQX48E69C^hp<6t| zvd3UrG5%&rW;!h4bYpx&wC}%G*#gN}@i1^N!ADSw4&0>lOQV{d&r@ud&I~e`k3o4q zp0>3fz`ogKcL?XSVP67w?L;cSDAa7vZzZaPK4uiuQ%s61et;Wzc;R2Ccm#syq;+j` z@UBtTL)f)HDq)YS6N`&;l3Bqn;?<3}v@~kO9sb1+Gwc#NAG7)FT~;6yO5*)i|`T9Mxp+8*yZ&hL=;{}FSXSl9J5F$H-N-_KCVd4=Tac`T^xRniS#r*$cm^@|2+7O#7x;r)xgnu5FrjX!xRI;gu4f(yPGfbF8IJJcP!EJ zz)$4?&{hnNjchgK;e_%tczu~yaieFEMP`;W_RS#g_KnwK7AFCNC0p@ZqHzZhDLQYN zG0z!&L%RNf53D-j#bw~W)N5IsUXHjT#G_gNM!MyuN_TTg;F{yRb#7y+@?r1$ZxP@>JinRM z761TDo?2?kM*ewwR!-lU5I%R;uxdP3_9$vrTCK1d$!vMsAN1jPSi7jOKPF1E4wSt; zTCFSshE0ZRMzrO-O5t&F`OLMrF;Z`-)Dt3SN!X))n>~$G4vUIX3Zo#1@^4FHKDqu~ zP%we?9@QS5zpAqSy>%7%Ya;kMZ^DsQ;{F(yL+JCJ9_5*I!TJXMi+g+=j7+6mQ~L}| zK=8mqC#V=r%%aWD)VNtmpwMZ>s{3wX^&g}u;7x&^K1DIh%d-qPCz{!QD^viC_37Q6 z46e#jx-vE8)Ii0H9dhTcKDU=DJT5ItL1+ubse3?Ct!p`isHmI)3}bs4Dk7LkF~KpD zrNI$WET~nRFe2x@>SfOQ=9Z>tLJaROS$({r&&W%kYbl4d1i;{FN$ELP4 zv$e5CUR0S?3j|TJp|eJOg5}qw!lqnq{F1rb!P6+f$7fRb^0PT#JdLQzGAb5*JsAgU zR~f9%`_{ze?4C7%JZ=-|_Ry>d@+o^lO9@)sx%|LD{)lSOnm!d0GZQ6qLxG3h()uB6 zlqBzDmpvq4v0Q;Y`A01T8@|H{sm9K*;QQR>1mljHekHK!Ov;WS>(2p! z$R^d(cX>~i%E7OcI@ktBo=y`TOV?ZTbkr);*T6YNmIyVTjAgkkA8EEhvx^6&ch+{s z_U;D*DlR+CWX6t|7~(Xf4$Y|seJI=9Z>zR5M>d?98uE*po@`SLE!1#Dh~)cQTj5%- z`asD4B&NoBY99@R_`L%cPjLiK;Qshc^HlsBhr3-P4PUf<0Tt+~MU|H#-@|Sm@jfmx z_Cmc7y=ph*p;x&WlG{L{lPiW^QXKE+ zyZ9@;PARyM$R5v%`m`SyrP_!((ClJD2x0ODuZP7 zJs3Y-cEfoGf4Ot_4pfH#qgl~fh3wv#wt%l<^%YFq6lj5QTNJIlX|^%ZqS4}vX=|-n zRBQWT zV;-e-D?VMM?_Wo@Y$GQ=Cmw+}J8B>b#UpPYygjduCPB(7Y>`cTv!s=zAMR{Xnj4t> z9{W`-lV-xNCp$(AW)93dbKCwOLWGKJl)CirW)WzbfxXy}_!-5ky#6b{1u%JGO6m2} zg1X#htTX4!8gf#a!l;_1Q|we04-;>0p6M^-#VYN?1B{}htS(l<>rd`BdR6;4wNiVu zR3`NjfqHb5NQ7Sv0{2IdaB(sA1B>g%R>)vH@DBBQ4} zCqww0GW0lAeZ^$0i+f4F6rY{Qq3bi#6h33qiM%cn6*Lyo!Ch)hkmy#{{>5vRqs^(s zRyXc`207fZnd!E3MeEh*E0m3w-Q48ql6&82Aki#5Sp$CU%w+z%c1}Y~ysDZ7H}4(S zu65djU?g6(t_6|i;5R2>$;!POmq(6TmV<`&G%}#n!$#)_C{y~ETrrtNqgul6tsLox zescYs^j@)61BP60?BW=@|7nvyHQ6uHeKLDE30c^l&G38F2%2Jj17=^zLJ}p_yjpE? z^B(|zYUMa&imEj)xFPvkn^$%XVXfQ#cX=F zHZs33Uz_MpVH!>%8+^|WrdvaNBFMqn|G?`z~6EO<7Xro*w*{A?L^t1lNRR&*{>%Y zr#Yg#CERR|>8z{$Gx=YeQt>x6^7jTng}42eso8akc?1iZLjuHoTwm(haT@tt9iycU za(f0@Gn_~M@DknXne3)#op?#wYw&Cj6bdGtvu}wGD;L7PLSJ@f{WiXGt2HB=Z^Uj1 zk^X*ndkUt}E3Wy-x|S_BswVW7m8B~mHrM-ZtKk(!cZgYr38yRpz_|y?Yvc5sSFt_T zl)t5C0{GS9hFlUHUb;|iJehf$nCRFhO$2c5q|y2a=4^=WYl-R4E|aM^c@Z?>^SXx` zb%PT=8cLES|80Nq4dk8|zD9$Oo0LiR+SN6YY5 z4333f?JvndJ`CyM^aXaEM=71eEbb$=f+H{Lz&65N*;XVUxNw%Z1rvlSj$EMwvFshl z(*fxCYJYJKM;Ju1cQvCegHrS%AS#=m=tF& zuQJhc0B{J8b`%FR%PM}sffxTHXoU*?8TWFmBtU?Js)EFNNcD=PO-fURCY9IS3%r{8 zWWsHp+<#vO)k6ax<_-IKFcx5$H}uLdT<-ucub#{kb^iL=f7S~AQ%_?{EwkrEpKz5G z-wKX_#Jl(4)tcMz1A(KujcLL1^}+jEkq!Q~(a<3kRPHmJ52HsrJDUKh4GmPMX-T}H zQP0Vrp({L7_H~nxc zF0m2GIyWp>wI3MF0{IW&+vvTKZ2X%)g!_BV;h7sososzjFwBqb`-B8yVDVbcv+L*9 zy}qZ9%CwvIaOr`33Xu*4_?M?0X8PwX$4SFfH$4-O8&UC63&N#XIOW&F8--^R6$-|D35uZMQ+I^W*Q=h~d(bfPLL~!y^o6b0inckE52^i(K);Q)q=+WcU8VcD%7)f@7ake}|IJ*u$g7R1KQ6mcJROa+Y>^8762Wic1{mphG^1banI0V)*u4Oj z3!$?O#3}IOCRSwvX3qidcJ(ngC6V5an`en3dviqz!FOX=hd+@E+TXZuPn#}zu1zI9 zRwB80i-#DI@9mzg+S@DwH+B!sNv)@ZX)o(4U-`H_taKziBbv_4~Nzc-{?t-|OwGJ|=m1%QD7M?qQnTELFdr^NIe( zy)&?Mn;OjQJ2@X{XmVeLM#!a;iQKtc2Q=sz4U~GrURwP@*Jnw3&*6P=eI{GLV|nIT z@2diDEUH=ouZ*+oL1h?)0K_V zM51?Z_f7A2^86@TfgV-Ksh(Na z{PAlT{q!Sc+A9q=6*X(5*!)yGAb~= zWglCLbQmWu8$qzMLz%3rfrQk?k<`Fh z2xBuabd|2$m^5Iper0eYMh1i~mJd5F6mM2v(A^a}YWOQ8p$185wLos*RRuWz1q%04 z~W?x z6X}ZaNk!eD>&})W)&xiEn&7oY7y0G-n0xbzX>@R{uHtz~yy?-^xSI8v%LPI$)-009 z0ht;WBJN2`j`%^=2D?1WcGLPL@Q*0q$TiR$AJkXxP>+26wi*cKX7BJ zlkHp@?a9@lOv|GG=E;?~saSwtN{MSn4c}s;p+!BJN;!81=X;LD$&l0*VuE|>h|0u< zs~HxIwj6r?v4W~;iE_N_^2dZ?(RBe9uPD=0lX~?9Ty`yyb{nA_+)*S7&hjiW{`76UQ zvcsO$itM3BZt%%^Hz$R3I|`1{;H!yqT-Z70`mQg93CU5e14$XjD<#6@RO>RC$SN$= zWB8Im0q7~e6LW+^nAPQs$3P9`v7B8ET8hBIBpUU|ezlamzQ-VvZ%$WUOSMP2`=rc^ zdZRi3+76I=gAqPDX3J%-BzswfH^1Xx&w4EEGZMYg%h7%oj$~^4;F=j?TfVnx+LFJZ zX4E#k(>2?L=(JinOtB3lzh0^DUjKLUE*p8DKE;*b5E`95-humQ=x%SJg@W}iXw z-qJ6n7;LzP?6qCVL`%XulPZfE@2KwZzLS?ar$rQCxTzun(H_UwVVJLE)sg>LUF z-_Tb{5dhjRn=lqgK}(W^^a}ea^HMpC_w{O=RU3$#D^bA+LJpc9t=f~rF{`>1$*3e# z2+D?}C@zv^y+rATXRSOaB2KGH16(#c&ppLN3S2^;ObWi|I>xwuvZC(l#?mm05Cj{Q z3LYVZg8~Yk;Tb4B7iKfEaHdK!I5%4jnCnJ1jn+Hkcp^O(LVB%#gd1KoRxf2tuk~9i ze!j9vzejhH$d3NKfbPG@I=4rQcXfubLL*~u$R5AHXUcq>p-}B^*uh&D$)}{SbtM|6 zsHn13K*)+Q^qXr`A~go9b&GlcF4|X3-Y#pS^vEFYRphRXCA=KTfcqex?>>H!Y=le}`fV71k{z?nTs0NfM=q;+>O?cmo_E`QDC_6CiSM{O$t&dEGu-hVpJg;y5< zxoPSV%4%{A_$^M+7TJ}-C?F?J8pA0VL4Z>$R|)+<9nQzn$>fuNM%8t5EUpUk^0*et zAg1*wgoVpfPnvh%VeFal?N zc@90waSBQO6<-ZKjK%^6=$C}Zz7{7u&*|rwYbgPFqL$SPyN*%XS@q74!q5Y0_%}zK zLNopLy&xMj)c*2VE}1Aj3|IWe{}jD$rQ>>Ot9i`oXzEWmb3W0~%Xu1?37CN1`E~tJ zX#BxNm>|=@_gy~QPHn@31#)}vpG?lEC!!3hjKEL#60X<}fkBWSsmLm5Kj zo9k3VrjvA;VN{;TR=C5nU!i0Oyrx?xmH%4d29D|)JOG5g=M^$X8B2LJ3F+ zo6b}o_vU)d=w!y00GC}-)cZ4!Du7kk4RYNh95h(%x$zuD+ze1u7{14Qj~UXsACYCz z@z5Ig!>d~a)F889W*;V=ZQb+Z*j8)7|E0bB;dZBOyWtkUkW<^W3252XMJX0&6%uRd zs{m>OY>!oljV|4-GYF}>a3=`kl1*AKXiznb<5pFz7S%{k1i=fN=9S}te0~2tdDLJB z;IVaDnPf;hQPnq%;zgU>0uKc}{&g#GH~fJsrPgFnkX}D$MC=D^Y~5?_!@t$DQk|lo z1sh_8lMJ4L)uNi$-JXNhdOKhgm-A*XE3gGkCaA{ zRP1|NZwUE|yFUh~9X~)}$I~;$oQPjG*t+esm;F>!ps(#*Eea*fhTuPvOl%(X<9gUb-$*L{GQu@LrBk-GdFf#0@m8As|%Rr## zf+*bPF&?;8G`|DXe(2j4{NaS?%q#qZmTca-x3m`wF+RN7}VlB=Dc>43Xw?R_w?% zp>XRCC5Ynw2hT0?!R21(K!qQf7)QK#+p~|Sx~q(t{;(z>7!0b<)0zN30p8cNwCX;ab5K!|CIh}xCCxdvhIlHPHT7(qm`>&!xpZ> z**qimN~LH(O4HbuFNrjkRwcmrmy~8{JJdH_+GZGeMAiE+8C8MLsDIN920ovwjZz=O zU)FcYJI_uMSCzHk`r*5|1f>zRS-()0Q5ZF}Uw*>!&!yQZ<3hZ7;9bXa>zLhQ@}(1M zQuM1wqmfy*;J|?5L8Nno7e9tpqTTJ`&!tUncBfb5UUo6Z@qxd?qddnS6_GujyJUs^ zqG;L^56}Fx^mAt%U_u;B>E`{`tNTG#%Dq_=}+)mh`ER7vEB zjTJ{rRG1(sL;Ih1EeB!I5Bw8S(=MPl3kkzN=6iF?L0tMl&?Kro;ilFq6O=(qXG!F< zP}4{Lx6`>q)TM+ehD3RTB$|E+iU8b8Y0&|lFlcQ1fS! zpEs0=Y@#FxV$4?>N4>q(zLvbm{we&M4r&l!yyh@cc3XC&`WX1yxLGez-E~RJv0J*N zmFD}cxj$0_R7DID+Yn&#{JIO^uT)IjgXr}= z%Jl4Rn++58_P&Ur!M=mJpAy4zgRecc zd+ov9C0FxPRn?&nA^N;1M~yw$P9Eg`D-T#aesB;UoL;YtQhP1Z@ zYcKTvb57O@3&@Qf&~q_f;tjBRJ8XHTE4GKR`=h)J&eY?sL(>jGJEmZL7oB19-`#8L_lujw`~tI6Hugrg*8Ogm05?CPSE}LkFF?g*F6qBGG#o3{H6KB6)ZqYU01DE I#Wv#q07IPYQ~&?~ literal 0 HcmV?d00001 diff --git a/src/images/kivy/text_images/H.png b/src/images/kivy/text_images/H.png new file mode 100644 index 0000000000000000000000000000000000000000..279bd1ce6db1edf17782fdb3babb05b5cfc39ecb GIT binary patch literal 5054 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 OU~F*fdX=7Y-2VXhe*nn< literal 0 HcmV?d00001 diff --git a/src/images/kivy/text_images/I.png b/src/images/kivy/text_images/I.png new file mode 100644 index 0000000000000000000000000000000000000000..c88f048d7a0a28879958c158d554a7457d21481d GIT binary patch literal 4713 zcmZ`-XHb*fx&$vqMUcDJ=eBZ;{*DtFIIKM+LV=Q<+fIy3oJf-gM?-<}n z*!S~qNjA$>;`DTz?-C9%id*<7yZPS64E8yL zFz^6qcLfs`2f5V&R*u-xf8<~@0a$%qh~DMuVEsgLn3WI!L~qD4wei!_@d_JE+t%{t z)V<4!HsM|WytUcjf9%%i;$A3WvT3&y!Pf~5{(6{G<>lw(xp|*O;E7Z^;^^QXD9oHpxGz22PU~7ZGzTkbuq=o(Uap zfAK@M81{o!`cr22jjDKs3+u}I{f7RzkG zrznPUu}vvO7TFy?zxd<3V#_ zmda$=g*2vVYret&&e3<_9}ZDF1@+<9mplSri!={q@)$VcP4V4VHZS%sfm?(wn!*yd zTnV7==2h=)aYOkv8{-`O2-z_4Ve9!bJ1=2PF2po>zRY;*B0&%i;lRCc;41DEfPZDe zL?9%170!dkn}?g8VedXQ?saOyxOI6{OO}%!g{KEa`x+QDt%A~5t+I?1VpS@T0@|7{ z?XUe-UwpKVmXP{FLOQq?!kug)9zl0K9X?V})+S5{$HbAjh_G>{>6c9}Nu+nLinoVX znl|sK3%=a-#%5aL&@pZ}{a4nwsVUW8S-yo#_I38FQVr=m`9AOEm3-1|Wd_<0ihTCo zFK$d0Thn%r7eW=AScbkz1|@vy$Y zP)sFco^K0lWI6RU90Nt_N7d1sc?!;{5mxcz%GKTfu~8sGC-KdT?-_dKJ?Qmza&1)> zX@@}A<4c)0wJW#Uk1fqsynp2o(0{iX`Na3lQS^n;lI6@+vQ&wiCf$17+eOopexgHX!K)nAyUB+xbI-feS#HlAG%>myS!s5- zWDjrK))(H|xwoz0=Hbv$z>aZtUO84!+5qccpV?FsR0b8zl!Y6UZ04Xj33Ex@En^h# zWA*9`zc=S=X$JRaUxU_t^YkFn?a)AWsLssKD7?;@iZZ*X-PL}v1+yF>h93FQ^?O%? zbb0DD3|~(NFM<6t41S}Bq?l9%w(r^uAB-uh13rXFxkai3q=;Wx0V}c||mY z929bwKj)zf+cRN!e7-VGEZ{2yxB4u7z*qk)eh^OS?(9D;NAt{)OB%$~JGxAc5tys= zvXf(mfLyTM20}WqEZJ<6lIo+ZSN zkJZ2zzJFy*y8*;%?zV0X!lkH!$j=3eESLr{v?-HZYUZ6&)#67DFU#--q>MjTQV&oM z4aF}&8GB)(Av6>Vy(Q9mHjwCcwZtbrOVc{6>3UC@I_flscxf!h}) z7*hHi7ERx82Dq5fFAerO-on809fHpG$X~Iq3unZN{TkmdL*RG5GS+d|&|)>d)$E}P z)lh+B(KRTrEaZG&1t~4%N4}tljlg^Uc15F+z(zHsPes>OQ^(@wA$Saj6Z{Br9jj3) zg9bk#6m303!4e~t3OwN{-Ix};#zW7sUvPoA5D@}W0@OV~_Oqtc%<867B2JxzEcYgy z9vF?q7+o|9HgaY$30D?h#K5G${-W}Md8t#NQQMY@y>ejI(3P_e_Q$HsVp;HHet5Hg zFbmyb&H~RC<@D?f;q>SVIo(uqc~|muilDVOewwo(CL`FlLmQfiB_@qXdv753}FpGLqxujv}E;-*1d#lkJyEk z`tB^-omhC7to03DUNmm;iu5h}+B1^cSNl9M@*>Cyt{HtDtECdIoV)@C0_c)}+d76U zv#Om|O|Nh}YYi)4A?JXUdM>u4OFFiIrFUTuKr^a=si)WYhd2Ue*`D^mL9WIRyd!r= zK_4+IV07yXboqIQ(Se`fxZU-g4Qdaos-1|QQ4S08jBr@$l;u7d!W;89E`5k>EpXOH z-iKP@zrZ^3fr__Jn^ari>yoEW2m%d&b_zK{Jeu8jaN z_%4R~KRv0us3e7fjDNPje&OGk9SCyO?g9D+&gKrZ{Z#ENFD();>Pz3zABNPRp)_{N z?_Dal$5Bj@Z(+Xn#FvneOK<)80=bB+w`K`+YX~kZpKa&yt=2c*-=7UMx-uQKMVjX@ z@T;h#*n3NvX1&KzBC4)_-s7M&yiAX9+mh!S-iIO*W*{Nod_1!SfW+P!H^5mj8_x?e z*KB_A4%~$=@d#V|SyngO=)S{Mn_|ZjC@^ zs$;im9-E8onCbrzMoqhUbxYkyNdKPxg8u6M!zLBrdR|}kHsBx!G~e}`_!g^7@p^K4 z-yZ!c2itoEP^)x{ys~c_*CL~!7o5m2yr95Rs@zZ&hDNYzVtW(XZ;)+*9tU2}M^(Cu zSFRN|8C>RLm8(vjiDHF8SrqX}Pcf6A&k^ks)yv@5HY2EHg}lNu(QtcoM)pXshG=sW zZdWOVzoiDN8Kl0MFd+E4)v9fVKk|dD1zUe-QtQAut3fg^-4!~R2RAo1`G0_A7FXyG zx5un%fzA=6mOMP6=x(=?-e#r z3wH1d+K)HhX=LR6j3rp=lH*;(FN@04rko=?uquX6)rNcnf3f zFTc>-uwGE@bZz8K`%}oP$QGc`1a&gjwaV^?Uv^|19C;kc1$sB)=)>xAj{J%Qay3BbEK;8PBe;D(=_gnrT2u6mFQ~1I{r=Si zzp}b}YKBJxXRZTvC2mVE&HcZydW?hWl|Ff`m4YmIC$HR@Pir>wP{%(#A$w(fUr}4^ zG$_T17um<2yB6Ki?)i`|^kB+g=(^JE7|+K3n(DtjKNF>bEdN%#sDsFX1w01>HL`xn7c-)u{5BpzPYx}D@qJtS?X5`i2m%-M zg>H!hM`RD$dcYO;PZG4VDgx~Bmvu35RH2Y^X~rG_y1EXD!;m&R`iC-ELo(FC+{c9| zstvCMMhPaYfH8M}DoPy%4zfo4-IxrWA2d{`^YQ!S$2Aqk4-lH8o5DENv|tiMcE@;n zV5O{#zYLX?IIk~R*)hSX<_b;?mE7a5~_yQ7aTQ$mDA&bVGKIz zYE4J!0Pf)zyXkYi|u7xPH z(`Y7-%TIpCHU1vY0HT~W=?1EKmg3yb{vFOqf>e?7n*twCEmNF9iS#oAe)7C9FZ73e z=)w*-U9<(*0ohPFti`0(!2K1fM+Yg+cZy`;8uLnPB^}6GAm@g+sL(km;ibrPzr6a9 zvg0f2adUZ&I&u8H#AeD(4HstKn{1p{u`6_%x2S``Jg0CdgY1y9@zGDOVLsAo1ma9n z?_~H+cP_wsQLN9p{z-S)*1@u(ZK;?vQqF><3;cxWXi=f&P5;X(Ir7p4yBs*5yfcUaulMc zNtC2_W;$X!RH<{UozOy$_@{7jd&lpuB&1vSsPiA znEtpS(Pi?GT|S4AyMB^6c?nEbt=@?vdz2za5RBJ&uj7K}rnhSi`y97FJmBwIH7kk! zX02S>)Z>Zd?{^AfQ9Qpsy*%tgi))j!ok}WTgl*45kn}qpzYBI+S?BS5dQaZ+AQHE{ zE`T;lzOS)3Wsc=BR5y&^qPc^rwv*zg`;}BTybDO09O1Itj?T*qRiBN?Zx?0iUG{N`iBTWt>OHn$$sg3v_~l-Oe45QRqeFKV qlEYqnMDKXXF*mO9s(%ZTMtRTdZL;)q81)K?=I)*QxA9tzQU3yY6)uGU literal 0 HcmV?d00001 diff --git a/src/images/kivy/text_images/J.png b/src/images/kivy/text_images/J.png new file mode 100644 index 0000000000000000000000000000000000000000..1533117129f4e267b4e475d10a41eb7fd378f7ce GIT binary patch literal 5841 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Ν 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

QYWluBV| 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 Y9xc^R`1J?OaV;!OwU?@x14|+Q2gQq6b^rhX diff --git a/src/images/ngletteravatar/Galleryr_rcirclelogo_Small.jpg b/src/images/ngletteravatar/Galleryr_rcirclelogo_Small.jpg deleted file mode 100644 index 8e3b1c35f346ed5f02ffb532adc37607331159e9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36701 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 diff --git a/src/images/ngletteravatar/a.png b/src/images/ngletteravatar/a.png deleted file mode 100644 index 1c3bb8bf45035627880f9ccaa45d0fbba142812a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2378 zcmV-Q3AOf#P)6?Cw~3;`~Lj>wE$$g0B*YgakcN;Cx6$63yv03- zI#D=fBeGyVcHFn!-SW*@3azmbTP#soRIO(wE&E000O@NklYg^h% z5P%J%BxD7op`rp-1=I>ct@Vbr{{MeK z7=*7S+jG%FbSPK1*Tg@HxrgvjlLz!*I}CzkTF50vecZk7eMA0rT0~g`ZCu(_6z$PM z3%H5mpdJK`dl18N5Y)a3+w)ORjB@5sZe5(nf9y|e&qq08AP;G|BeyO(lPhctig!%6 z<+_r13m-_l6VGDX6H*NHE^t{SLP^$KJQr*e#etMBL7P@3?r;-_1zX|d3gyi1?=Mh4 z?H5bdzzO7&2V!`W3^(@6-%vORJ)` zg5uiCo-*h)f5%O|MBHIq`*P{@Bl3RxYU)Ra ztM>=)Q6-pTANv3~o9e5cF@fIiy=Bj;yNENXb~aYY$r7sH^&=lw2s?O4uUFl5&@TTQ z)9?p!+=r_@O%bZ!jm3J8yHN1uaJ9MuJ=EOBi*#3af#c5${4*c^Cu(koi|h}F!60aL z9gP!y1xsv5XMqvQnb&ou4I{b|wNFVgFp#sZt#{3lr=#p$wqk$%I{-SNf8kQ-N|iL| zthX)ciEj91C5rDGrLK{w|1q@TGP`IuovnMv!GJQOeM-%M+s#r)W7KPEKBZq4`V)pOrku9?(cGf5aKm(bxRRf!`jeUwx8}#S7Xz1+-tNnT zD27rEOE&YpfLEqIP+WVn6Ic2vk7@Yghpd4f9?~xxK2sG`f}Z?H%@8kPhLpAF8m+B0 z^*p;kcX3>M*~h2~Fx3Ldj6SNNLdd$8fK&zILAoDa##NR z50a_>a3VieaA8!n6v*?bM72O@!^NpQdEn)!{0ez-^eP#5Xnd zn=Yqj?4mS$%s#^snfeZAMnfCx4i2(UFs;IHI5nd*uE|4|$0XxKv~--c~>Z?3gcDDloSI46oU%ffs~rzWEBR=h*JeW2h+d;2dgk(gmPX% zJCC#qqwnAfzo08$LEaB^QMs+TCrzo?S4r+xYU*zq70?dsP~Cxm{D^b9FkFl3Q$^@g zZ$||Wr%tA3U{e95?7L;^r!IM=ByL68U{Co9%NvKUbV3>T)vIk~pk8y~(uE<=zp@8W zNevNIVI%{0E(~~R$vawMav?QbSYB;omeN-_p{mN(xs)00kf~42W|X!hExn?!ai&3L znp&t-xI>IdwNK9E4Wr%EQmbW=Z@KD7dOJMhW%x_&iJ~~9D^(a&kzH49lvl7^V__Az zS;#+gt$Hfda3M>^gL>c`ZF1#a2RKJvUWHdAKlsbmkR&p})C{!}I_pd@s(GOP=RyZp znAzF`QPAZ~ z@>)H}2>msOTE|!n%9$n2*wj#=_w89}Bn2aM+G;O0g8{wJ#D@A!j(7c`1?b{YDciU& zc2Q@1(Lav0DhzT;n`nNNzDLPI*Nh0q-Jq#Jc7UodjLRC>sbSK(iTW0LpIMMAxjGAJ zQvoynT6!m4!?bUhnhI$0&&*Q72AP{Qp-_S7Y~EBLGq8hVJ4c~Pra?0_706E7Ak%2L zl$x<^+*Ba@aJOaCsTn{oGc-3|sbx4dBW+!EW=?(!OwTPFe8~XZU_y_{rbltvgdOt+ zwUEouR3JO_F5$$OTM@pY^u9Ct;8SYGtFhD!Y%0(&TM;p#{`97fDIL;GO$Bm5FBveE ze1a>i8Lmu%b9^$4bw2r&G_Fl|=!nNjBQcgh^e~-v4Gr~1cRHOK8w{%=j16XWP=;X` whG7_nVHk#C7=~dOhG7_nVHk#C80LS*e>nzv^j$oL4FCWD07*qoM6N<$f^eLb1poj5 diff --git a/src/images/ngletteravatar/b.png b/src/images/ngletteravatar/b.png deleted file mode 100644 index 462bf80882285392898fc1bae786792f11e9a2c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4732 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 diff --git a/src/images/ngletteravatar/c.png b/src/images/ngletteravatar/c.png deleted file mode 100644 index 180b7edc34501ebfdc97fe263e73b5dd64e31b4e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1637 zcmbuAdpr{e0LQm3^4KEh*hq|vlw?DCdCt6AZgWIZC|f5cuJWAeCcD~&>1CL8=8C+g zc{W8cSyqO5q@uP%jB}U@B8cSkKgb6`TYL){rUS{$M}1LwT!d?007w6$1`Aq z;=iTw)rM;l%zZY1$DjjI2F;&TDwQd#dwaRaqkTH8OA4r)05nVjI1`F5s`m?u&gsu{ zt4ir-<)Y%tt|wE%*;R>L(LAx7Jv2Y~=UTN`o+9(rn9P`E%y0_*biH5_hw@m@Cs$YpKPkfz<#%{I zb@x_F(Q&qE*>vC~LB(UZv&dw9Wwvg02T|Llr~q2g1(Khotih2=dI{+j#uZ;~YCn(* zo&GOzftpG+gk<^=5atI2Dq!8?t?t^WaLBG>-3px1?PVyyjkEJ=doLTM!x^eg{Lda{?j*FV<;@OZEb+GvxdU<&;PWip#Qu@M9I;i_OPTWs6Lb#$Ns?Z?a3*}!b^ zT%@X+jRl>t>R+XmJteegga95u(z-tmEt9tMpM}h#f`RhoOvtP zL)PcFBglI;$fs#vRKRbMK_kJ9nWi1OQT*a_X$TOUlA|rnQPn~0C}@4SJPJYv6?C3a zoS>iw-sV5lKMS!}q@kY~0Um&%NDMFa2z`)T;O8o!&D_6-v;0SOV?y5hvGS^F*v2CI4JUNnZrqj&kuu?ecHi;Auo#$*h2^LG`R= zg40LBu7I4!Hf2igtDU{ZrSI3nIOJ(Jrf?=RL1*!1sr|>WWieqPDhbL7RZKg6qr?<@ z;f=M^@Uf>FS{-q_)SnflNwW)q!3_%R4msON)Gr*4N>eK%_*O)b8Ac$R2-lcvs0b{cNG~MQkUu;wAZHNoDi4cFLs+} zzC<(}n{udF>ViHNsWO~*5->w`Bg$SqOSrOIyw76p* zlLLtE_I;@YWM$+|H5W6Ru)LhGzKl_yWtUGi4IcHfPjmwhoXbQ^xZXEx#q^K1y{~z)lV~#sjzis%xOZabdDuS$G$ludWV7w0T@v?d47R|^*UotIGnH#?W@J0E1 J)_6pa{{oFWH_re7 diff --git a/src/images/ngletteravatar/d.png b/src/images/ngletteravatar/d.png deleted file mode 100644 index 9e983e4dfdfe506cc7c7345424737ecd6cb70fbf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1705 zcmaKtdpy$%1IE2fxon(bEO8D=Se&-n2t{pnN^YBUnEP!Q?HwuU;?l;?B@BsIuA6eJ zmP||>rO}*`OGL%UrI*}NXXD@KFQ|Cq%92bWUrKyB5utSPxVVhOR~=& zWlz$@k7;6dym+8d^6Iganku>j^_58@T6OwGy z^t2OG@A>*ND3c{rtb2%~A%n)dIeim;0D>xF(PeA~Gu9~pXRDS*x7@E88x?|qy&TM6VMIy`bTjI4k2M=$mJHhKC_`G_16#H4N}kmKS# z#fyS(=ryp7u1_ zN(;=fG}Z5p_DM?!ileV+x1EeO*h1>UyQAzW;e`Q%yWY;tttT{~y1fFFjiQ{KrJab> zl}(y20yO9f7RBr|Ijnh$Y6mocAOvt#LW8?sgo!huckEbKlsq+UIRQ-H4+`ogEQNgg z{k49bKF<(2Q;1NBDrA_B%lBO#Ipi4 z;_&4jr+;yqgY~s(bbcd`{nz`04tABYTMwff<3^vcxw~45IaD&Rzc#o)%UqIoo_lK} zi6qxqtz{~FT$6O>0J!qfrGIU^QYdTJbU zNlxRz8v!I6z*DF{jo{*8&>mqq2ygn3d-Ue}l76Q9ngM}pT;PgG~b ztKbAJV7j^3FZ3>RbNtqIH9OI{hLp*r>5_X7)c9OG-@FgRHPjU!J?+7!_!u}9n`@UV xq=)PwI%)s_ diff --git a/src/images/ngletteravatar/depositphotos_142729281-stock-illustration-letter-l-sign-design-template.jpg b/src/images/ngletteravatar/depositphotos_142729281-stock-illustration-letter-l-sign-design-template.jpg deleted file mode 100644 index 7630511f6b637e7548074e9434db239641ac9543..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 243858 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%0DFz;==Nh3 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 P0T}2#zE`8;^zQ!vHTMmM literal 0 HcmV?d00001 diff --git a/src/images/kivy/text_images/N.png b/src/images/kivy/text_images/N.png new file mode 100644 index 0000000000000000000000000000000000000000..2d235d0618e32ec6c2d4d0f2f488a4c1e5289011 GIT binary patch literal 6509 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 O(3lvR-$WX?MgJd&$OdNs literal 0 HcmV?d00001 diff --git a/src/images/kivy/text_images/O.png b/src/images/kivy/text_images/O.png new file mode 100644 index 0000000000000000000000000000000000000000..c0cc972ad641d61181a66c2858e783b6f6ead93a GIT binary patch literal 7912 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 S?#+Gx0000Y`X9O literal 0 HcmV?d00001 diff --git a/src/images/kivy/text_images/P.png b/src/images/kivy/text_images/P.png new file mode 100644 index 0000000000000000000000000000000000000000..57ec50124111d42dffe70bec13bb3d7487843791 GIT binary patch literal 5914 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 N002ovPDHLkV1kMUAfNyM literal 0 HcmV?d00001 diff --git a/src/images/kivy/text_images/R.png b/src/images/kivy/text_images/R.png new file mode 100644 index 0000000000000000000000000000000000000000..090646f5d6690043e424755a6f039ad23379595c GIT binary patch literal 6404 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 literal 0 HcmV?d00001 diff --git a/src/images/kivy/text_images/S.png b/src/images/kivy/text_images/S.png new file mode 100644 index 0000000000000000000000000000000000000000..444419cf0d38b0b6f03a567558a54831fab69811 GIT binary patch literal 7826 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 literal 0 HcmV?d00001 diff --git a/src/images/kivy/text_images/T.png b/src/images/kivy/text_images/T.png new file mode 100644 index 0000000000000000000000000000000000000000..ace7b36b257305def81f8856ac85fdbe798dfa6a GIT binary patch literal 4793 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 ShpVT13SCXZM^)E&HB3ksGXp`uS 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 K6jAaPq5lIkL3H8( literal 0 HcmV?d00001 diff --git a/src/images/kivy/text_images/X.png b/src/images/kivy/text_images/X.png new file mode 100644 index 0000000000000000000000000000000000000000..be919fc4b9003ea5d3e15c7bacd1aad66a292c8c GIT binary patch literal 7367 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 literal 0 HcmV?d00001 diff --git a/src/images/kivy/text_images/Y.png b/src/images/kivy/text_images/Y.png new file mode 100644 index 0000000000000000000000000000000000000000..4819bbd197ec6177f5a2cc3d575e77a108a1f405 GIT binary patch literal 6459 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 literal 0 HcmV?d00001 diff --git a/src/images/kivy/text_images/Z.png b/src/images/kivy/text_images/Z.png new file mode 100644 index 0000000000000000000000000000000000000000..7d1c8e016888f107ae07afa9f570de3945b86ba5 GIT binary patch literal 6166 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� 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%0DtYQ1&7 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 diff --git a/src/images/me.jpg b/src/images/me.jpg deleted file mode 100644 index f54c791fe1ea1e84882dc1214d1716712ac7fab7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31355 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 diff --git a/src/images/ngletteravatar/12.png b/src/images/ngletteravatar/12.png deleted file mode 100644 index de894b4a68772a5d3c68bf436b8b06797d134f0d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1914 zcmV-=2Zi{FP)#_VNIMJpg_;`TP3j^6Ox!e*kXn_3_&3;e@%GC6HIU+Q_!l!WN26V`hmSC{f!jZh7!`jT9%CcdjdStDISDAV46H_kX(Mk%5!u^sGfw{K-q5{XbE5Mi)yONkT9T z!!QiPFbu;m48t%C!!QiPFbu;mjQuG0aU4QQsR+}5_@k)B+wd{=KZy0W%U~7fMHEHV z4=dSzRp(phOL+ZsGR$7yH@m!0j%=~#R}Jr`qsd#=J?{$Xv{bS-^d92>J(FcQ@5YfU zTWF~bz4H5m9FOyP7?!V-Nzngf+zTf2LE%c%MxW7!y%*nk7%8G6&v)_rxts0ZCZnGl zXL)hlb_KFF?Ec<=UXKh{>rOb?IE7}6+`hjmpw<<7Hz`ESvb zVddy=f;x;Bqce}@45_Y${d#bW3@SEhD3m+UJ&qR|RBY5x2zN2nHPxVE!-hiUp`K+; zjVU&5C{%RR<3?$!yJ_688aL{pF*GzVHg70|zt)O#Gf_ks3O}Q^*jw9cD8_R=*?2dh zh%pqd9yEf&fFj6Ho_a?d=X_R)RB6dk=uAdpTQYM(|S~FyrfUS z424`#Jx$>C6lW;hIn~oCproGw8mfY3#K{YI$`3RYPFK$|z=I;vP&joEu`Lm5C|4B3 zw+K?$4XB~;X*s;Vfd&z4=v*j>drV=sGNFd5pdgNNWM6|d6k<<793ZPH*igRHsQ-%m zbORfzd_wD|$Xz#dEmcSKBnoXP&$Q4#0nUWsG1S$xXK@ULq)j&uSWV%E&N`tlH(YND zH&iigM+Fue3UQ)u(*tb0z#F)@LDElKr~?}x3rM)-DX3*1%>*k zF=40{)*Bw`L_SPuMhtVkQ3@)}h+$MGMq^4dVJMXKhQfM7_wO()LDMHAhGQK=n+;v- z4W;$%(Eh&E`WULh`meUb-(fhncojp&w$BgsDn`fZRg8=6>om>}+t=FMH`+i%rF$D$ z)u()u6qO5Y#E??~aXj>bfeFm*J* z1JC$O_JXo$X1EJ^@aHX7lt8=09h$+Px6pShtZ@gr@$HZjqCDUkOZd5`n8y2=#V5OD zmnIfN;1Nr-VWe*x!kw7GpCR2*PujQml&m~c2=~aVF~Ek=D;c5|p)}z0_&X1o;;q3N z>b2lb7kaq~)KCcuVF0b9SVLFesBnaobq8svq6E2|e+6mi>${UfaNY;dP{ulh3u_IP zKH>~K9(x67s4r^8Uk(W~lpFdY_0V^j0dIyZXy%K(nWhF7LpnIxNeo$kivmcIU={B8Zho`GAgZ!~GBRH4^pI%5Q z%A$UQrE}Vui(rF>N^L^@rPd2`lY&hdDmOpY0Zg;Vs9-aO%8jz1?qXgyreBw9z)<_u zZ<3AH=AV{(wjn)&IGok-^WF|EcqjOohV;I3u|0KayfQcasGR0mhV+1S5x<1V)i0SI z{5JSK$52T}R(9F_Nm(x~a zz8ha7FG>GlMw&DYg5{~4ueXcB^F^SN7DCQQfs%{73%qa1J%~% z#xM-SFbu;m48t%C!!QiPFbu;m48t%C!!ZBy52LSdmMf@YbN~PV07*qoM6N<$g7L1g AXaE2J diff --git a/src/images/ngletteravatar/14.png b/src/images/ngletteravatar/14.png deleted file mode 100644 index 42b692dd8fd98de75ac1903d5bf7a24624e512fe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1249 zcmeAS@N?(olHy`uVBq!ia0vp^4?&oN8Ax*GDtQ2@oB*E?SB4Eo|NsBbuNKmY!nap&3fk6*0zoo_yOi(%98g%4hQ{r%_Bm+uxkPWvCZ{ObG9#dn_W zdGW6G`lFMt-X|Tt?6mvroSToU&)>;Cc6HUGm;U=N9C`UJ^2GJNEB6a8+?jIue*M{7 zu`v^?7#LU#JY5_^D(1Ys^KjBE1Ch4GFd@NdOppf^8CX4 znSXDl)wruDaybH>iwr*8-x$pE|4;n>dneyKvut;~zx}PAVdk&(@n2L{Pk(u>a)+hN z?8%;{dzKb+<$rtn>}!QlSjAHPo_QZ%Z{N&yUt0Os+ApnFUmNdww|8B)lFn+I>NP#< zUA1>y6mNSetbbv?_hl2)+rhd z>H3Yfx>GUbhx$TR9a?Dk_e1)%H47wq`C=a`?EH0eqrg|aNn&g-ElRIU63XOm4t*|c zz3L-}fH>RgTl4eJ6%%SsS^f{t)e31#z7C#t=s;MW-;_edqainb z7xR8x@%e)Y+sfPdI<7Lyd|I!?y1(rgQsY^6XhG@Yx(WQ&%;5_QAJ3(*W2^G_aEc`wDkRDmb&%6ho%`^?<;t8tahnf8Q(gm@2e-j+4R6R z|FvD))D~gh@We-Tp~74dn>W_(epcqaxUb?$7Ldvt&h)p z-#@|85}@UcBZFOm6G-8<9Ut=Ddu?7Z@Q_O;dJmgP*gvtlyde%QWAEpzvVuJ7CTOwU%|{66f5*qVPQ z?DG%INV)#)>x5~~f1hX9e)D?qg4A37$DSvxnLjD@aQw8tUpjhnE=y$^A6cw#bNh*E z*R|CAH6Pk%pV2&X?RV|G>8{%Q&V6is{lhafH1_a@*X5mE7U#vMoeVycP;;bnlEwR< z%eQA7{l>deLgNa5>#v0Y_9Cy^o&E-_kmvg9ys(aJ!TX=MbM01$$L^3mQDGq`zWP~N gIC3rru^aCH;ye3L|Ki(cuT?;NPgg&ebxsLQ07&_Nxc~qF diff --git a/src/images/ngletteravatar/3.png b/src/images/ngletteravatar/3.png deleted file mode 100644 index ad83cef27af6553e5ea42cf24bb06ea860d90745..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1335 zcmaJ>`#Tc~0QFI5Xi1lw+nsMdT#s0JrKithWeStWyysD#ZAI5>g_38NuuQYfwB&s^ zJ?4=i5jH7|d2Xz1AyeLu*>c@~p!0p_oFC5l;rw*6;m(&-0Ga?985tFrgZPIisvmjE&4sIdOG3x{5 zd-Vj(?p1UNyqA}VRElEb7pt$hYB`e|klu)y`V)qZxQQ&3kchr2j0<^2d|~+Kr`G}= zt9FWs$V)t6h>BYkCi$#X_0{&A*Dzk_5?`=ttOJS9UGqKvyr2D^pF1H!p-v`#D%jXY_bkG$f1!Oao3lW{xEL4 zZNo6H^+h2j;&V=R4Tz>4HxVIodc!e3fyq_%RPC($bnHdo%p@emZYe0#hhOdT76PJM zgl%pdC2mlN11(PTQS52&?h&n-#kZ^n8dj>%QC`bK8-keRCyUTOiwyMW1JO-U%cn9o zXXi#-){4CkOYQoPBp0$4!W}86nXP}lP~lRGD5uEn;4MKzR^bSv*2qW+z;RRCXxS$Y z`pPt9da0qa+x?Ys;=?-Y9Zqv^P~e)1Vsfz>z^-OWu|Zq?>VQ?Rk(v+W7z`hzehJk8 zDJH^!w05j-#nK2@BwOvk!eo?auzMSWEAwe%V_iqVJZYr|pJ)sW^gG3U`Hxxf4B7hF zxWO=DRHe+S!#^!19=2NK%@gb=V0k*_VZTXUfq9hA8sTZT>>_$xz}3my)Np3N=C@vx zLhP3lvkk1!p`aPVyXy&LVD<44iL8nT6Rq&TWMXS^P_fu1NFvjvVGpbLhkc$6VVi+FaJx+JCz#GTX= zne<~G;g=XW;A#JK$)iK_=aVwd9_==M`a2oRzWeS+Lu!0MNvW!Cc12V&thCLSaZAY( zV;AfcBlHX!D8v_va^_m|sZCU#B#g@i)y=13g0UwbJ4}D%>X;I%);{N47w8tWk={gy z4w^10?Y_ZaN%Eeb^ee^5$2by;nuD!L+B`&yTimMVyg_k8h+309$624Q*|RqLQ*WZi zOiIb&yW*c;A4V`E($;*Gv+q0s8%%tcB;w8_q}#F)3rGPYY3N~{rK?uSv*FNi(@JW@ z79f4wGdSkvdaTtW@1oqD4|if$7)j8laOPLw#2}OcXR@+-VU%1Du(37Ao)X*LXJn2y zpqs7Rm|ZM`hYxL)%NwOPnsi9zG+$Rms^Tm;+9`eU-ZwUX7Dn_8^{qA0nk{Joqw#ce z6hew_rcH0e2H8SN+MtuV;e*H;SV$3L^`(LKkJ{b)#$dL4eQe6kiB#PMap;)7<@CiZ qEvmIOI(nyNKjVk*FOFj`O8bnn?|3w(o*`8(_nED^|wx1#Z diff --git a/src/images/ngletteravatar/5.png b/src/images/ngletteravatar/5.png deleted file mode 100644 index 3875aace1b1dca153bcb12b7852b380398969189..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1379 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 diff --git a/src/images/ngletteravatar/65.png b/src/images/ngletteravatar/65.png deleted file mode 100644 index fb608098f790c106b83af40c16796a098ee02f75..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 822 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58CaNs)Vi3hp+L$Yz$e6YR`kCA|Noy^d+)`a z_gg#9{rUBGMd|Tt`=0EbcIDg0pARp*o|k-JeZ%P^3va%A`ekwUVYyiqObiT62R&UJ zLn>~)IcLbrpuoehp>=k;*fIw02uIFK9~t=FS1$lr$M9iysOa~qt9|Ci#_zVCQB)ur z6x>={eqq@ni@wM8Ik~G}HtRo}#U6F*=qim#t`{7g90lYb=zirDn*NL7AQzufhLwh$ zK+o(hS8b&o5-hHcijpOP2C_A4Uas(#UG+dE;zWSd>^c8EmwfpD=KHhh|9{>s?ql7+ zAa;N$f`L1MQKtdUS-?Nx|9ZwQ1Ko4C9{x+>FAy>t0Z| zI=w*U+%9eZ?6`}|XT^t!h^$_bG2#5$B^;F(UpCl2%9+%nQF@50(9A1jijrbs;WQ<+ zsMNF8AuqLq-&|cg|Jsa4Ca;=iFEVA0ysEb7>LQE4`OaZmc0JheyHx8$cbN2%yWDd` z_KF0am0M)aCVBML%e_VGiprmQzOCGGert1z%X)Q{XSe1p`C;&?Vs%FA)JKPV9?xB} zdsFL6?+vHknmIY{(Ga$nvU2Su*{zE%OKmw2`*E@C^#7OmBR?t!wM{x~E+8E5XCdQ} z|8Vz@OHx8hUav{$b8*`b&kx!yiXYeOvfi0BtyP9w zan?k(7bh5hi$~p-by%dr`%#De%esHhXIx`CSh><~e#6QwA;uAGP2HCa@=q}wJmNRg zJ>`R-L(*cCfY*`#erzygyEW6(D*vV$7t74tq2v2C>HC4nYqx)QDk!KqJ#)SMQHkUp e!((uw?;nH6fy>9lQwpRZ%Z$0oSbue#Zee-pOWTbJTk<|Ij zzqLA$yS}7g`?M^|m*tYV>Av|s_9!>)&C2}R$Bs{u@}Z5@&DyB8mRlm>eW{E)^?BrL zeS8NW6u-RE*^%4ZUo8_d`v1l^dkLAw0I>ZE&iNFP%$Q?DU!f!Sy6oVa9w+0{X=L2& zxKi2Pw{zZMU+6u~fl-k|iMJk#p*Dl_(lWWT`@b#E*zvLKj}j9$!+2|vwrahx zcqy(wWdi#q5$;D$v3b2}UWGhxGhI5ZJt-Ki>oy{wvvSl+ddR`?m}D}~g1YVlWl6~H`~&rYFC>dVDi}G*?n4I&lb3#El4!iOPY0H zBp(|N0q6R3NI$22u$L$bv)t;l5;W zET7dR9=_?a0O9G^93+tTn3jejPyBi6dc?9;lw3WQQg5MX?$1?&KA@cqL!5ZGJtj86 z@?qHnM3JMp1eK*6L~{*@*jmX*%=7wkAp_ngF62{a2@zd4 zu+P9rZo9Q-sU3`OWz!nHV0pV{wwr&<261}Q)1~XzI@Q)X71Ve8LwJ+)fYU0?LaSv8cc_EPt4sr^Gp@qKP=a{#6qB;bCoGk*q{qK@3De@QY*9~ zW;OBE`wzPc@?|E?CrvlXjSXzO^3I2^x~>{v?3q&3|G9*Xsf}$#Z_%$M9qVG%?EyF! KFK4z>NculNOoo;K diff --git a/src/images/ngletteravatar/90.png b/src/images/ngletteravatar/90.png deleted file mode 100644 index 7d1770ab170a2a59f28856a1be498c9ac14f5b68..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9685 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