From 63c9f751a914d92ac4872354be7f52cef71163f0 Mon Sep 17 00:00:00 2001 From: Luke Montalvo Date: Fri, 18 Apr 2014 00:48:42 -0500 Subject: [PATCH 1/8] + Add beginning code for an alternative curses interface --- src/bitmessagecurses/__init__.py | 144 +++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 src/bitmessagecurses/__init__.py diff --git a/src/bitmessagecurses/__init__.py b/src/bitmessagecurses/__init__.py new file mode 100644 index 00000000..f4d292d7 --- /dev/null +++ b/src/bitmessagecurses/__init__.py @@ -0,0 +1,144 @@ +# Copyright (c) 2014 Luke Montalvo +# This file adds a alternative commandline interface + +import curses +import shared +import os +import sys +import StringIO + +from addresses import * + +quit = False +menutab = 1 +menu = ["Inbox", "Send", "Sent", "Your Identities", "Subscriptions", "Address Book", "Blacklist", "Network Status"] +log = "" + +addresses = [] +addrcur = 0 + +class printLog: + def write(self, output): + global log + log += output +printlog = printLog() + +def cpair(a): + r = curses.color_pair(a) + if r not in range(1, curses.COLOR_PAIRS-1): + r = curses.color_pair(0) + return r + +def drawmenu(stdscr): + menustr = " " + for i in range(0, len(menu)): + if menutab == i+1: + menustr = menustr[:-1] + menustr += "[" + menustr += str(i+1)+menu[i] + if menutab == i+1: + menustr += "] " + elif i != len(menu)-1: + menustr += " " + stdscr.addstr(2, 5, menustr, curses.A_UNDERLINE) + +def drawtab(stdscr): + if menutab in range(0, len(menu)): + if menutab == 1: # Inbox + stdscr.addstr(3, 5, "new messages") + elif menutab == 2: # Send + stdscr.addstr(3, 5, "to: from:") + elif menutab == 4: # Identities + for i, item in enumerate(addresses): + a = 0 + if i == addrcur: + a = curses.A_REVERSE + stdscr.addstr(3+i, 5, item[0], cpair(item[3]) | a) + stdscr.refresh() + +def redraw(stdscr): + stdscr.erase() + stdscr.border() + drawmenu(stdscr) + stdscr.refresh() +def handlech(c, stdscr): + if c != curses.ERR: + if c in range(256): + if chr(c) in '12345678': + global menutab + menutab = int(chr(c)) + elif chr(c) == 'q': + global quit + quit = True + else: + global addrcur + if c == curses.KEY_UP: + if (addrcur > 0): + addrcur -= 1 + elif c == curses.KEY_DOWN: + if (addrcur < len(addresses)-1): + addrcur += 1 + redraw(stdscr) + +def runwrapper(): + sys.stdout = printlog + stdscr = curses.initscr() + + stdscr.nodelay(1) + curses.curs_set(0) + curses.start_color() + + curses.wrapper(run) + shutdown() + +def run(stdscr): + # Init list of address in 'Your Identities' tab + configSections = shared.config.sections() + if curses.has_colors(): + curses.init_pair(1, curses.COLOR_RED, curses.COLOR_BLACK) # red + if curses.can_change_color(): + curses.init_color(8, 500, 500, 500) # gray + curses.init_pair(8, 8, 0) + curses.init_color(9, 844, 465, 0) # orange + curses.init_pair(9, 9, 0) + else: + global menutab + menutab = 4 + curses.beep() + for addressInKeysFile in configSections: + if addressInKeysFile != "bitmessagesettings": + isEnabled = shared.config.getboolean(addressInKeysFile, "enabled") + addresses.append([shared.config.get(addressInKeysFile, "label"), isEnabled, str(decodeAddress(addressInKeysFile)[2])]) + if not isEnabled: + addresses[len(addresses)-1].append(8) # gray + elif shared.safeConfigGetBoolean(addressInKeysFile, 'chan'): + addresses[len(addresses)-1].append(9) # orange + elif shared.safeConfigGetBoolean(addressInKeysFile, 'mailinglist'): + addresses[len(addresses)-1].append(5) # magenta + + # Load messages from database + """ + loadInbox() + loadSend() + """ + + # Initialize address display and send form + """ + rerenderAddressBook() + rerenderSubscriptions() + rerenderComboBoxSendForm() + """ + + redraw(stdscr) + while quit == False: + drawtab(stdscr) + handlech(stdscr.getch(), stdscr) + +def shutdown(): + sys.stdout = sys.__stdout__ + print("Shutting down...") + sys.stdout = printlog + shared.doCleanShutdown() + sys.stdout = sys.__stdout__ + + os._exit(0) From 813f4c7ed94c1a4678e31361aaf6d951df38f9b9 Mon Sep 17 00:00:00 2001 From: Luke Montalvo Date: Sat, 19 Apr 2014 13:45:37 -0500 Subject: [PATCH 2/8] + Add dependency list + Add stderr capturing + Add identities and network status tabs + Add dialogs to configure identities + Add color pair definitions + Add the '-c' flag to use the curses interface * Reorganize imports * Switch logger to file_only mode when running with curses --- src/bitmessagecurses/__init__.py | 299 +++++++++++++++++++++++++++++-- src/bitmessagemain.py | 17 +- src/debug.py | 12 +- 3 files changed, 306 insertions(+), 22 deletions(-) diff --git a/src/bitmessagecurses/__init__.py b/src/bitmessagecurses/__init__.py index f4d292d7..b030f24c 100644 --- a/src/bitmessagecurses/__init__.py +++ b/src/bitmessagecurses/__init__.py @@ -1,27 +1,54 @@ # Copyright (c) 2014 Luke Montalvo # This file adds a alternative commandline interface +# +# Dependencies: +# * from python2-pip +# * python2-pythondialog +# * dialog -import curses -import shared import os import sys import StringIO +import time +from time import strftime, localtime +from threading import Timer + +import curses +import dialog +from dialog import Dialog + +import shared +import ConfigParser from addresses import * quit = False menutab = 1 menu = ["Inbox", "Send", "Sent", "Your Identities", "Subscriptions", "Address Book", "Blacklist", "Network Status"] log = "" +logpad = None +inventorydata = 0 +startuptime = time.time() addresses = [] addrcur = 0 +addrcopy = 0 class printLog: def write(self, output): global log log += output + def flush(self): + pass +class errLog: + def write(self, output): + global log + log += "!"+output + def flush(self): + pass printlog = printLog() +errlog = errLog() + def cpair(a): r = curses.color_pair(a) @@ -42,18 +69,81 @@ def drawmenu(stdscr): menustr += " " stdscr.addstr(2, 5, menustr, curses.A_UNDERLINE) +def resetlookups(): + inventorydata = shared.numberOfInventoryLookupsPerformed + shared.numberOfInventoryLookupsPerformed = 0 + Timer(2, resetlookups, ()).start() def drawtab(stdscr): - if menutab in range(0, len(menu)): + if menutab in range(1, len(menu)+1): if menutab == 1: # Inbox - stdscr.addstr(3, 5, "new messages") + pass elif menutab == 2: # Send - stdscr.addstr(3, 5, "to: from:") + pass + elif menutab == 3: # Sent + pass elif menutab == 4: # Identities + stdscr.addstr(3, 5, "Label", curses.A_BOLD) + stdscr.addstr(3, 50, "Address", curses.A_BOLD) + stdscr.addstr(3, 100, "Stream", curses.A_BOLD) for i, item in enumerate(addresses): a = 0 - if i == addrcur: - a = curses.A_REVERSE - stdscr.addstr(3+i, 5, item[0], cpair(item[3]) | a) + if i == addrcur: # Highlight current address + a = a | curses.A_REVERSE + if item[1] == True and item[3] not in [8,9]: # Embolden enabled, non-special addresses + a = a | curses.A_BOLD + stdscr.addstr(4+i, 5, item[0], a) + stdscr.addstr(4+i, 50, item[2], cpair(item[3]) | a) + stdscr.addstr(4+i, 100, str(1), a) + elif menutab == 5: # Subscriptions + pass + elif menutab == 6: # Address book + pass + elif menutab == 7: # Blacklist + pass + elif menutab == 8: # Network status + # Connection data + stdscr.addstr(4, 5, "Total Connections: "+str(len(shared.connectedHostsList)).ljust(2)) + stdscr.addstr(6, 6, "Stream #", curses.A_BOLD) + stdscr.addstr(6, 17, "Connections", curses.A_BOLD) + streamcount = [] + for host, stream in shared.connectedHostsList.items(): + if stream >= len(streamcount): + streamcount.append(1) + else: + streamcount[stream] += 1 + for i, item in enumerate(streamcount): + if i < 5: + if i == 0: + stdscr.addstr(7+i, 6, "?") + else: + stdscr.addstr(7+i, 6, str(i)) + stdscr.addstr(7+i, 17, str(item)) + + # Uptime and processing data + stdscr.addstr(6, 35, "Since startup on "+unicode(strftime(shared.config.get('bitmessagesettings', 'timeformat'), localtime(int(startuptime))))) + stdscr.addstr(7, 40, "Processed "+str(shared.numberOfMessagesProcessed).ljust(4)+" person-to-person messages.") + stdscr.addstr(8, 40, "Processed "+str(shared.numberOfBroadcastsProcessed).ljust(4)+" broadcast messages.") + stdscr.addstr(9, 40, "Processed "+str(shared.numberOfPubkeysProcessed).ljust(4)+" public keys.") + + # Inventory data + stdscr.addstr(11, 35, "Inventory lookups per second: "+str(int(inventorydata/2)).ljust(3)) + + # Log + stdscr.addstr(13, 6, "Log", curses.A_BOLD) + n = log.count('\n') + if n > 0: + l = log.split('\n') + if n > 512: + del l[:(n-256)] + logpad.erase() + n = len(l) + for i, item in enumerate(l): + a = 0 + if len(item) > 0 and item[0] == '!': + a = curses.color_pair(1) + item = item[1:] + logpad.addstr(i, 0, item, a) + logpad.refresh(n-curses.LINES+2, 0, 14, 6, curses.LINES-2, curses.COLS-7) stdscr.refresh() def redraw(stdscr): @@ -61,6 +151,10 @@ def redraw(stdscr): stdscr.border() drawmenu(stdscr) stdscr.refresh() +def dialogreset(stdscr): + stdscr.clear() + stdscr.keypad(1) + curses.curs_set(0) def handlech(c, stdscr): if c != curses.ERR: if c in range(256): @@ -70,20 +164,176 @@ def handlech(c, stdscr): elif chr(c) == 'q': global quit quit = True + elif chr(c) == '\n': + if menutab == 4: + curses.curs_set(1) + d = Dialog(dialog="dialog") + d.set_background_title("Your Identities Dialog Box") + r, t = d.menu("Do what with \""+addresses[addrcur][0]+"\" : \""+addresses[addrcur][2]+"\"?", + choices=[("1", "Create new address"), + ("2", "Copy address to internal buffer"), + ("3", "Rename"), + ("4", "Enable"), + ("5", "Disable"), + ("6", "Delete"), + ("7", "Special address behavior")]) + if r == d.DIALOG_OK: + if t == "1": # Create new address + d.set_background_title("Create new address") + d.scrollbox(unicode("Here you may generate as many addresses as you like.\n" + "Indeed, creating and abandoning addresses is encouraged.\n" + "Deterministic addresses have several pros and cons:\n" + "\nPros:\n" + " * You can recreate your addresses on any computer from memory\n" + " * You need not worry about backing up your keys.dat file as long as you \n can remember your passphrase\n" + "Cons:\n" + " * You must remember (or write down) your passphrase in order to recreate \n your keys if they are lost\n" + " * You must also remember the address version and stream numbers\n" + " * If you choose a weak passphrase someone may be able to brute-force it \n and then send and receive messages as you"), + exit_label="Continue") + r, t = d.menu("Choose an address generation technique", + choices=[("1", "Use a random number generator"), + ("2", "Use a passphrase")]) + if r == d.DIALOG_OK: + if t == "1": + d.set_background_title("Randomly generate address") + r, t = d.inputbox("Label (not shown to anyone except you)") + label = "" + if r == d.DIALOG_OK and len(t) > 0: + label = t + r, t = d.menu("Choose a stream", + choices=[("1", "Use the most available stream"),("", "(Best if this is the first of many addresses you will create)"), + ("2", "Use the same stream as an existing address"),("", "(Saves you some bandwidth and processing power)")]) + if r == d.DIALOG_OK: + if t == "1": + stream = 1 + elif t == "2": + addrs = [] + for i, item in enumerate(addresses): + addrs.append([str(i), item[2]]) + r, t = d.menu("Choose an existing address's stream", choices=addrs) + if r == d.DIALOG_OK: + stream = decodeAddress(addrs[int(t)][1])[2] + shorten = False + r, t = d.checklist("Miscellaneous options", + choices=[("1", "Spend time shortening the address", shorten)]) + if r == d.DIALOG_OK and "1" in t: + shorten = True + shared.addressGeneratorQueue.put(("createRandomAddress", 4, stream, label, 1, "", shorten)) + elif t == "2": + d.set_background_title("Make deterministic addresses") + r, t = d.passwordform("Enter passphrase", + [("Passphrase", 1, 1, "", 2, 1, 64, 128), + ("Confirm passphrase", 3, 1, "", 4, 1, 64, 128)], + form_height=4, insecure=True) + if r == d.DIALOG_OK: + if t[0] == t[1]: + passphrase = t[0] + r, t = d.rangebox("Number of addresses to generate", + width=48, min=1, max=99, init=8) + if r == d.DIALOG_OK: + number = t + stream = 1 + shorten = False + r, t = d.checklist("Miscellaneous options", + choices=[("1", "Spend time shortening the address", shorten)]) + if r == d.DIALOG_OK and "1" in t: + shorten = True + d.scrollbox(unicode("In addition to your passphrase, be sure to remember the following numbers:\n" + "\n * Address version number: "+str(4)+"\n" + " * Stream number: "+str(stream)), + exit_label="Continue") + shared.addressGeneratorQueue.put(('createDeterministicAddresses', 4, stream, "unused deterministic address", number, str(passphrase), shorten)) + else: + d.scrollbox(unicode("Passphrases do not match"), exit_label="Continue") + elif t == "2": # Copy address to internal buffer + addrcopy = addrcur + elif t == "3": # Rename address label + a = addresses[addrcur][2] + label = addresses[addrcur][0] + r, t = d.inputbox("New address label", init=label) + if r == d.DIALOG_OK: + label = t + shared.config.set(a, "label", label) + # Write config + with open(shared.appdata + 'keys.dat', 'wb') as configfile: + shared.config.write(configfile) + addresses[addrcur][0] = label + elif t == "4": # Enable address + a = addresses[addrcur][2] + shared.config.set(a, "enabled", "true") # Set config + # Write config + with open(shared.appdata + 'keys.dat', 'wb') as configfile: + shared.config.write(configfile) + # Change color + if shared.safeConfigGetBoolean(a, 'chan'): + addresses[addrcur][3] = 9 # orange + elif shared.safeConfigGetBoolean(a, 'mailinglist'): + addresses[addrcur][3] = 5 # magenta + else: + addresses[addrcur][3] = 0 # black + addresses[addrcur][1] = True + shared.reloadMyAddressHashes() # Reload address hashes + elif t == "5": # Disable address + a = addresses[addrcur][2] + shared.config.set(a, "enabled", "false") # Set config + addresses[addrcur][3] = 8 # Set color to gray + # Write config + with open(shared.appdata + 'keys.dat', 'wb') as configfile: + shared.config.write(configfile) + addresses[addrcur][1] = False + shared.reloadMyAddressHashes() # Reload address hashes + elif t == "6": # Delete address + pass + elif t == "7": # Special address behavior + a = addresses[addrcur][2] + d.set_background_title("Special address behavior") + if shared.safeConfigGetBoolean(a, "chan"): + d.scrollbox(unicode("This is a chan address. You cannot use it as a pseudo-mailing list."), exit_label="Continue") + else: + m = shared.safeConfigGetBoolean(a, "mailinglist") + r, t = d.radiolist("Select address behavior", + choices=[("1", "Behave as a normal address", not m), ("2", "Behave as a pseudo-mailing-list address", m)]) + if r == d.DIALOG_OK: + if t == "1" and m == True: + shared.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 == False: + try: + mn = shared.config.get(a, "mailinglistname") + except ConfigParser.NoOptionError: + mn = "" + r, t = d.inputbox("Mailing list name", init=mn) + if r == d.DIALOG_OK: + mn = t + shared.config.set(a, "mailinglist", "true") + shared.config.set(a, "mailinglistname", mn) + addresses[addrcur][3] = 6 # Set color to magenta + # Write config + with open(shared.appdata + 'keys.dat', 'wb') as configfile: + shared.config.write(configfile) + dialogreset(stdscr) else: global addrcur if c == curses.KEY_UP: - if (addrcur > 0): + if menutab == 4 and addrcur > 0: addrcur -= 1 elif c == curses.KEY_DOWN: - if (addrcur < len(addresses)-1): + if menutab == 4 and addrcur < len(addresses)-1: addrcur += 1 redraw(stdscr) def runwrapper(): sys.stdout = printlog + sys.stderr = errlog stdscr = curses.initscr() + global logpad + logpad = curses.newpad(1024, curses.COLS) + stdscr.nodelay(1) curses.curs_set(0) curses.start_color() @@ -92,29 +342,43 @@ def runwrapper(): shutdown() def run(stdscr): - # Init list of address in 'Your Identities' tab - configSections = shared.config.sections() + # Schedule inventory lookup data + resetlookups() + + # Init color pairs if curses.has_colors(): curses.init_pair(1, curses.COLOR_RED, curses.COLOR_BLACK) # red + curses.init_pair(2, curses.COLOR_GREEN, curses.COLOR_BLACK) # green + curses.init_pair(3, curses.COLOR_YELLOW, curses.COLOR_BLACK) # yellow + curses.init_pair(4, curses.COLOR_BLUE, curses.COLOR_BLACK) # blue + curses.init_pair(5, curses.COLOR_MAGENTA, curses.COLOR_BLACK) # magenta + curses.init_pair(6, curses.COLOR_CYAN, curses.COLOR_BLACK) # cyan + curses.init_pair(7, curses.COLOR_WHITE, curses.COLOR_BLACK) # white if curses.can_change_color(): curses.init_color(8, 500, 500, 500) # gray curses.init_pair(8, 8, 0) curses.init_color(9, 844, 465, 0) # orange curses.init_pair(9, 9, 0) - else: - global menutab - menutab = 4 - curses.beep() + else: + curses.init_pair(8, curses.COLOR_WHITE, curses.COLOR_BLACK) # grayish + curses.init_pair(9, curses.COLOR_YELLOW, curses.COLOR_BLACK) # orangish + + # Init list of address in 'Your Identities' tab + configSections = shared.config.sections() for addressInKeysFile in configSections: if addressInKeysFile != "bitmessagesettings": isEnabled = shared.config.getboolean(addressInKeysFile, "enabled") - addresses.append([shared.config.get(addressInKeysFile, "label"), isEnabled, str(decodeAddress(addressInKeysFile)[2])]) + addresses.append([shared.config.get(addressInKeysFile, "label"), isEnabled, addressInKeysFile]) + # Set address color if not isEnabled: addresses[len(addresses)-1].append(8) # gray elif shared.safeConfigGetBoolean(addressInKeysFile, 'chan'): addresses[len(addresses)-1].append(9) # orange elif shared.safeConfigGetBoolean(addressInKeysFile, 'mailinglist'): addresses[len(addresses)-1].append(5) # magenta + else: + addresses[len(addresses)-1].append(0) # black + addresses.reverse() # Load messages from database """ @@ -140,5 +404,6 @@ def shutdown(): sys.stdout = printlog shared.doCleanShutdown() sys.stdout = sys.__stdout__ + sys.stderr = sys.__stderr__ os._exit(0) diff --git a/src/bitmessagemain.py b/src/bitmessagemain.py index e4a073d9..b92c2795 100755 --- a/src/bitmessagemain.py +++ b/src/bitmessagemain.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python2.7 # Copyright (c) 2012 Jonathan Warren # Copyright (c) 2012 The Bitmessage developers # Distributed under the MIT/X11 software license. See the accompanying @@ -99,6 +99,11 @@ class Main: # is the application already running? If yes then exit. thisapp = singleton.singleinstance() + # get curses flag + curses = False + if '-c' in sys.argv: + curses = True + signal.signal(signal.SIGINT, helper_generic.signal_handler) # signal.signal(signal.SIGINT, signal.SIG_DFL) @@ -159,10 +164,16 @@ class Main: except Exception as err: print 'PyBitmessage requires PyQt unless you want to run it as a daemon and interact with it using the API. You can download PyQt from http://www.riverbankcomputing.com/software/pyqt/download or by searching Google for \'PyQt Download\'. If you want to run in daemon mode, see https://bitmessage.org/wiki/Daemon' print 'Error message:', err + print 'You can also run PyBitmessage with the new curses interface by providing \'-c\' as a commandline argument.' os._exit(0) - import bitmessageqt - bitmessageqt.run() + if curses == False: + import bitmessageqt + bitmessageqt.run() + else: + print 'Running with curses' + import bitmessagecurses + bitmessagecurses.runwrapper() else: shared.config.remove_option('bitmessagesettings', 'dontconnect') diff --git a/src/debug.py b/src/debug.py index fe7815e7..02ab94f8 100644 --- a/src/debug.py +++ b/src/debug.py @@ -19,6 +19,7 @@ Use: `from debug import logger` to import this facility into whatever module you import logging import logging.config import shared +import sys # TODO(xj9): Get from a config file. log_level = 'DEBUG' @@ -69,7 +70,10 @@ def configureLogging(): # TODO (xj9): Get from a config file. #logger = logging.getLogger('console_only') configureLogging() -logger = logging.getLogger('both') +if '-c' in sys.argv: + logger = logging.getLogger('file_only') +else: + logger = logging.getLogger('both') def restartLoggingInUpdatedAppdataLocation(): global logger @@ -78,4 +82,8 @@ def restartLoggingInUpdatedAppdataLocation(): i.flush() i.close() configureLogging() - logger = logging.getLogger('both') \ No newline at end of file + if '-c' in sys.argv: + logger = logging.getLogger('file_only') + else: + logger = logging.getLogger('both') + From 7d10d2aa2af73d2a3d34ed355f8b92c3e9e821bf Mon Sep 17 00:00:00 2001 From: Luke Montalvo Date: Sat, 19 Apr 2014 13:57:08 -0500 Subject: [PATCH 3/8] + Fix CPU hogging by implementing tab-based refresh improvements * Make tables more distinct with horizontal lines - Remove start_color() because wrapper() does it anyway --- src/bitmessagecurses/__init__.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/bitmessagecurses/__init__.py b/src/bitmessagecurses/__init__.py index b030f24c..a1c3ab4e 100644 --- a/src/bitmessagecurses/__init__.py +++ b/src/bitmessagecurses/__init__.py @@ -25,6 +25,7 @@ from addresses import * quit = False menutab = 1 menu = ["Inbox", "Send", "Sent", "Your Identities", "Subscriptions", "Address Book", "Blacklist", "Network Status"] +naptime = 100 log = "" logpad = None inventorydata = 0 @@ -85,15 +86,16 @@ def drawtab(stdscr): stdscr.addstr(3, 5, "Label", curses.A_BOLD) stdscr.addstr(3, 50, "Address", curses.A_BOLD) stdscr.addstr(3, 100, "Stream", curses.A_BOLD) + stdscr.hline(4, 5, '-', 101) for i, item in enumerate(addresses): a = 0 if i == addrcur: # Highlight current address a = a | curses.A_REVERSE if item[1] == True and item[3] not in [8,9]: # Embolden enabled, non-special addresses a = a | curses.A_BOLD - stdscr.addstr(4+i, 5, item[0], a) - stdscr.addstr(4+i, 50, item[2], cpair(item[3]) | a) - stdscr.addstr(4+i, 100, str(1), a) + stdscr.addstr(5+i, 5, item[0], a) + stdscr.addstr(5+i, 50, item[2], cpair(item[3]) | a) + stdscr.addstr(5+i, 100, str(1), a) elif menutab == 5: # Subscriptions pass elif menutab == 6: # Address book @@ -104,7 +106,8 @@ def drawtab(stdscr): # Connection data stdscr.addstr(4, 5, "Total Connections: "+str(len(shared.connectedHostsList)).ljust(2)) stdscr.addstr(6, 6, "Stream #", curses.A_BOLD) - stdscr.addstr(6, 17, "Connections", curses.A_BOLD) + stdscr.addstr(6, 18, "Connections", curses.A_BOLD) + stdscr.hline(7, 6, '-', 23) streamcount = [] for host, stream in shared.connectedHostsList.items(): if stream >= len(streamcount): @@ -112,12 +115,12 @@ def drawtab(stdscr): else: streamcount[stream] += 1 for i, item in enumerate(streamcount): - if i < 5: + if i < 4: if i == 0: - stdscr.addstr(7+i, 6, "?") + stdscr.addstr(8+i, 6, "?") else: - stdscr.addstr(7+i, 6, str(i)) - stdscr.addstr(7+i, 17, str(item)) + stdscr.addstr(8+i, 6, str(i)) + stdscr.addstr(8+i, 18, str(item).ljust(2)) # Uptime and processing data stdscr.addstr(6, 35, "Since startup on "+unicode(strftime(shared.config.get('bitmessagesettings', 'timeformat'), localtime(int(startuptime))))) @@ -159,8 +162,14 @@ def handlech(c, stdscr): if c != curses.ERR: if c in range(256): if chr(c) in '12345678': - global menutab + global menutab, naptime menutab = int(chr(c)) + if menutab in [1,8]: # Tabs which require live updating + stdscr.nodelay(1) + naptime = 100 + else: + stdscr.nodelay(0) + naptime = 0 elif chr(c) == 'q': global quit quit = True @@ -336,7 +345,6 @@ def runwrapper(): stdscr.nodelay(1) curses.curs_set(0) - curses.start_color() curses.wrapper(run) shutdown() @@ -397,6 +405,8 @@ def run(stdscr): while quit == False: drawtab(stdscr) handlech(stdscr.getch(), stdscr) + if naptime > 0: + curses.napms(naptime) def shutdown(): sys.stdout = sys.__stdout__ From c3feb54b7bcc786511a8704b1857b91f9360c93d Mon Sep 17 00:00:00 2001 From: Luke Montalvo Date: Tue, 29 Apr 2014 00:10:33 -0500 Subject: [PATCH 4/8] + Add lists for each tab requiring a large table + Add function ascii() to strip non-ASCII characters + Add Inbox, Send, and Address Book tabs support + Add Home and End key handling to allow skipping to the top or bottom of tables + Add sendMessage() function so replies are easier and code is not duplicated + Add screen clear before initial draw in order to get rid of the default background color * Fix resetlookups() by allowing access to global inventorydata * Shorten resetlookups() Timer to 1 second * Fix table display when output requires scrolling * Change table column width from 50 to 30 - Remove conditional naptime by using blocking input with 1 second timeout - Remove stderr capturing for development purposes --- src/bitmessagecurses/__init__.py | 416 +++++++++++++++++++++++++++---- 1 file changed, 370 insertions(+), 46 deletions(-) diff --git a/src/bitmessagecurses/__init__.py b/src/bitmessagecurses/__init__.py index a1c3ab4e..24482657 100644 --- a/src/bitmessagecurses/__init__.py +++ b/src/bitmessagecurses/__init__.py @@ -1,6 +1,7 @@ # Copyright (c) 2014 Luke Montalvo -# This file adds a alternative commandline interface +# This file adds a alternative commandline interface, feel free to critique and fork # +# This has only been tested on Arch Linux # Dependencies: # * from python2-pip # * python2-pythondialog @@ -17,10 +18,12 @@ from threading import Timer import curses import dialog from dialog import Dialog +from helper_sql import * import shared import ConfigParser from addresses import * +from pyelliptic.openssl import OpenSSL quit = False menutab = 1 @@ -29,11 +32,23 @@ naptime = 100 log = "" logpad = None inventorydata = 0 - startuptime = time.time() + +inbox = [] +inboxcur = 0 +sent = [] +sentcur = 0 addresses = [] addrcur = 0 addrcopy = 0 +subscriptions = [] +subcur = 0 +addrbook = [] +abookcur = 0 +blacklist = [] +blackcur = 0 + +BROADCAST_STR = "[Broadcast subscribers]" class printLog: def write(self, output): @@ -56,6 +71,12 @@ def cpair(a): if r not in range(1, curses.COLOR_PAIRS-1): r = curses.color_pair(0) return r +def ascii(s): + r = "" + for c in s: + if ord(c) in range(128): + r += c + return r def drawmenu(stdscr): menustr = " " @@ -71,35 +92,57 @@ def drawmenu(stdscr): stdscr.addstr(2, 5, menustr, curses.A_UNDERLINE) def resetlookups(): + global inventorydata inventorydata = shared.numberOfInventoryLookupsPerformed shared.numberOfInventoryLookupsPerformed = 0 - Timer(2, resetlookups, ()).start() + Timer(1, resetlookups, ()).start() def drawtab(stdscr): if menutab in range(1, len(menu)+1): if menutab == 1: # Inbox - pass - elif menutab == 2: # Send - pass + stdscr.addstr(3, 5, "To", curses.A_BOLD) + stdscr.addstr(3, 30, "From", curses.A_BOLD) + stdscr.addstr(3, 60, "Subject", curses.A_BOLD) + stdscr.addstr(3, 90, "Time Received", curses.A_BOLD) + stdscr.hline(4, 5, '-', 91) + for i, item in enumerate(inbox[max(min(len(inbox)-curses.LINES+6, inboxcur-5), 0):]): + if 6+i < curses.LINES: + a = 0 + if i == inboxcur - max(min(len(inbox)-curses.LINES+6, inboxcur-5), 0): # Highlight current address + a = a | curses.A_REVERSE + if item[7] == False: # If not read, highlight + a = a | curses.A_BOLD + stdscr.addstr(5+i, 5, item[1][:29], a) + stdscr.addstr(5+i, 30, item[3][:29], a) + stdscr.addstr(5+i, 60, item[5][:29], a) + stdscr.addstr(5+i, 90, item[6][:29], a) elif menutab == 3: # Sent pass - elif menutab == 4: # Identities + elif menutab == 2 or menutab == 4: # Send or Identities stdscr.addstr(3, 5, "Label", curses.A_BOLD) - stdscr.addstr(3, 50, "Address", curses.A_BOLD) - stdscr.addstr(3, 100, "Stream", curses.A_BOLD) - stdscr.hline(4, 5, '-', 101) - for i, item in enumerate(addresses): + stdscr.addstr(3, 30, "Address", curses.A_BOLD) + stdscr.addstr(3, 60, "Stream", curses.A_BOLD) + stdscr.hline(4, 5, '-', 61) + for i, item in enumerate(addresses[max(min(len(addresses)-curses.LINES+6, addrcur-5), 0):]): a = 0 - if i == addrcur: # Highlight current address + if i == addrcur - max(min(len(addresses)-curses.LINES+6, addrcur-5), 0): # Highlight current address a = a | curses.A_REVERSE if item[1] == True and item[3] not in [8,9]: # Embolden enabled, non-special addresses a = a | curses.A_BOLD - stdscr.addstr(5+i, 5, item[0], a) - stdscr.addstr(5+i, 50, item[2], cpair(item[3]) | a) - stdscr.addstr(5+i, 100, str(1), a) + stdscr.addstr(5+i, 5, item[0][:30], a) + stdscr.addstr(5+i, 30, item[2][:30], cpair(item[3]) | a) + stdscr.addstr(5+i, 60, str(1)[:30], a) elif menutab == 5: # Subscriptions pass elif menutab == 6: # Address book - pass + stdscr.addstr(3, 5, "Label", curses.A_BOLD) + stdscr.addstr(3, 30, "Address", curses.A_BOLD) + stdscr.hline(4, 5, '-', 31) + for i, item in enumerate(addrbook[max(min(len(addrbook)-curses.LINES+6, abookcur-5), 0):]): + a = 0 + if i == abookcur - max(min(len(addrbook)-curses.LINES+6, abookcur-5), 0): # Highlight current address + a = a | curses.A_REVERSE + stdscr.addstr(5+i, 5, item[0][:30], a) + stdscr.addstr(5+i, 30, item[1][:30], a) elif menutab == 7: # Blacklist pass elif menutab == 8: # Network status @@ -129,7 +172,7 @@ def drawtab(stdscr): stdscr.addstr(9, 40, "Processed "+str(shared.numberOfPubkeysProcessed).ljust(4)+" public keys.") # Inventory data - stdscr.addstr(11, 35, "Inventory lookups per second: "+str(int(inventorydata/2)).ljust(3)) + stdscr.addstr(11, 35, "Inventory lookups per second: "+str(inventorydata).ljust(3)) # Log stdscr.addstr(13, 6, "Log", curses.A_BOLD) @@ -162,19 +205,102 @@ def handlech(c, stdscr): if c != curses.ERR: if c in range(256): if chr(c) in '12345678': - global menutab, naptime + global menutab menutab = int(chr(c)) - if menutab in [1,8]: # Tabs which require live updating - stdscr.nodelay(1) - naptime = 100 - else: - stdscr.nodelay(0) - naptime = 0 elif chr(c) == 'q': global quit quit = True elif chr(c) == '\n': - if menutab == 4: + if menutab == 1: + curses.curs_set(1) + d = Dialog(dialog="dialog") + d.set_background_title("Inbox Message Dialog Box") + r, t = d.menu("Do what with \""+inbox[inboxcur][5]+"\" from \""+inbox[inboxcur][3]+"\"?", + choices=[("1", "View message"), + ("2", "Mark message as unread"), + ("3", "Reply"), + ("4", "Add sender to Address Book"), + ("5", "Save message as text file"), + ("6", "Move to trash")]) + if r == d.DIALOG_OK: + if t == "1": # View + d.set_background_title("\""+inbox[inboxcur][5]+"\" from \""+inbox[inboxcur][3]+"\" to \""+inbox[inboxcur][1]+"\"") + msg = "" + ret = sqlQuery("SELECT message FROM inbox WHERE msgid=?", inbox[inboxcur][0]) + if ret != []: + for row in ret: + msg, = row + msg = shared.fixPotentiallyInvalidUTF8Data(msg) + d.scrollbox(unicode(ascii(msg)), 30, 100, exit_label="Continue") + sqlExecute("UPDATE inbox SET read=1 WHERE msgid=?", inbox[inboxcur][0]) + inbox[inboxcur][7] = 1 + else: + d.scrollbox(unicode("Could not fetch message."), exit_label="Continue") + elif t == "2": # Mark unread + sqlExecute("UPDATE inbox SET read=0 WHERE msgid=?", inbox[inboxcur][0]) + inbox[inboxcur][7] = 0 + elif t == "3": # Reply + curses.curs_set(1) + m = inbox[inboxcur] + fromaddr = m[4] + ischan = False + for i, item in enumerate(addresses): + if fromaddr == item[2] and item[3] != 0: + ischan = True + break + if not addresses[i][1]: + d.scrollbox(unicode("Sending address disabled, please either enable it or choose a different address."), exit_label="Continue") + return + toaddr = m[2] + if ischan: + toaddr = fromaddr + + subject = m[5] + if not m[5][:4] == "Re: ": + subject = "Re: "+m[5] + body = "" + ret = sqlQuery("SELECT message FROM inbox WHERE msgid=?", m[0]) + if ret != []: + body = "\n\n------------------------------------------------------\n" + for row in ret: + body, = row + + sendMessage(fromaddr, toaddr, ischan, subject, body) + dialogreset(stdscr) + elif t == "4": # Add to Address Book + addr = inbox[inboxcur][4] + if addr in [item[1] for i,item in enumerate(addrbook)]: + r, t = d.inputbox("Label for address \""+addr+"\"") + if r == d.DIALOG_OK: + sqlExecute("INSERT INTO addressbook VALUES (?,?)", t, addr) + addrbook.reverse().append([t, addr]).reverse() # Prepend new entry + else: + d.scrollbox(unicode("The selected address is already in the Address Book."), exit_label="Continue") + elif t == "5": # Save message + d.set_background_title("Save \""+inbox[inboxcur][5]+"\" as text file") + r, t = d.inputbox("Filename", init=inbox[inboxcur][5]+".txt") + if r == d.DIALOG_OK: + msg = "" + ret = sqlQuery("SELECT message FROM inbox WHERE msgid=?", inbox[inboxcur][0]) + if ret != []: + for row in ret: + msg, = row + fh = open(t, "a") # Open in append mode just in case + fh.write(msg) + fh.close() + else: + d.scrollbox(unicode("Could not fetch message."), exit_label="Continue") + elif t == "6": # Move to trash + sqlExecute("UPDATE inbox SET folder='trash' WHERE msgid=?", inbox[inboxcur][0]) + del inbox[inboxcur] + d.scrollbox(unicode("Message moved to trash. There is no interface to view your trash, but the message is still on disk if you are desperate to recover it."), + exit_label="Continue") + dialogreset(stdscr) + elif menutab == 2: + curses.curs_set(1) + sendMessage(addresses[addrcur][2]) + dialogreset(stdscr) + elif menutab == 4: curses.curs_set(1) d = Dialog(dialog="dialog") d.set_background_title("Your Identities Dialog Box") @@ -256,6 +382,7 @@ def handlech(c, stdscr): else: d.scrollbox(unicode("Passphrases do not match"), exit_label="Continue") elif t == "2": # Copy address to internal buffer + global addrcopy addrcopy = addrcur elif t == "3": # Rename address label a = addresses[addrcur][2] @@ -302,7 +429,8 @@ def handlech(c, stdscr): else: m = shared.safeConfigGetBoolean(a, "mailinglist") r, t = d.radiolist("Select address behavior", - choices=[("1", "Behave as a normal address", not m), ("2", "Behave as a pseudo-mailing-list address", m)]) + choices=[("1", "Behave as a normal address", not m), + ("2", "Behave as a pseudo-mailing-list address", m)]) if r == d.DIALOG_OK: if t == "1" and m == True: shared.config.set(a, "mailinglist", "false") @@ -326,25 +454,235 @@ def handlech(c, stdscr): shared.config.write(configfile) dialogreset(stdscr) else: - global addrcur + global addrcur, inboxcur, abookcur if c == curses.KEY_UP: - if menutab == 4 and addrcur > 0: + if menutab == 1 and inboxcur > 0: + inboxcur -= 1 + if (menutab == 2 or menutab == 4) and addrcur > 0: addrcur -= 1 + if menutab == 6 and abookcur > 0: + abookcur -= 1 elif c == curses.KEY_DOWN: - if menutab == 4 and addrcur < len(addresses)-1: + if menutab == 1 and inboxcur < len(inbox)-1: + inboxcur += 1 + if (menutab == 2 or menutab == 4) and addrcur < len(addresses)-1: addrcur += 1 + if menutab == 6 and abookcur < len(addrbook)-1: + abookcur += 1 + elif c == curses.KEY_HOME: + if menutab == 1: + inboxcur = 0 + if menutab == 2 or menutab == 4: + addrcur = 0 + if menutab == 6: + abookcur = 0 + elif c == curses.KEY_END: + if menutab == 1: + inboxcur = len(inbox)-1 + if menutab == 2 or menutab == 4: + addrcur = len(addresses)-1 + if menutab == 6: + abookcur = len(addrbook)-1 redraw(stdscr) +def sendMessage(sender="", recv="", broadcast=None, subject="", body=""): + if sender == "": + return + d = Dialog(dialog="dialog") + d.set_background_title("Send a message") + if recv == "": + r, t = d.inputbox("Recipient address (Cancel to load from the Address Book or leave blank to broadcast)", 10, 60) + if r != d.DIALOG_OK: + global menutab + menutab = 6 + return + recv = t + if broadcast == None: + r, t = d.radiolist("How to send the message?", + choices=[("1", "Send to one or more specific people", True), + ("2", "Broadcast to everyone who is subscribed to your address", False)]) + if r != d.DIALOG_OK: + return + broadcast = False + if t == "2": # Broadcast + broadcast = True + if subject == "": + r, t = d.inputbox("Message subject", width=60) + if r != d.DIALOG_OK: + return + subject = shared.fixPotentiallyInvalidUTF8Data(t) + if body == "": + r, t = d.inputbox("Message body", 10, 80) + if r != d.DIALOG_OK: + return + body = shared.fixPotentiallyInvalidUTF8Data(t) + + if not broadcast: + recvlist = [] + for i, item in enumerate(recv.replace(",", ";").split(";")): + recvlist.append(item.strip()) + list(set(recvlist)) # Remove exact duplicates + for addr in recvlist: + if addr != "": + status, version, stream, ripe = decodeAddress(addr) + if status != "success": + d.set_background_title("Recipient address error") + err = "Could not decode" + addr + " : " + status + "\n\n" + if status == "missingbm": + err += "Bitmessage addresses should start with \"BM-\"." + elif status == "checksumfailed": + err += "The address was not typed or copied correctly." + elif status == "invalidcharacters": + err += "The address contains invalid characters." + elif status == "versiontoohigh": + err += "The address version is too high. Either you need to upgrade your Bitmessage software or your acquaintance is doing something clever." + elif status == "ripetooshort": + err += "Some data encoded in the address is too short. There might be something wrong with the software of your acquaintance." + elif status == "ripetoolong": + err += "Some data encoded in the address is too long. There might be something wrong with the software of your acquaintance." + else: + err += "It is unknown what is wrong with the address." + d.scrollbox(unicode(err), exit_label="Continue") + else: + addr = addBMIfNotPresent(addr) + if version > 4 or version <= 1: + d.set_background_title("Recipient address error") + d.scrollbox(unicode("Could not understand version number " + version + "of address" + addr + "."), + exit_label="Continue") + continue + if stream > 1 or stream == 0: + d.set_background_title("Recipient address error") + d.scrollbox(unicode("Bitmessage currently only supports stream numbers of 1, unlike as requested for address " + addr + "."), + exit_label="Continue") + continue + if len(shared.connectedHostsList) == 0: + d.set_background_title("Not connected warning") + d.scrollbox(unicode("Because you are not currently connected to the network, "), + exit_label="Continue") + + ackdata = OpenSSL.rand(32) + sqlExecute( + '''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)''', + '', + addr, + ripe, + sender, + subject, + body, + ackdata, + int(time.time()), + 'msgqueued', + 1, + 1, + 'sent', + 2) + shared.workerQueue.put(("sendmessage", addr)) + else: # Broadcast + if addr == "": + d.set_background_title("Empty sender error") + d.scrollbox(unicode("You must specify an address to send the message from."), + exit_label="Continue") + else: + ackdata = OpenSSL.rand(32) + addr = BROADCAST_STR + ripe = "" + sqlExecute( + '''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)''', + '', + addr, + ripe, + sender, + subject, + body, + ackdata, + int(time.time()), + 'broadcastqueued', + 1, + 1, + 'sent', + 2) + shared.workerQueue.put(('sendbroadcast', '')) + +def loadInbox(): + sys.stdout = sys.__stdout__ + print("Loading inbox messages...") + sys.stdout = printlog + + where = "toaddress || fromaddress || subject || message" + what = "%%" + ret = sqlQuery("""SELECT msgid, toaddress, fromaddress, subject, received, read + FROM inbox WHERE folder='inbox' AND %s LIKE ? + ORDER BY received + """ % (where,), what) + global inbox + for row in ret: + msgid, toaddr, fromaddr, subject, received, read = row + subject = ascii(shared.fixPotentiallyInvalidUTF8Data(subject)) + + # Set label for to address + try: + if toaddr == BROADCAST_STR: + tolabel = BROADCAST_STR + else: + tolabel = shared.config.get(toaddr, "label") + except: + tolabel = "" + if tolabel == "": + tolabel = toaddr + tolabel = shared.fixPotentiallyInvalidUTF8Data(tolabel) + + # Set label for from address + fromlabel = "" + if shared.config.has_section(fromaddr): + fromlabel = shared.config.get(fromaddr, "label") + if fromlabel == "": # Check Address Book + qr = sqlQuery("SELECT label FROM addressbook WHERE address=?", fromaddr) + if qr != []: + for r in qr: + fromlabel, = r + if fromlabel == "": # Check Subscriptions + qr = sqlQuery("SELECT label FROM subscriptions WHERE address=?", fromaddr) + if qr != []: + for r in qr: + fromlabel, = r + if fromlabel == "": + fromlabel = fromaddr + fromlabel = shared.fixPotentiallyInvalidUTF8Data(fromlabel) + + # Load into array + inbox.append([msgid, tolabel, toaddr, fromlabel, fromaddr, subject, + strftime(shared.config.get('bitmessagesettings', 'timeformat'), localtime(int(received))), + read]) + inbox.reverse() +def loadAddrBook(): + sys.stdout = sys.__stdout__ + print("Loading address book...") + sys.stdout = printlog + + ret = sqlQuery("SELECT label, address FROM addressbook") + global addrbook + for row in ret: + label, addr = row + label = shared.fixPotentiallyInvalidUTF8Data(label) + addrbook.append([label, addr]) + addrbook.reverse() def runwrapper(): sys.stdout = printlog - sys.stderr = errlog + #sys.stderr = errlog + + # Load messages from database + loadInbox() + #loadSent() + loadAddrBook() + stdscr = curses.initscr() global logpad logpad = curses.newpad(1024, curses.COLS) - stdscr.nodelay(1) + stdscr.nodelay(0) curses.curs_set(0) + stdscr.timeout(1000) curses.wrapper(run) shutdown() @@ -388,25 +726,11 @@ def run(stdscr): addresses[len(addresses)-1].append(0) # black addresses.reverse() - # Load messages from database - """ - loadInbox() - loadSend() - """ - - # Initialize address display and send form - """ - rerenderAddressBook() - rerenderSubscriptions() - rerenderComboBoxSendForm() - """ - + stdscr.clear() redraw(stdscr) while quit == False: drawtab(stdscr) handlech(stdscr.getch(), stdscr) - if naptime > 0: - curses.napms(naptime) def shutdown(): sys.stdout = sys.__stdout__ From 196047b2ed4ba724970637010cfc836e5c8e8c55 Mon Sep 17 00:00:00 2001 From: Luke Montalvo Date: Tue, 29 Apr 2014 21:45:41 -0500 Subject: [PATCH 5/8] + Add Sent, Subscription, and Blacklist tab functionality + Add code to delete address from Your Identities + Add code to load Sentbox, Subscriptions, and Blacklist * Lengthen column width from 30 to 40 to better fit unlabeled addresses and long subject lines * Fix row overflow support * Reorder Dialog initialization to remove duplicate code * Add reply argument to sendMessage() * Add newline to Move to Trash message * Replace Your Identities address copy option with an option to send a message --- src/bitmessagecurses/__init__.py | 390 ++++++++++++++++++++++++++----- 1 file changed, 329 insertions(+), 61 deletions(-) diff --git a/src/bitmessagecurses/__init__.py b/src/bitmessagecurses/__init__.py index 24482657..ed39bae4 100644 --- a/src/bitmessagecurses/__init__.py +++ b/src/bitmessagecurses/__init__.py @@ -36,7 +36,7 @@ startuptime = time.time() inbox = [] inboxcur = 0 -sent = [] +sentbox = [] sentcur = 0 addresses = [] addrcur = 0 @@ -47,6 +47,7 @@ addrbook = [] abookcur = 0 blacklist = [] blackcur = 0 +bwtype = "black" BROADCAST_STR = "[Broadcast subscribers]" @@ -100,10 +101,10 @@ def drawtab(stdscr): if menutab in range(1, len(menu)+1): if menutab == 1: # Inbox stdscr.addstr(3, 5, "To", curses.A_BOLD) - stdscr.addstr(3, 30, "From", curses.A_BOLD) - stdscr.addstr(3, 60, "Subject", curses.A_BOLD) - stdscr.addstr(3, 90, "Time Received", curses.A_BOLD) - stdscr.hline(4, 5, '-', 91) + stdscr.addstr(3, 40, "From", curses.A_BOLD) + stdscr.addstr(3, 80, "Subject", curses.A_BOLD) + stdscr.addstr(3, 120, "Time Received", curses.A_BOLD) + stdscr.hline(4, 5, '-', 121) for i, item in enumerate(inbox[max(min(len(inbox)-curses.LINES+6, inboxcur-5), 0):]): if 6+i < curses.LINES: a = 0 @@ -111,40 +112,82 @@ def drawtab(stdscr): a = a | curses.A_REVERSE if item[7] == False: # If not read, highlight a = a | curses.A_BOLD - stdscr.addstr(5+i, 5, item[1][:29], a) - stdscr.addstr(5+i, 30, item[3][:29], a) - stdscr.addstr(5+i, 60, item[5][:29], a) - stdscr.addstr(5+i, 90, item[6][:29], a) + stdscr.addstr(5+i, 5, item[1][:34], a) + stdscr.addstr(5+i, 40, item[3][:39], a) + stdscr.addstr(5+i, 80, item[5][:39], a) + stdscr.addstr(5+i, 120, item[6][:39], a) elif menutab == 3: # Sent - pass + stdscr.addstr(3, 5, "To", curses.A_BOLD) + stdscr.addstr(3, 40, "From", curses.A_BOLD) + stdscr.addstr(3, 80, "Subject", curses.A_BOLD) + stdscr.addstr(3, 120, "Status", curses.A_BOLD) + stdscr.hline(4, 5, '-', 121) + for i, item in enumerate(sentbox[max(min(len(sentbox)-curses.LINES+6, sentcur-5), 0):]): + if 6+i < curses.LINES: + a = 0 + if i == sentcur - max(min(len(sentbox)-curses.LINES+6, sentcur-5), 0): # Highlight current address + a = a | curses.A_REVERSE + stdscr.addstr(5+i, 5, item[0][:34], a) + stdscr.addstr(5+i, 40, item[2][:39], a) + stdscr.addstr(5+i, 80, item[4][:39], a) + stdscr.addstr(5+i, 120, item[5][:39], a) elif menutab == 2 or menutab == 4: # Send or Identities stdscr.addstr(3, 5, "Label", curses.A_BOLD) - stdscr.addstr(3, 30, "Address", curses.A_BOLD) - stdscr.addstr(3, 60, "Stream", curses.A_BOLD) - stdscr.hline(4, 5, '-', 61) + stdscr.addstr(3, 40, "Address", curses.A_BOLD) + stdscr.addstr(3, 80, "Stream", curses.A_BOLD) + stdscr.hline(4, 5, '-', 81) for i, item in enumerate(addresses[max(min(len(addresses)-curses.LINES+6, addrcur-5), 0):]): - a = 0 - if i == addrcur - max(min(len(addresses)-curses.LINES+6, addrcur-5), 0): # Highlight current address - a = a | curses.A_REVERSE - if item[1] == True and item[3] not in [8,9]: # Embolden enabled, non-special addresses - a = a | curses.A_BOLD - stdscr.addstr(5+i, 5, item[0][:30], a) - stdscr.addstr(5+i, 30, item[2][:30], cpair(item[3]) | a) - stdscr.addstr(5+i, 60, str(1)[:30], a) + if 6+i < curses.LINES: + a = 0 + if i == addrcur - max(min(len(addresses)-curses.LINES+6, addrcur-5), 0): # Highlight current address + a = a | curses.A_REVERSE + if item[1] == True and item[3] not in [8,9]: # Embolden enabled, non-special addresses + a = a | curses.A_BOLD + stdscr.addstr(5+i, 5, item[0][:34], a) + stdscr.addstr(5+i, 40, item[2][:39], cpair(item[3]) | a) + stdscr.addstr(5+i, 80, str(1)[:39], a) elif menutab == 5: # Subscriptions - pass + stdscr.addstr(3, 5, "Label", curses.A_BOLD) + stdscr.addstr(3, 80, "Address", curses.A_BOLD) + stdscr.addstr(3, 120, "Enabled", curses.A_BOLD) + stdscr.hline(4, 5, '-', 121) + for i, item in enumerate(subscriptions[max(min(len(subscriptions)-curses.LINES+6, subcur-5), 0):]): + if 6+i < curses.LINES: + a = 0 + if i == subcur - max(min(len(subscriptions)-curses.LINES+6, subcur-5), 0): # Highlight current address + a = a | curses.A_REVERSE + if item[2] == True: # Embolden enabled subscriptions + a = a | curses.A_BOLD + stdscr.addstr(5+i, 5, item[0][:74], a) + stdscr.addstr(5+i, 80, item[1][:39], a) + stdscr.addstr(5+i, 120, str(item[2]), a) elif menutab == 6: # Address book stdscr.addstr(3, 5, "Label", curses.A_BOLD) - stdscr.addstr(3, 30, "Address", curses.A_BOLD) - stdscr.hline(4, 5, '-', 31) + stdscr.addstr(3, 40, "Address", curses.A_BOLD) + stdscr.hline(4, 5, '-', 41) for i, item in enumerate(addrbook[max(min(len(addrbook)-curses.LINES+6, abookcur-5), 0):]): - a = 0 - if i == abookcur - max(min(len(addrbook)-curses.LINES+6, abookcur-5), 0): # Highlight current address - a = a | curses.A_REVERSE - stdscr.addstr(5+i, 5, item[0][:30], a) - stdscr.addstr(5+i, 30, item[1][:30], a) + if 6+i < curses.LINES: + a = 0 + if i == abookcur - max(min(len(addrbook)-curses.LINES+6, abookcur-5), 0): # Highlight current address + a = a | curses.A_REVERSE + stdscr.addstr(5+i, 5, item[0][:34], a) + stdscr.addstr(5+i, 40, item[1][:39], a) elif menutab == 7: # Blacklist - pass + stdscr.addstr(3, 5, "Type: "+bwtype) + stdscr.addstr(4, 5, "Label", curses.A_BOLD) + stdscr.addstr(4, 80, "Address", curses.A_BOLD) + stdscr.addstr(4, 120, "Enabled", curses.A_BOLD) + stdscr.hline(5, 5, '-', 121) + for i, item in enumerate(blacklist[max(min(len(blacklist)-curses.LINES+6, blackcur-5), 0):]): + if 7+i < curses.LINES: + a = 0 + if i == blackcur - max(min(len(blacklist)-curses.LINES+6, blackcur-5), 0): # Highlight current address + a = a | curses.A_REVERSE + if item[2] == True: # Embolden enabled subscriptions + a = a | curses.A_BOLD + stdscr.addstr(6+i, 5, item[0][:74], a) + stdscr.addstr(6+i, 80, item[1][:39], a) + stdscr.addstr(6+i, 120, str(item[2]), a) elif menutab == 8: # Network status # Connection data stdscr.addstr(4, 5, "Total Connections: "+str(len(shared.connectedHostsList)).ljust(2)) @@ -211,9 +254,9 @@ def handlech(c, stdscr): global quit quit = True elif chr(c) == '\n': + curses.curs_set(1) + d = Dialog(dialog="dialog") if menutab == 1: - curses.curs_set(1) - d = Dialog(dialog="dialog") d.set_background_title("Inbox Message Dialog Box") r, t = d.menu("Do what with \""+inbox[inboxcur][5]+"\" from \""+inbox[inboxcur][3]+"\"?", choices=[("1", "View message"), @@ -265,11 +308,11 @@ def handlech(c, stdscr): for row in ret: body, = row - sendMessage(fromaddr, toaddr, ischan, subject, body) + sendMessage(fromaddr, toaddr, ischan, subject, body, True) dialogreset(stdscr) elif t == "4": # Add to Address Book addr = inbox[inboxcur][4] - if addr in [item[1] for i,item in enumerate(addrbook)]: + if addr not in [item[1] for i,item in enumerate(addrbook)]: r, t = d.inputbox("Label for address \""+addr+"\"") if r == d.DIALOG_OK: sqlExecute("INSERT INTO addressbook VALUES (?,?)", t, addr) @@ -293,20 +336,37 @@ def handlech(c, stdscr): elif t == "6": # Move to trash sqlExecute("UPDATE inbox SET folder='trash' WHERE msgid=?", inbox[inboxcur][0]) del inbox[inboxcur] - d.scrollbox(unicode("Message moved to trash. There is no interface to view your trash, but the message is still on disk if you are desperate to recover it."), + d.scrollbox(unicode("Message moved to trash. There is no interface to view your trash, \nbut the message is still on disk if you are desperate to recover it."), exit_label="Continue") - dialogreset(stdscr) elif menutab == 2: - curses.curs_set(1) sendMessage(addresses[addrcur][2]) - dialogreset(stdscr) + elif menutab == 3: + d.set_background_title("Sent Messages Dialog Box") + r, t = d.menu("Do what with \""+sentbox[sentcur][4]+"\" to \""+sentbox[sentcur][0]+"\"?", + choices=[("1", "View message"), + ("2", "Move to trash")]) + if r == d.DIALOG_OK: + if t == "1": # View + d.set_background_title("\""+sentbox[sentcur][4]+"\" from \""+sentbox[sentcur][3]+"\" to \""+sentbox[sentcur][1]+"\"") + msg = "" + ret = sqlQuery("SELECT message FROM sent WHERE subject=? AND ackdata=?", sentbox[sentcur][4], sentbox[sentcur][6]) + if ret != []: + for row in ret: + msg, = row + msg = shared.fixPotentiallyInvalidUTF8Data(msg) + d.scrollbox(unicode(ascii(msg)), 30, 100, exit_label="Continue") + else: + d.scrollbox(unicode("Could not fetch message."), exit_label="Continue") + elif t == "2": # Move to trash + sqlExecute("UPDATE sent SET folder='trash' WHERE subject=? AND ackdata=?", sentbox[sentcur][4], sentbox[sentcur][6]) + del sentbox[sentcur] + d.scrollbox(unicode("Message moved to trash. There is no interface to view your trash, \nbut the message is still on disk if you are desperate to recover it."), + exit_label="Continue") elif menutab == 4: - curses.curs_set(1) - d = Dialog(dialog="dialog") d.set_background_title("Your Identities Dialog Box") r, t = d.menu("Do what with \""+addresses[addrcur][0]+"\" : \""+addresses[addrcur][2]+"\"?", choices=[("1", "Create new address"), - ("2", "Copy address to internal buffer"), + ("2", "Send a message from this address"), ("3", "Rename"), ("4", "Enable"), ("5", "Disable"), @@ -381,9 +441,8 @@ def handlech(c, stdscr): shared.addressGeneratorQueue.put(('createDeterministicAddresses', 4, stream, "unused deterministic address", number, str(passphrase), shorten)) else: d.scrollbox(unicode("Passphrases do not match"), exit_label="Continue") - elif t == "2": # Copy address to internal buffer - global addrcopy - addrcopy = addrcur + elif t == "2": # Send a message + sendMessage(addresses[addrcur][2]) elif t == "3": # Rename address label a = addresses[addrcur][2] label = addresses[addrcur][0] @@ -420,7 +479,12 @@ def handlech(c, stdscr): addresses[addrcur][1] = False shared.reloadMyAddressHashes() # Reload address hashes elif t == "6": # Delete address - pass + 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": + shared.config.remove_section(addresses[addrcur][2]) + with open(shared.appdata + 'keys.dat', 'wb') as configfile: + shared.config.write(configfile) + del addresses[addrcur] elif t == "7": # Special address behavior a = addresses[addrcur][2] d.set_background_title("Special address behavior") @@ -452,39 +516,147 @@ def handlech(c, stdscr): # Write config with open(shared.appdata + 'keys.dat', 'wb') as configfile: shared.config.write(configfile) - dialogreset(stdscr) + elif menutab == 5: + d.set_background_title("Subscriptions Dialog Box") + r, t = d.menu("Do what with subscription to \""+subscriptions[subcur][0]+"\"?", + choices=[("1", "Add new subscription"), + ("2", "Delete this subscription"), + ("3", "Enable"), + ("4", "Disable")]) + if r == d.DIALOG_OK: + if t == "1": + r, t = d.inputbox("New subscription address") + if r == d.DIALOG_OK: + addr = addBMIfNotPresent(t) + if not shared.isAddressInMySubscriptionsList(addr): + r, t = d.inputbox("New subscription label") + if r == d.DIALOG_OK: + label = t + subscriptions.reverse().append(label, addr, True).reverse() + sqlExecute("INSERT INTO subscriptions VALUES (?,?,?)", label, address, True) + shared.reloadBroadcastSendersForWhichImWatching() + elif t == "2": + r, t = d.inpuxbox("Type in \"I want to delete this subscription\"") + if r == d.DIALOG_OK and t == "I want to delete this subscription": + sqlExecute("DELETE FROM subscriptions WHERE label=? AND address=?", subscriptions[subcur][0], subscriptions[subcur][1]) + shared.reloadBroadcastSendersForWhichImWatching() + del subscriptions[subcur] + elif t == "3": + sqlExecute("UPDATE subscriptions SET enabled=1 WHERE label=? AND address=?", subscriptions[subcur][0], subscriptions[subcur][1]) + shared.reloadBroadcastSendersForWhichImWatching() + subscriptions[subcur][2] = True + elif t == "4": + sqlExecute("UPDATE subscriptions SET enabled=0 WHERE label=? AND address=?", subscriptions[subcur][0], subscriptions[subcur][1]) + shared.reloadBroadcastSendersForWhichImWatching() + subscriptions[subcur][2] = False + elif menutab == 6: + d.set_background_title("Address Book Dialog Box") + r, t = d.menu("Do what with \""+addrbook[abookcur][0]+"\" : \""+addrbook[abookcur][1]+"\"", + choices=[("1", "Send a message to this address"), + ("2", "Subscribe to this address"), + ("3", "Add new address to Address Book"), + ("4", "Delete this address")]) + if r == d.DIALOG_OK: + if t == "1": + sendMessage(recv=addrbook[abookcur][1]) + elif t == "2": + r, t = d.inputbox("New subscription label") + if r == d.DIALOG_OK: + label = t + subscriptions.reverse().append(label, addr, True).reverse() + sqlExecute("INSERT INTO subscriptions VALUES (?,?,?)", label, address, True) + shared.reloadBroadcastSendersForWhichImWatching() + elif t == "3": + r, t = d.inputbox("Input new address") + if r == d.DIALOG_OK: + addr = t + if addr not in [item[1] for i,item in enumerate(addrbook)]: + r, t = d.inputbox("Label for address \""+addr+"\"") + if r == d.DIALOG_OK: + sqlExecute("INSERT INTO addressbook VALUES (?,?)", t, addr) + addrbook.reverse().append([t, addr]).reverse() # Prepend new entry + else: + d.scrollbox(unicode("The selected address is already in the Address Book."), exit_label="Continue") + elif t == "4": + r, t = d.inputbox("Type in \"I want to delete this Address Book entry\"") + if r == d.DIALOG_OK and t == "I want to delete this Address Book entry": + sqlExecute("DELETE FROM addressbook WHERE label=? AND address=?", addrbook[abookcur][0], addrbook[abookcur][1]) + del addrbook[abookcur] + elif menutab == 7: + d.set_background_title("Blacklist Dialog Box") + r, t = d.menu("Do what with \""+blacklist[blackcur][0]+"\" : \""+blacklist[blackcur][1]+"\"?", + choices=[("1", "Delete"), + ("2", "Enable"), + ("3", "Disable")]) + if r == d.DIALOG_OK: + if t == "1": + r, t = d.inputbox("Type in \"I want to delete this Blacklist entry\"") + if r == d.DIALOG_OK and t == "I want to delete this Blacklist entry": + sqlExecute("DELETE FROM blacklist WHERE label=? AND address=?", blacklist[blackcur][0], blacklist[blackcur][1]) + del blacklist[blackcur] + elif t == "2": + sqlExecute("UPDATE blacklist SET enabled=1 WHERE label=? AND address=?", blacklist[blackcur][0], blacklist[blackcur][1]) + blacklist[blackcur][2] = True + elif t== "3": + sqlExecute("UPDATE blacklist SET enabled=0 WHERE label=? AND address=?", blacklist[blackcur][0], blacklist[blackcur][1]) + blacklist[blackcur][2] = False + dialogreset(stdscr) else: - global addrcur, inboxcur, abookcur + global addrcur, inboxcur, sentcur, subcur, abookcur, blackcur if c == curses.KEY_UP: if menutab == 1 and inboxcur > 0: inboxcur -= 1 if (menutab == 2 or menutab == 4) and addrcur > 0: addrcur -= 1 + if menutab == 3 and sentcur > 0: + sentcur -= 1 + if menutab == 5 and subcur > 0: + subcur -= 1 if menutab == 6 and abookcur > 0: abookcur -= 1 + if menutab == 7 and blackcur > 0: + blackcur -= 1 elif c == curses.KEY_DOWN: if menutab == 1 and inboxcur < len(inbox)-1: inboxcur += 1 if (menutab == 2 or menutab == 4) and addrcur < len(addresses)-1: addrcur += 1 + if menutab == 3 and sentcur < len(sentbox)-1: + sentcur += 1 + if menutab == 5 and subcur < len(subscriptions)-1: + subcur += 1 if menutab == 6 and abookcur < len(addrbook)-1: abookcur += 1 + if menutab == 7 and blackcur < len(blacklist)-1: + blackcur += 1 elif c == curses.KEY_HOME: if menutab == 1: inboxcur = 0 if menutab == 2 or menutab == 4: addrcur = 0 + if menutab == 3: + sentcur = 0 + if menutab == 5: + subcur = 0 if menutab == 6: abookcur = 0 + if menutab == 7: + blackcur = 0 elif c == curses.KEY_END: if menutab == 1: inboxcur = len(inbox)-1 if menutab == 2 or menutab == 4: addrcur = len(addresses)-1 + if menutab == 3: + sentcur = len(sentbox)-1 + if menutab == 5: + subcur = len(subscriptions)-1 if menutab == 6: abookcur = len(addrbook)-1 + if menutab == 7: + blackcur = len(blackcur)-1 redraw(stdscr) -def sendMessage(sender="", recv="", broadcast=None, subject="", body=""): +def sendMessage(sender="", recv="", broadcast=None, subject="", body="", reply=False): if sender == "": return d = Dialog(dialog="dialog") @@ -505,13 +677,13 @@ def sendMessage(sender="", recv="", broadcast=None, subject="", body=""): broadcast = False if t == "2": # Broadcast broadcast = True - if subject == "": - r, t = d.inputbox("Message subject", width=60) + if subject == "" or reply: + r, t = d.inputbox("Message subject", width=60, init=subject) if r != d.DIALOG_OK: return subject = shared.fixPotentiallyInvalidUTF8Data(t) - if body == "": - r, t = d.inputbox("Message body", 10, 80) + if body == "" or reply: + r, t = d.inputbox("Message body", 10, 80, init=body) if r != d.DIALOG_OK: return body = shared.fixPotentiallyInvalidUTF8Data(t) @@ -561,8 +733,8 @@ def sendMessage(sender="", recv="", broadcast=None, subject="", body=""): ackdata = OpenSSL.rand(32) sqlExecute( - '''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)''', - '', + "INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", + "", addr, ripe, sender, @@ -570,10 +742,10 @@ def sendMessage(sender="", recv="", broadcast=None, subject="", body=""): body, ackdata, int(time.time()), - 'msgqueued', + "msgqueued", 1, 1, - 'sent', + "sent", 2) shared.workerQueue.put(("sendmessage", addr)) else: # Broadcast @@ -586,8 +758,8 @@ def sendMessage(sender="", recv="", broadcast=None, subject="", body=""): addr = BROADCAST_STR ripe = "" sqlExecute( - '''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)''', - '', + "INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", + "", addr, ripe, sender, @@ -595,10 +767,10 @@ def sendMessage(sender="", recv="", broadcast=None, subject="", body=""): body, ackdata, int(time.time()), - 'broadcastqueued', + "broadcastqueued", 1, 1, - 'sent', + "sent", 2) shared.workerQueue.put(('sendbroadcast', '')) @@ -653,6 +825,83 @@ def loadInbox(): strftime(shared.config.get('bitmessagesettings', 'timeformat'), localtime(int(received))), read]) inbox.reverse() +def loadSent(): + sys.stdout = sys.__stdout__ + print("Loading sent messages...") + sys.stdout = printlog + + where = "toaddress || fromaddress || subject || message" + what = "%%" + ret = sqlQuery("""SELECT toaddress, fromaddress, subject, status, ackdata, lastactiontime + FROM sent WHERE folder='sent' AND %s LIKE ? + ORDER BY lastactiontime + """ % (where,), what) + global sent + for row in ret: + toaddr, fromaddr, subject, status, ackdata, lastactiontime = row + subject = ascii(shared.fixPotentiallyInvalidUTF8Data(subject)) + + # Set label for to address + tolabel = "" + qr = sqlQuery("SELECT label FROM addressbook WHERE address=?", toaddr) + if qr != []: + for r in qr: + tolabel, = r + if tolabel == "": + qr = sqlQuery("SELECT label FROM subscriptions WHERE address=?", toaddr) + if qr != []: + for r in qr: + tolabel, = r + if tolabel == "": + if shared.config.has_section(toaddr): + tolabel = shared.config.get(toaddr, "label") + if tolabel == "": + tolabel = toaddr + + # Set label for from address + fromlabel = "" + if shared.config.has_section(fromaddr): + fromlabel = shared.config.get(fromaddr, "label") + if fromlabel == "": + fromlabel = fromaddr + + # Set status string + if status == "awaitingpubkey": + statstr = "Waiting for their public key. Will request it again soon" + elif status == "doingpowforpubkey": + statstr = "Encryption key request queued" + elif status == "msgqueued": + statstr = "Message queued" + elif status == "msgsent": + t = strftime(shared.config.get('bitmessagesettings', 'timeformat'), localtime(int(lastactiontime))) + statstr = "Message sent at "+t+".Waiting for acknowledgement." + elif status == "msgsentnoackexpected": + t = strftime(shared.config.get('bitmessagesettings', 'timeformat'), localtime(int(lastactiontime))) + statstr = "Message sent at "+t+"." + elif status == "doingmsgpow": + statstr = "The proof of work required to send the message has been queued." + elif status == "askreceived": + t = strftime(shared.config.get('bitmessagesettings', 'timeformat'), localtime(int(lastactiontime))) + statstr = "Acknowledgment of the message received at "+t+"." + elif status == "broadcastqueued": + statstr = "Broadcast queued." + elif status == "broadcastsent": + t = strftime(shared.config.get('bitmessagesettings', 'timeformat'), localtime(int(lastactiontime))) + statstr = "Broadcast sent at "+t+"." + elif status == "forcepow": + statstr = "Forced difficulty override. Message will start sending soon." + elif status == "badkey": + statstr = "Warning: Could not encrypt message because the recipient's encryption key is no good." + elif status == "toodifficult": + statstr = "Error: The work demanded by the recipient is more difficult than you are willing to do." + else: + t = strftime(shared.config.get('bitmessagesettings', 'timeformat'), localtime(int(lastactiontime))) + statstr = "Unknown status "+status+" at "+t+"." + + # Load into array + sentbox.append([tolabel, toaddr, fromlabel, fromaddr, subject, statstr, ackdata, + strftime(shared.config.get('bitmessagesettings', 'timeformat'), localtime(int(lastactiontime)))]) + sentbox.reverse() def loadAddrBook(): sys.stdout = sys.__stdout__ print("Loading address book...") @@ -665,6 +914,23 @@ def loadAddrBook(): label = shared.fixPotentiallyInvalidUTF8Data(label) addrbook.append([label, addr]) addrbook.reverse() +def loadSubscriptions(): + ret = sqlQuery("SELECT label, address, enabled FROM subscriptions") + for row in ret: + label, address, enabled = row + subscriptions.append([label, address, enabled]) + subscriptions.reverse() +def loadBlackWhiteList(): + global bwtype + bwtype = shared.config.get("bitmessagesettings", "blackwhitelist") + if bwtype == "black": + ret = sqlQuery("SELECT label, address, enabled FROM blacklist") + else: + ret = sqlQuery("SELECT label, address, enabled FROM whitelist") + for row in ret: + label, address, enabled = row + blacklist.append([label, address, enabled]) + blacklist.reverse() def runwrapper(): sys.stdout = printlog @@ -672,8 +938,10 @@ def runwrapper(): # Load messages from database loadInbox() - #loadSent() + loadSent() loadAddrBook() + loadSubscriptions() + loadBlackWhiteList() stdscr = curses.initscr() From d4327cef81e06e80ddedbedea871ccc48807cb5e Mon Sep 17 00:00:00 2001 From: Luke Montalvo Date: Wed, 30 Apr 2014 19:03:12 -0500 Subject: [PATCH 6/8] + Add text wrapping to message viewer * Move global variable declaration to prevent warnings * Fix Address Book and Subscriptions entry prepending * Fix shared.fixPotentiallyInvalidUTF8Data() corrupting sent subjects and message bodies --- src/bitmessagecurses/__init__.py | 42 ++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/src/bitmessagecurses/__init__.py b/src/bitmessagecurses/__init__.py index ed39bae4..da262f85 100644 --- a/src/bitmessagecurses/__init__.py +++ b/src/bitmessagecurses/__init__.py @@ -10,6 +10,7 @@ import os import sys import StringIO +from textwrap import * import time from time import strftime, localtime @@ -246,6 +247,7 @@ def dialogreset(stdscr): curses.curs_set(0) def handlech(c, stdscr): if c != curses.ERR: + global inboxcur, addrcur, sentcur, subcur, abookcur, blackcur if c in range(256): if chr(c) in '12345678': global menutab @@ -268,13 +270,16 @@ def handlech(c, stdscr): if r == d.DIALOG_OK: if t == "1": # View d.set_background_title("\""+inbox[inboxcur][5]+"\" from \""+inbox[inboxcur][3]+"\" to \""+inbox[inboxcur][1]+"\"") - msg = "" + data = "" ret = sqlQuery("SELECT message FROM inbox WHERE msgid=?", inbox[inboxcur][0]) if ret != []: for row in ret: - msg, = row - msg = shared.fixPotentiallyInvalidUTF8Data(msg) - d.scrollbox(unicode(ascii(msg)), 30, 100, exit_label="Continue") + data, = row + data = shared.fixPotentiallyInvalidUTF8Data(data) + msg = "" + for i, item in enumerate(data.split("\n")): + msg += fill(item, replace_whitespace=False)+"\n" + d.scrollbox(unicode(ascii(msg)), 30, 80, exit_label="Continue") sqlExecute("UPDATE inbox SET read=1 WHERE msgid=?", inbox[inboxcur][0]) inbox[inboxcur][7] = 1 else: @@ -311,12 +316,17 @@ def handlech(c, stdscr): sendMessage(fromaddr, toaddr, ischan, subject, body, True) dialogreset(stdscr) elif t == "4": # Add to Address Book + global addrbook addr = inbox[inboxcur][4] if addr not in [item[1] for i,item in enumerate(addrbook)]: r, t = d.inputbox("Label for address \""+addr+"\"") if r == d.DIALOG_OK: - sqlExecute("INSERT INTO addressbook VALUES (?,?)", t, addr) - addrbook.reverse().append([t, addr]).reverse() # Prepend new entry + label = t + sqlExecute("INSERT INTO addressbook VALUES (?,?)", label, addr) + # Prepend entry + addrbook.reverse() + addrbook.append([label, addr]) + addrbook.reverse() else: d.scrollbox(unicode("The selected address is already in the Address Book."), exit_label="Continue") elif t == "5": # Save message @@ -348,13 +358,16 @@ def handlech(c, stdscr): if r == d.DIALOG_OK: if t == "1": # View d.set_background_title("\""+sentbox[sentcur][4]+"\" from \""+sentbox[sentcur][3]+"\" to \""+sentbox[sentcur][1]+"\"") - msg = "" + data = "" ret = sqlQuery("SELECT message FROM sent WHERE subject=? AND ackdata=?", sentbox[sentcur][4], sentbox[sentcur][6]) if ret != []: for row in ret: - msg, = row - msg = shared.fixPotentiallyInvalidUTF8Data(msg) - d.scrollbox(unicode(ascii(msg)), 30, 100, exit_label="Continue") + data, = row + data = shared.fixPotentiallyInvalidUTF8Data(data) + msg = "" + for i, item in enumerate(data.split("\n")): + msg += fill(item, replace_whitespace=False)+"\n" + d.scrollbox(unicode(ascii(msg)), 30, 80, exit_label="Continue") else: d.scrollbox(unicode("Could not fetch message."), exit_label="Continue") elif t == "2": # Move to trash @@ -602,7 +615,6 @@ def handlech(c, stdscr): blacklist[blackcur][2] = False dialogreset(stdscr) else: - global addrcur, inboxcur, sentcur, subcur, abookcur, blackcur if c == curses.KEY_UP: if menutab == 1 and inboxcur > 0: inboxcur -= 1 @@ -681,13 +693,14 @@ def sendMessage(sender="", recv="", broadcast=None, subject="", body="", reply=F r, t = d.inputbox("Message subject", width=60, init=subject) if r != d.DIALOG_OK: return - subject = shared.fixPotentiallyInvalidUTF8Data(t) + subject = t if body == "" or reply: r, t = d.inputbox("Message body", 10, 80, init=body) if r != d.DIALOG_OK: return - body = shared.fixPotentiallyInvalidUTF8Data(t) - + body = t + body = body.replace("\\n", "\n").replace("\\t", "\t") + if not broadcast: recvlist = [] for i, item in enumerate(recv.replace(",", ";").split(";")): @@ -730,7 +743,6 @@ def sendMessage(sender="", recv="", broadcast=None, subject="", body="", reply=F d.set_background_title("Not connected warning") d.scrollbox(unicode("Because you are not currently connected to the network, "), exit_label="Continue") - ackdata = OpenSSL.rand(32) sqlExecute( "INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", From f24f275e61b52916385c816430bd20fe30a0776f Mon Sep 17 00:00:00 2001 From: Luke Montalvo Date: Wed, 30 Apr 2014 19:29:04 -0500 Subject: [PATCH 7/8] * A few minor changes including accidentally unappended code --- src/bitmessagecurses/__init__.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/bitmessagecurses/__init__.py b/src/bitmessagecurses/__init__.py index da262f85..c19ce034 100644 --- a/src/bitmessagecurses/__init__.py +++ b/src/bitmessagecurses/__init__.py @@ -1,7 +1,7 @@ # Copyright (c) 2014 Luke Montalvo # This file adds a alternative commandline interface, feel free to critique and fork # -# This has only been tested on Arch Linux +# This has only been tested on Arch Linux and Linux Mint # Dependencies: # * from python2-pip # * python2-pythondialog @@ -545,7 +545,11 @@ def handlech(c, stdscr): r, t = d.inputbox("New subscription label") if r == d.DIALOG_OK: label = t - subscriptions.reverse().append(label, addr, True).reverse() + # Prepend entry + subscriptions.reverse() + subscriptions.append([label, addr, True]) + subscriptions.reverse() + sqlExecute("INSERT INTO subscriptions VALUES (?,?,?)", label, address, True) shared.reloadBroadcastSendersForWhichImWatching() elif t == "2": @@ -576,7 +580,11 @@ def handlech(c, stdscr): r, t = d.inputbox("New subscription label") if r == d.DIALOG_OK: label = t - subscriptions.reverse().append(label, addr, True).reverse() + # Prepend entry + subscriptions.reverse() + subscriptions.append([label, addr, True]) + subscriptions.reverse() + sqlExecute("INSERT INTO subscriptions VALUES (?,?,?)", label, address, True) shared.reloadBroadcastSendersForWhichImWatching() elif t == "3": @@ -587,7 +595,10 @@ def handlech(c, stdscr): r, t = d.inputbox("Label for address \""+addr+"\"") if r == d.DIALOG_OK: sqlExecute("INSERT INTO addressbook VALUES (?,?)", t, addr) - addrbook.reverse().append([t, addr]).reverse() # Prepend new entry + # Prepend entry + addrbook.reverse() + addrbook.append([t, addr]) + addrbook.reverse() else: d.scrollbox(unicode("The selected address is already in the Address Book."), exit_label="Continue") elif t == "4": From cc5301327ce90457f0d64fdba1f4489bc951a2d6 Mon Sep 17 00:00:00 2001 From: Luke Montalvo Date: Wed, 30 Apr 2014 19:29:04 -0500 Subject: [PATCH 8/8] * A few minor changes including accidentally unappended code * Fix sending as a chan address --- src/bitmessagecurses/__init__.py | 37 +++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/src/bitmessagecurses/__init__.py b/src/bitmessagecurses/__init__.py index da262f85..91c99df8 100644 --- a/src/bitmessagecurses/__init__.py +++ b/src/bitmessagecurses/__init__.py @@ -1,7 +1,7 @@ # Copyright (c) 2014 Luke Montalvo # This file adds a alternative commandline interface, feel free to critique and fork # -# This has only been tested on Arch Linux +# This has only been tested on Arch Linux and Linux Mint # Dependencies: # * from python2-pip # * python2-pythondialog @@ -349,7 +349,10 @@ def handlech(c, stdscr): d.scrollbox(unicode("Message moved to trash. There is no interface to view your trash, \nbut the message is still on disk if you are desperate to recover it."), exit_label="Continue") elif menutab == 2: - sendMessage(addresses[addrcur][2]) + a = "" + if addresses[addrcur][3] != 0: # if current address is a chan + a = addresses[addrcur][2] + sendMessage(addresses[addrcur][2], a) elif menutab == 3: d.set_background_title("Sent Messages Dialog Box") r, t = d.menu("Do what with \""+sentbox[sentcur][4]+"\" to \""+sentbox[sentcur][0]+"\"?", @@ -455,7 +458,10 @@ def handlech(c, stdscr): else: d.scrollbox(unicode("Passphrases do not match"), exit_label="Continue") elif t == "2": # Send a message - sendMessage(addresses[addrcur][2]) + a = "" + if addresses[addrcur][3] != 0: # if current address is a chan + a = addresses[addrcur][2] + sendMessage(addresses[addrcur][2], a) elif t == "3": # Rename address label a = addresses[addrcur][2] label = addresses[addrcur][0] @@ -545,7 +551,11 @@ def handlech(c, stdscr): r, t = d.inputbox("New subscription label") if r == d.DIALOG_OK: label = t - subscriptions.reverse().append(label, addr, True).reverse() + # Prepend entry + subscriptions.reverse() + subscriptions.append([label, addr, True]) + subscriptions.reverse() + sqlExecute("INSERT INTO subscriptions VALUES (?,?,?)", label, address, True) shared.reloadBroadcastSendersForWhichImWatching() elif t == "2": @@ -576,7 +586,11 @@ def handlech(c, stdscr): r, t = d.inputbox("New subscription label") if r == d.DIALOG_OK: label = t - subscriptions.reverse().append(label, addr, True).reverse() + # Prepend entry + subscriptions.reverse() + subscriptions.append([label, addr, True]) + subscriptions.reverse() + sqlExecute("INSERT INTO subscriptions VALUES (?,?,?)", label, address, True) shared.reloadBroadcastSendersForWhichImWatching() elif t == "3": @@ -587,7 +601,10 @@ def handlech(c, stdscr): r, t = d.inputbox("Label for address \""+addr+"\"") if r == d.DIALOG_OK: sqlExecute("INSERT INTO addressbook VALUES (?,?)", t, addr) - addrbook.reverse().append([t, addr]).reverse() # Prepend new entry + # Prepend entry + addrbook.reverse() + addrbook.append([t, addr]) + addrbook.reverse() else: d.scrollbox(unicode("The selected address is already in the Address Book."), exit_label="Continue") elif t == "4": @@ -680,7 +697,7 @@ def sendMessage(sender="", recv="", broadcast=None, subject="", body="", reply=F menutab = 6 return recv = t - if broadcast == None: + if broadcast == None and sender != recv: r, t = d.radiolist("How to send the message?", choices=[("1", "Send to one or more specific people", True), ("2", "Broadcast to everyone who is subscribed to your address", False)]) @@ -761,18 +778,18 @@ def sendMessage(sender="", recv="", broadcast=None, subject="", body="", reply=F 2) shared.workerQueue.put(("sendmessage", addr)) else: # Broadcast - if addr == "": + if recv == "": d.set_background_title("Empty sender error") d.scrollbox(unicode("You must specify an address to send the message from."), exit_label="Continue") else: ackdata = OpenSSL.rand(32) - addr = BROADCAST_STR + recv = BROADCAST_STR ripe = "" sqlExecute( "INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", "", - addr, + recv, ripe, sender, subject,