diff --git a/.travis.yml b/.travis.yml index 1edba418..d7141188 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ addons: packages: - build-essential - libcap-dev + - tor install: - pip install -r requirements.txt - ln -s src pybitmessage # tests environment diff --git a/requirements.txt b/requirements.txt index c55e5cf1..be429a9f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ python_prctl psutil pycrypto +stem diff --git a/setup.py b/setup.py index 3a9c7a3c..61afa91e 100644 --- a/setup.py +++ b/setup.py @@ -70,7 +70,6 @@ if __name__ == "__main__": 'pybitmessage.network', 'pybitmessage.plugins', 'pybitmessage.pyelliptic', - 'pybitmessage.socks', 'pybitmessage.storage' ] diff --git a/src/bitmessagecurses/__init__.py b/src/bitmessagecurses/__init__.py index dc6e8a0c..80dc3f14 100644 --- a/src/bitmessagecurses/__init__.py +++ b/src/bitmessagecurses/__init__.py @@ -1,40 +1,40 @@ +""" +src/bitmessagecurses/__init__.py +================================ +""" + # 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 and Linux Mint # Dependencies: # * from python2-pip # * python2-pythondialog # * dialog +import ConfigParser +import curses import os import sys -import StringIO -from textwrap import * - import time -from time import strftime, localtime +from textwrap import fill from threading import Timer -import curses -import dialog -from dialog import Dialog -from helper_sql import * -from helper_ackPayload import genAckPayload - -from addresses import * -import ConfigParser +from addresses import addBMIfNotPresent, decodeAddress from bmconfigparser import BMConfigParser +from dialog import Dialog +from helper_ackPayload import genAckPayload +from helper_sql import sqlExecute, sqlQuery from inventory import Inventory import l10n +import network.stats from pyelliptic.openssl import OpenSSL import queues import shared import shutdown -import network.stats -quit = False +quit = False # pylint: disable=redefined-builtin menutab = 1 menu = ["Inbox", "Send", "Sent", "Your Identities", "Subscriptions", "Address Book", "Blacklist", "Network Status"] naptime = 100 @@ -60,156 +60,189 @@ bwtype = "black" BROADCAST_STR = "[Broadcast subscribers]" -class printLog: + +class printLog: # pylint: disable=no-self-use, no-init, old-style-class + """Printing logs""" + def write(self, output): + # pylint: disable=global-statement global log log += output + def flush(self): pass -class errLog: + + +class errLog: # pylint: disable=no-self-use, no-init, old-style-class + """Error logs""" def write(self, output): + # pylint: disable=global-statement global log - log += "!"+output + log += "!" + output + def flush(self): pass + + printlog = printLog() errlog = errLog() def cpair(a): + """Color pairs""" r = curses.color_pair(a) - if r not in range(1, curses.COLOR_PAIRS-1): + if r not in range(1, curses.COLOR_PAIRS - 1): r = curses.color_pair(0) return r + + def ascii(s): + """ASCII values""" r = "" for c in s: if ord(c) in range(128): r += c return r + def drawmenu(stdscr): + """Creating menu's""" menustr = " " - for i in range(0, len(menu)): - if menutab == i+1: + for i, _ in enumerate(menu): + if menutab == i + 1: menustr = menustr[:-1] menustr += "[" - menustr += str(i+1)+menu[i] - if menutab == i+1: + menustr += str(i + 1) + menu[i] + if menutab == i + 1: menustr += "] " - elif i != len(menu)-1: + elif i != len(menu) - 1: menustr += " " stdscr.addstr(2, 5, menustr, curses.A_UNDERLINE) + def set_background_title(d, title): + """Setting background title""" try: d.set_background_title(title) except: d.add_persistent_args(("--backtitle", title)) + def scrollbox(d, text, height=None, width=None): + """Setting scroll box""" try: - d.scrollbox(text, height, width, exit_label = "Continue") + d.scrollbox(text, height, width, exit_label="Continue") except: - d.msgbox(text, height or 0, width or 0, ok_label = "Continue") + d.msgbox(text, height or 0, width or 0, ok_label="Continue") + def resetlookups(): - global inventorydata + """Reset the Inventory Lookups""" + global inventorydata # pylint: disable=global-statement inventorydata = Inventory().numberOfInventoryLookupsPerformed Inventory().numberOfInventoryLookupsPerformed = 0 Timer(1, resetlookups, ()).start() -def drawtab(stdscr): - if menutab in range(1, len(menu)+1): - if menutab == 1: # Inbox + + +def drawtab(stdscr): # pylint: disable=too-many-branches, too-many-statements + """Method for drawing different tabs""" + if menutab in range(1, len(menu) + 1): + if menutab == 1: # Inbox 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, "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: + 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 + 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 + if item[7] is False: # If not read, highlight a = a | curses.A_BOLD - 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 + 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 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: + 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 + 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(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, 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):]): - if 6+i < curses.LINES: + for i, item in enumerate(addresses[max(min(len(addresses) - curses.LINES + 6, addrcur - 5), 0):]): + if 6 + i < curses.LINES: a = 0 - if i == addrcur - max(min(len(addresses)-curses.LINES+6, addrcur-5), 0): # 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 + if item[1] 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 + 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 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: + 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 + 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 + if item[2]: # 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(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, 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):]): - if 6+i < curses.LINES: + for i, item in enumerate(addrbook[max(min(len(addrbook) - curses.LINES + 6, abookcur - 5), 0):]): + if 6 + i < curses.LINES: a = 0 - if i == abookcur - max(min(len(addrbook)-curses.LINES+6, abookcur-5), 0): # Highlight current address + 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 - stdscr.addstr(3, 5, "Type: "+bwtype) + stdscr.addstr(5 + i, 5, item[0][:34], a) + stdscr.addstr(5 + i, 40, item[1][:39], a) + elif menutab == 7: # Blacklist + 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: + 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 + 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 + if item[2]: # 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 + 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 connected_hosts = network.stats.connectedHostsList() stdscr.addstr( @@ -228,51 +261,63 @@ def drawtab(stdscr): for i, item in enumerate(streamcount): if i < 4: if i == 0: - stdscr.addstr(8+i, 6, "?") + stdscr.addstr(8 + i, 6, "?") else: - stdscr.addstr(8+i, 6, str(i)) - stdscr.addstr(8+i, 18, str(item).ljust(2)) - + 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 "+l10n.formatTimestamp(startuptime, False)) - 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.") - + stdscr.addstr(6, 35, "Since startup on " + l10n.formatTimestamp(startuptime, False)) + 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(inventorydata).ljust(3)) - + stdscr.addstr(11, 35, "Inventory lookups per second: " + str(inventorydata).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)] + del l[:(n - 256)] logpad.erase() n = len(l) for i, item in enumerate(l): a = 0 - if len(item) > 0 and item[0] == '!': + if item 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) + logpad.refresh(n - curses.LINES + 2, 0, 14, 6, curses.LINES - 2, curses.COLS - 7) stdscr.refresh() + def redraw(stdscr): + """Redraw menu""" stdscr.erase() stdscr.border() drawmenu(stdscr) stdscr.refresh() + + def dialogreset(stdscr): + """Resetting dialogue""" stdscr.clear() stdscr.keypad(1) curses.curs_set(0) + + +# pylint: disable=too-many-branches, too-many-statements def handlech(c, stdscr): + # pylint: disable=redefined-outer-name, too-many-nested-blocks, too-many-locals, global-statement if c != curses.ERR: global inboxcur, addrcur, sentcur, subcur, abookcur, blackcur - if c in range(256): + if c in range(256): if chr(c) in '12345678': global menutab menutab = int(chr(c)) @@ -284,17 +329,27 @@ def handlech(c, stdscr): d = Dialog(dialog="dialog") if menutab == 1: set_background_title(d, "Inbox Message Dialog Box") - r, t = d.menu("Do what with \""+inbox[inboxcur][5]+"\" from \""+inbox[inboxcur][3]+"\"?", - choices=[("1", "View message"), + 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 - set_background_title(d, "\""+inbox[inboxcur][5]+"\" from \""+inbox[inboxcur][3]+"\" to \""+inbox[inboxcur][1]+"\"") - data = "" + if t == "1": # View + set_background_title( + d, + "\"" + + inbox[inboxcur][5] + + "\" from \"" + + inbox[inboxcur][3] + + "\" to \"" + + inbox[inboxcur][1] + + "\"") + data = "" # pyint: disable=redefined-outer-name ret = sqlQuery("SELECT message FROM inbox WHERE msgid=?", inbox[inboxcur][0]) if ret != []: for row in ret: @@ -302,16 +357,16 @@ def handlech(c, stdscr): data = shared.fixPotentiallyInvalidUTF8Data(data) msg = "" for i, item in enumerate(data.split("\n")): - msg += fill(item, replace_whitespace=False)+"\n" + msg += fill(item, replace_whitespace=False) + "\n" scrollbox(d, unicode(ascii(msg)), 30, 80) sqlExecute("UPDATE inbox SET read=1 WHERE msgid=?", inbox[inboxcur][0]) inbox[inboxcur][7] = 1 else: scrollbox(d, unicode("Could not fetch message.")) - elif t == "2": # Mark unread + elif t == "2": # Mark unread sqlExecute("UPDATE inbox SET read=0 WHERE msgid=?", inbox[inboxcur][0]) inbox[inboxcur][7] = 0 - elif t == "3": # Reply + elif t == "3": # Reply curses.curs_set(1) m = inbox[inboxcur] fromaddr = m[4] @@ -320,29 +375,31 @@ def handlech(c, stdscr): if fromaddr == item[2] and item[3] != 0: ischan = True break - if not addresses[i][1]: - scrollbox(d, unicode("Sending address disabled, please either enable it or choose a different address.")) + if not addresses[i][1]: # pylint: disable=undefined-loop-variable + scrollbox(d, unicode( + "Sending address disabled, please either enable it" + "or choose a different address.")) return toaddr = m[2] if ischan: toaddr = fromaddr - + subject = m[5] if not m[5][:4] == "Re: ": - subject = "Re: "+m[5] + 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, True) dialogreset(stdscr) - elif t == "4": # Add to Address Book + elif t == "4": # Add to Address Book 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 addr not in [item[1] for i, item in enumerate(addrbook)]: + r, t = d.inputbox("Label for address \"" + addr + "\"") if r == d.DIALOG_OK: label = t sqlExecute("INSERT INTO addressbook VALUES (?,?)", label, addr) @@ -352,61 +409,85 @@ def handlech(c, stdscr): addrbook.reverse() else: scrollbox(d, unicode("The selected address is already in the Address Book.")) - elif t == "5": # Save message - set_background_title(d, "Save \""+inbox[inboxcur][5]+"\" as text file") - r, t = d.inputbox("Filename", init=inbox[inboxcur][5]+".txt") + elif t == "5": # Save message + set_background_title(d, "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 = open(t, "a") # Open in append mode just in case fh.write(msg) fh.close() else: scrollbox(d, unicode("Could not fetch message.")) - elif t == "6": # Move to trash + elif t == "6": # Move to trash sqlExecute("UPDATE inbox SET folder='trash' WHERE msgid=?", inbox[inboxcur][0]) del inbox[inboxcur] - scrollbox(d, 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.")) + scrollbox(d, 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.")) elif menutab == 2: a = "" - if addresses[addrcur][3] != 0: # if current address is a chan + if addresses[addrcur][3] != 0: # if current address is a chan a = addresses[addrcur][2] sendMessage(addresses[addrcur][2], a) elif menutab == 3: set_background_title(d, "Sent Messages Dialog Box") - r, t = d.menu("Do what with \""+sentbox[sentcur][4]+"\" to \""+sentbox[sentcur][0]+"\"?", - choices=[("1", "View message"), + 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 - set_background_title(d, "\""+sentbox[sentcur][4]+"\" from \""+sentbox[sentcur][3]+"\" to \""+sentbox[sentcur][1]+"\"") + if t == "1": # View + set_background_title( + d, + "\"" + + sentbox[sentcur][4] + + "\" from \"" + + sentbox[sentcur][3] + + "\" to \"" + + sentbox[sentcur][1] + + "\"") data = "" - ret = sqlQuery("SELECT message FROM sent WHERE subject=? AND ackdata=?", sentbox[sentcur][4], sentbox[sentcur][6]) + ret = sqlQuery( + "SELECT message FROM sent WHERE subject=? AND ackdata=?", + sentbox[sentcur][4], + sentbox[sentcur][6]) if ret != []: for row in ret: data, = row data = shared.fixPotentiallyInvalidUTF8Data(data) msg = "" for i, item in enumerate(data.split("\n")): - msg += fill(item, replace_whitespace=False)+"\n" + msg += fill(item, replace_whitespace=False) + "\n" scrollbox(d, unicode(ascii(msg)), 30, 80) else: scrollbox(d, unicode("Could not fetch message.")) - elif t == "2": # Move to trash - sqlExecute("UPDATE sent SET folder='trash' WHERE subject=? AND ackdata=?", sentbox[sentcur][4], sentbox[sentcur][6]) + 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] - scrollbox(d, 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.")) + scrollbox(d, 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.")) elif menutab == 4: set_background_title(d, "Your Identities Dialog Box") if len(addresses) <= addrcur: - r, t = d.menu("Do what with addresses?", - choices=[("1", "Create new address")]) + r, t = d.menu( + "Do what with addresses?", + choices=[ + ("1", "Create new address")]) else: - r, t = d.menu("Do what with \""+addresses[addrcur][0]+"\" : \""+addresses[addrcur][2]+"\"?", - choices=[("1", "Create new address"), + r, t = d.menu( + "Do what with \"" + addresses[addrcur][0] + "\" : \"" + addresses[addrcur][2] + "\"?", + choices=[ + ("1", "Create new address"), ("2", "Send a message from this address"), ("3", "Rename"), ("4", "Enable"), @@ -414,31 +495,41 @@ def handlech(c, stdscr): ("6", "Delete"), ("7", "Special address behavior")]) if r == d.DIALOG_OK: - if t == "1": # Create new address + if t == "1": # Create new address set_background_title(d, "Create new address") - scrollbox(d, 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")) - r, t = d.menu("Choose an address generation technique", - choices=[("1", "Use a random number generator"), + scrollbox( + d, 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")) + 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": set_background_title(d, "Randomly generate address") r, t = d.inputbox("Label (not shown to anyone except you)") label = "" - if r == d.DIALOG_OK and len(t) > 0: + if r == d.DIALOG_OK and t: 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)")]) + 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 @@ -450,42 +541,69 @@ def handlech(c, stdscr): 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", 1 if shorten else 0)]) + r, t = d.checklist( + "Miscellaneous options", + choices=[( + "1", + "Spend time shortening the address", + 1 if shorten else 0)]) if r == d.DIALOG_OK and "1" in t: shorten = True - queues.addressGeneratorQueue.put(("createRandomAddress", 4, stream, label, 1, "", shorten)) + queues.addressGeneratorQueue.put(( + "createRandomAddress", + 4, + stream, + label, + 1, + "", + shorten)) elif t == "2": set_background_title(d, "Make deterministic addresses") - r, t = d.passwordform("Enter passphrase", - [("Passphrase", 1, 1, "", 2, 1, 64, 128), - ("Confirm passphrase", 3, 1, "", 4, 1, 64, 128)], + 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) + 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", 1 if shorten else 0)]) + r, t = d.checklist( + "Miscellaneous options", + choices=[( + "1", + "Spend time shortening the address", + 1 if shorten else 0)]) if r == d.DIALOG_OK and "1" in t: shorten = True - scrollbox(d, 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))) - queues.addressGeneratorQueue.put(('createDeterministicAddresses', 4, stream, "unused deterministic address", number, str(passphrase), shorten)) + scrollbox( + d, 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))) + queues.addressGeneratorQueue.put( + ('createDeterministicAddresses', 4, stream, + "unused deterministic address", number, + str(passphrase), shorten)) else: scrollbox(d, unicode("Passphrases do not match")) - elif t == "2": # Send a message + elif t == "2": # Send a message a = "" - if addresses[addrcur][3] != 0: # if current address is a chan + 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 + elif t == "3": # Rename address label a = addresses[addrcur][2] label = addresses[addrcur][0] r, t = d.inputbox("New address label", init=label) @@ -495,72 +613,79 @@ def handlech(c, stdscr): # Write config BMConfigParser().save() addresses[addrcur][0] = label - elif t == "4": # Enable address + elif t == "4": # Enable address a = addresses[addrcur][2] - BMConfigParser().set(a, "enabled", "true") # Set config + BMConfigParser().set(a, "enabled", "true") # Set config # Write config BMConfigParser().save() # Change color if BMConfigParser().safeGetBoolean(a, 'chan'): - addresses[addrcur][3] = 9 # orange + addresses[addrcur][3] = 9 # orange elif BMConfigParser().safeGetBoolean(a, 'mailinglist'): - addresses[addrcur][3] = 5 # magenta + addresses[addrcur][3] = 5 # magenta else: - addresses[addrcur][3] = 0 # black + addresses[addrcur][3] = 0 # black addresses[addrcur][1] = True - shared.reloadMyAddressHashes() # Reload address hashes - elif t == "5": # Disable address + shared.reloadMyAddressHashes() # Reload address hashes + elif t == "5": # Disable address a = addresses[addrcur][2] - BMConfigParser().set(a, "enabled", "false") # Set config - addresses[addrcur][3] = 8 # Set color to gray + BMConfigParser().set(a, "enabled", "false") # Set config + addresses[addrcur][3] = 8 # Set color to gray # Write config BMConfigParser().save() addresses[addrcur][1] = False - shared.reloadMyAddressHashes() # Reload address hashes - elif t == "6": # Delete address + 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() - del addresses[addrcur] - elif t == "7": # Special address behavior + BMConfigParser().remove_section(addresses[addrcur][2]) + BMConfigParser().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"): - scrollbox(d, unicode("This is a chan address. You cannot use it as a pseudo-mailing list.")) + scrollbox(d, unicode( + "This is a chan address. You cannot use it as a pseudo-mailing list.")) else: m = BMConfigParser().safeGetBoolean(a, "mailinglist") - r, t = d.radiolist("Select address behavior", - choices=[("1", "Behave as a normal address", not m), + 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: + if t == "1" and m: BMConfigParser().set(a, "mailinglist", "false") if addresses[addrcur][1]: - addresses[addrcur][3] = 0 # Set color to black + addresses[addrcur][3] = 0 # Set color to black else: - addresses[addrcur][3] = 8 # Set color to gray - elif t == "2" and m == False: + addresses[addrcur][3] = 8 # Set color to gray + elif t == "2" and m is False: try: mn = BMConfigParser().get(a, "mailinglistname") except ConfigParser.NoOptionError: - mn = "" + 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) - addresses[addrcur][3] = 6 # Set color to magenta + addresses[addrcur][3] = 6 # Set color to magenta # Write config BMConfigParser().save() elif menutab == 5: set_background_title(d, "Subscriptions Dialog Box") if len(subscriptions) <= subcur: - r, t = d.menu("Do what with subscription to \""+subscriptions[subcur][0]+"\"?", - choices=[("1", "Add new subscription")]) + r, t = d.menu( + "Do what with subscription to \"" + subscriptions[subcur][0] + "\"?", + choices=[ + ("1", "Add new subscription")]) else: - r, t = d.menu("Do what with subscription to \""+subscriptions[subcur][0]+"\"?", - choices=[("1", "Add new subscription"), + 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")]) @@ -581,27 +706,39 @@ def handlech(c, stdscr): sqlExecute("INSERT INTO subscriptions VALUES (?,?,?)", label, addr, True) shared.reloadBroadcastSendersForWhichImWatching() elif t == "2": - r, t = d.inpuxbox("Type in \"I want to delete this subscription\"") + r, t = d.inputbox("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] + 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]) + 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]) + 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: set_background_title(d, "Address Book Dialog Box") if len(addrbook) <= abookcur: - r, t = d.menu("Do what with addressbook?", + r, t = d.menu( + "Do what with addressbook?", choices=[("3", "Add new address to Address Book")]) else: - r, t = d.menu("Do what with \""+addrbook[abookcur][0]+"\" : \""+addrbook[abookcur][1]+"\"", - choices=[("1", "Send a message to this address"), + 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")]) @@ -623,8 +760,8 @@ def handlech(c, stdscr): 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 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) # Prepend entry @@ -636,25 +773,39 @@ def handlech(c, stdscr): 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]) + sqlExecute( + "DELETE FROM addressbook WHERE label=? AND address=?", + addrbook[abookcur][0], + addrbook[abookcur][1]) del addrbook[abookcur] elif menutab == 7: set_background_title(d, "Blacklist Dialog Box") - r, t = d.menu("Do what with \""+blacklist[blackcur][0]+"\" : \""+blacklist[blackcur][1]+"\"?", - choices=[("1", "Delete"), + 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]) + 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]) + 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]) + 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: @@ -672,17 +823,17 @@ def handlech(c, stdscr): if menutab == 7 and blackcur > 0: blackcur -= 1 elif c == curses.KEY_DOWN: - if menutab == 1 and inboxcur < len(inbox)-1: + if menutab == 1 and inboxcur < len(inbox) - 1: inboxcur += 1 - if (menutab == 2 or menutab == 4) and addrcur < len(addresses)-1: + if (menutab == 2 or menutab == 4) and addrcur < len(addresses) - 1: addrcur += 1 - if menutab == 3 and sentcur < len(sentbox)-1: + if menutab == 3 and sentcur < len(sentbox) - 1: sentcur += 1 - if menutab == 5 and subcur < len(subscriptions)-1: + if menutab == 5 and subcur < len(subscriptions) - 1: subcur += 1 - if menutab == 6 and abookcur < len(addrbook)-1: + if menutab == 6 and abookcur < len(addrbook) - 1: abookcur += 1 - if menutab == 7 and blackcur < len(blacklist)-1: + if menutab == 7 and blackcur < len(blacklist) - 1: blackcur += 1 elif c == curses.KEY_HOME: if menutab == 1: @@ -699,38 +850,47 @@ def handlech(c, stdscr): blackcur = 0 elif c == curses.KEY_END: if menutab == 1: - inboxcur = len(inbox)-1 + inboxcur = len(inbox) - 1 if menutab == 2 or menutab == 4: - addrcur = len(addresses)-1 + addrcur = len(addresses) - 1 if menutab == 3: - sentcur = len(sentbox)-1 + sentcur = len(sentbox) - 1 if menutab == 5: - subcur = len(subscriptions)-1 + subcur = len(subscriptions) - 1 if menutab == 6: - abookcur = len(addrbook)-1 + abookcur = len(addrbook) - 1 if menutab == 7: - blackcur = len(blackcur)-1 + blackcur = len(blackcur) - 1 redraw(stdscr) + + +# pylint: disable=too-many-locals, too-many-arguments def sendMessage(sender="", recv="", broadcast=None, subject="", body="", reply=False): + """Method for message sending""" if sender == "": return d = Dialog(dialog="dialog") set_background_title(d, "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) + 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 + global menutab # pylint: disable=global-statement menutab = 6 return recv = t - if broadcast == None and sender != recv: - r, t = d.radiolist("How to send the message?", - choices=[("1", "Send to one or more specific people", 1), + if broadcast is None and sender != recv: + r, t = d.radiolist( + "How to send the message?", + choices=[ + ("1", "Send to one or more specific people", 1), ("2", "Broadcast to everyone who is subscribed to your address", 0)]) if r != d.DIALOG_OK: return broadcast = False - if t == "2": # Broadcast + if t == "2": # Broadcast broadcast = True if subject == "" or reply: r, t = d.inputbox("Message subject", width=60, init=subject) @@ -748,9 +908,10 @@ def sendMessage(sender="", recv="", broadcast=None, subject="", body="", reply=F recvlist = [] for i, item in enumerate(recv.replace(",", ";").split(";")): recvlist.append(item.strip()) - list(set(recvlist)) # Remove exact duplicates + list(set(recvlist)) # Remove exact duplicates for addr in recvlist: if addr != "": + # pylint: disable=redefined-outer-name status, version, stream, ripe = decodeAddress(addr) if status != "success": set_background_title(d, "Recipient address error") @@ -762,13 +923,17 @@ def sendMessage(sender="", recv="", broadcast=None, subject="", body="", reply=F 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." + 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." + 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." + err += ("Some data encoded in the address is too long. There might be something wrong with" + " the software of your acquaintance.") elif status == "varintmalformed": - err += "Some data encoded in the address is malformed. There might be something wrong with the software of your acquaintance." + err += ("Some data encoded in the address is malformed. There might be something wrong with" + " the software of your acquaintance.") else: err += "It is unknown what is wrong with the address." scrollbox(d, unicode(err)) @@ -776,17 +941,24 @@ def sendMessage(sender="", recv="", broadcast=None, subject="", body="", reply=F addr = addBMIfNotPresent(addr) if version > 4 or version <= 1: set_background_title(d, "Recipient address error") - scrollbox(d, unicode("Could not understand version number " + version + "of address" + addr + ".")) + scrollbox(d, unicode( + "Could not understand version number " + + version + + "of address" + + addr + + ".")) continue if stream > 1 or stream == 0: set_background_title(d, "Recipient address error") - scrollbox(d, unicode("Bitmessage currently only supports stream numbers of 1, unlike as requested for address " + addr + ".")) + scrollbox(d, unicode( + "Bitmessage currently only supports stream numbers of 1," + "unlike as requested for address " + addr + ".")) continue if not network.stats.connectedHostsList(): set_background_title(d, "Not connected warning") scrollbox(d, unicode("Because you are not currently connected to the network, ")) stealthLevel = BMConfigParser().safeGetInt('bitmessagesettings', 'ackstealthlevel') - ackdata = genAckPayload(streamNumber, stealthLevel) + ackdata = genAckPayload(decodeAddress(addr)[2], stealthLevel) sqlExecute( "INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", "", @@ -796,22 +968,22 @@ def sendMessage(sender="", recv="", broadcast=None, subject="", body="", reply=F subject, body, ackdata, - int(time.time()), # sentTime (this will never change) - int(time.time()), # lastActionTime - 0, # sleepTill time. This will get set when the POW gets done. + int(time.time()), # sentTime (this will never change) + int(time.time()), # lastActionTime + 0, # sleepTill time. This will get set when the POW gets done. "msgqueued", - 0, # retryNumber + 0, # retryNumber "sent", - 2, # encodingType + 2, # encodingType BMConfigParser().getint('bitmessagesettings', 'ttl')) queues.workerQueue.put(("sendmessage", addr)) - else: # Broadcast + else: # Broadcast if recv == "": set_background_title(d, "Empty sender error") scrollbox(d, unicode("You must specify an address to send the message from.")) else: # dummy ackdata, no need for stealth - ackdata = genAckPayload(streamNumber, 0) + ackdata = genAckPayload(decodeAddress(addr)[2], 0) recv = BROADCAST_STR ripe = "" sqlExecute( @@ -823,21 +995,24 @@ def sendMessage(sender="", recv="", broadcast=None, subject="", body="", reply=F subject, body, ackdata, - int(time.time()), # sentTime (this will never change) - int(time.time()), # lastActionTime - 0, # sleepTill time. This will get set when the POW gets done. + int(time.time()), # sentTime (this will never change) + int(time.time()), # lastActionTime + 0, # sleepTill time. This will get set when the POW gets done. "broadcastqueued", - 0, # retryNumber - "sent", # folder - 2, # encodingType + 0, # retryNumber + "sent", # folder + 2, # encodingType BMConfigParser().getint('bitmessagesettings', 'ttl')) queues.workerQueue.put(('sendbroadcast', '')) + +# pylint: disable=redefined-outer-name, too-many-locals def loadInbox(): + """Load the list of messages""" sys.stdout = sys.__stdout__ - print("Loading inbox messages...") + print "Loading inbox messages..." sys.stdout = printlog - + where = "toaddress || fromaddress || subject || message" what = "%%" ret = sqlQuery("""SELECT msgid, toaddress, fromaddress, subject, received, read @@ -847,7 +1022,7 @@ def loadInbox(): 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: @@ -859,17 +1034,17 @@ def loadInbox(): if tolabel == "": tolabel = toaddr tolabel = shared.fixPotentiallyInvalidUTF8Data(tolabel) - + # Set label for from address fromlabel = "" if BMConfigParser().has_section(fromaddr): fromlabel = BMConfigParser().get(fromaddr, "label") - if fromlabel == "": # Check Address Book + 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 + if fromlabel == "": # Check Subscriptions qr = sqlQuery("SELECT label FROM subscriptions WHERE address=?", fromaddr) if qr != []: for r in qr: @@ -877,16 +1052,19 @@ def loadInbox(): if fromlabel == "": fromlabel = fromaddr fromlabel = shared.fixPotentiallyInvalidUTF8Data(fromlabel) - + # Load into array - inbox.append([msgid, tolabel, toaddr, fromlabel, fromaddr, subject, - l10n.formatTimestamp(received, False), read]) + inbox.append([msgid, tolabel, toaddr, fromlabel, fromaddr, subject, l10n.formatTimestamp( + received, False), read]) inbox.reverse() + + def loadSent(): + """Load the messages that sent""" sys.stdout = sys.__stdout__ - print("Loading sent messages...") + print "Loading sent messages..." sys.stdout = printlog - + where = "toaddress || fromaddress || subject || message" what = "%%" ret = sqlQuery("""SELECT toaddress, fromaddress, subject, status, ackdata, lastactiontime @@ -896,7 +1074,7 @@ def loadSent(): 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) @@ -913,14 +1091,14 @@ def loadSent(): tolabel = BMConfigParser().get(toaddr, "label") if tolabel == "": tolabel = toaddr - + # Set label for from address fromlabel = "" if BMConfigParser().has_section(fromaddr): fromlabel = BMConfigParser().get(fromaddr, "label") if fromlabel == "": fromlabel = fromaddr - + # Set status string if status == "awaitingpubkey": statstr = "Waiting for their public key. Will request it again soon" @@ -930,20 +1108,20 @@ def loadSent(): statstr = "Message queued" elif status == "msgsent": t = l10n.formatTimestamp(lastactiontime, False) - statstr = "Message sent at "+t+".Waiting for acknowledgement." + statstr = "Message sent at " + t + ".Waiting for acknowledgement." elif status == "msgsentnoackexpected": t = l10n.formatTimestamp(lastactiontime, False) - statstr = "Message sent at "+t+"." + statstr = "Message sent at " + t + "." elif status == "doingmsgpow": statstr = "The proof of work required to send the message has been queued." elif status == "ackreceived": t = l10n.formatTimestamp(lastactiontime, False) - statstr = "Acknowledgment of the message received at "+t+"." + statstr = "Acknowledgment of the message received at " + t + "." elif status == "broadcastqueued": statstr = "Broadcast queued." elif status == "broadcastsent": t = l10n.formatTimestamp(lastactiontime, False) - statstr = "Broadcast sent at "+t+"." + statstr = "Broadcast sent at " + t + "." elif status == "forcepow": statstr = "Forced difficulty override. Message will start sending soon." elif status == "badkey": @@ -952,31 +1130,47 @@ def loadSent(): statstr = "Error: The work demanded by the recipient is more difficult than you are willing to do." else: t = l10n.formatTimestamp(lastactiontime, False) - statstr = "Unknown status "+status+" at "+t+"." - + statstr = "Unknown status " + status + " at " + t + "." + # Load into array - sentbox.append([tolabel, toaddr, fromlabel, fromaddr, subject, statstr, ackdata, + sentbox.append([ + tolabel, + toaddr, + fromlabel, + fromaddr, + subject, + statstr, + ackdata, l10n.formatTimestamp(lastactiontime, False)]) sentbox.reverse() + + def loadAddrBook(): + """Load address book""" sys.stdout = sys.__stdout__ - print("Loading address book...") + print "Loading address book..." sys.stdout = printlog - + ret = sqlQuery("SELECT label, address FROM addressbook") for row in ret: label, addr = row label = shared.fixPotentiallyInvalidUTF8Data(label) addrbook.append([label, addr]) addrbook.reverse() + + def loadSubscriptions(): + """Load subscription functionality""" 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 + """load black/white list""" + global bwtype # pylint: disable=global-statement bwtype = BMConfigParser().get("bitmessagesettings", "blackwhitelist") if bwtype == "black": ret = sqlQuery("SELECT label, address, enabled FROM blacklist") @@ -987,51 +1181,53 @@ def loadBlackWhiteList(): blacklist.append([label, address, enabled]) blacklist.reverse() + def runwrapper(): sys.stdout = printlog - #sys.stderr = errlog - + # sys.stderr = errlog + # Load messages from database loadInbox() loadSent() loadAddrBook() loadSubscriptions() loadBlackWhiteList() - + stdscr = curses.initscr() - - global logpad + + global logpad # pylint: disable=global-statement logpad = curses.newpad(1024, curses.COLS) - + stdscr.nodelay(0) curses.curs_set(0) stdscr.timeout(1000) - + curses.wrapper(run) doShutdown() + def run(stdscr): # 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 + 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_color(8, 500, 500, 500) # gray curses.init_pair(8, 8, 0) - curses.init_color(9, 844, 465, 0) # orange + curses.init_color(9, 844, 465, 0) # orange curses.init_pair(9, 9, 0) else: - curses.init_pair(8, curses.COLOR_WHITE, curses.COLOR_BLACK) # grayish - curses.init_pair(9, curses.COLOR_YELLOW, curses.COLOR_BLACK) # orangish - + 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 = BMConfigParser().addresses() for addressInKeysFile in configSections: @@ -1039,27 +1235,29 @@ def run(stdscr): addresses.append([BMConfigParser().get(addressInKeysFile, "label"), isEnabled, addressInKeysFile]) # Set address color if not isEnabled: - addresses[len(addresses)-1].append(8) # gray + addresses[len(addresses) - 1].append(8) # gray elif BMConfigParser().safeGetBoolean(addressInKeysFile, 'chan'): - addresses[len(addresses)-1].append(9) # orange + addresses[len(addresses) - 1].append(9) # orange elif BMConfigParser().safeGetBoolean(addressInKeysFile, 'mailinglist'): - addresses[len(addresses)-1].append(5) # magenta + addresses[len(addresses) - 1].append(5) # magenta else: - addresses[len(addresses)-1].append(0) # black + addresses[len(addresses) - 1].append(0) # black addresses.reverse() - + stdscr.clear() redraw(stdscr) - while quit == False: + while quit is False: drawtab(stdscr) handlech(stdscr.getch(), stdscr) + def doShutdown(): + """Shutting the app down""" sys.stdout = sys.__stdout__ - print("Shutting down...") + print "Shutting down..." sys.stdout = printlog shutdown.doCleanShutdown() sys.stdout = sys.__stdout__ sys.stderr = sys.__stderr__ - - os._exit(0) + + os._exit(0) # pylint: disable=protected-access diff --git a/src/bitmessagemain.py b/src/bitmessagemain.py index c8f2d445..6aafaeff 100755 --- a/src/bitmessagemain.py +++ b/src/bitmessagemain.py @@ -194,7 +194,8 @@ class Main: from plugins.plugin import get_plugin try: proxyconfig_start = time.time() - get_plugin('proxyconfig', name=proxy_type)(config) + if not get_plugin('proxyconfig', name=proxy_type)(config): + raise TypeError except TypeError: logger.error( 'Failed to run proxy config plugin %s', @@ -418,7 +419,7 @@ class Main: self.stop() elif not state.enableGUI: from tests import core as test_core # pylint: disable=relative-import - test_core_result = test_core.run() + test_core_result = test_core.run(self) state.enableGUI = True self.stop() test_core.cleanup() diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 2c5f1485..440d36b2 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -23,7 +23,6 @@ from addresses import decodeAddress, addBMIfNotPresent import shared from bitmessageui import Ui_MainWindow from bmconfigparser import BMConfigParser -import defaults import namecoin from messageview import MessageView from migrationwizard import Ui_MigrationWizard @@ -31,15 +30,12 @@ from foldertree import ( AccountMixin, Ui_FolderWidget, Ui_AddressWidget, Ui_SubscriptionWidget, MessageList_AddressWidget, MessageList_SubjectWidget, Ui_AddressBookWidgetItemLabel, Ui_AddressBookWidgetItemAddress) -from settings import Ui_settingsDialog import settingsmixin import support -import debug from helper_ackPayload import genAckPayload from helper_sql import sqlQuery, sqlExecute, sqlExecuteChunked, sqlStoredProcedure import helper_search import l10n -import openclpow from utils import str_broadcast_subscribers, avatarize from account import ( getSortedAccounts, getSortedSubscriptions, accountClass, BMAccount, @@ -47,16 +43,15 @@ from account import ( import dialogs from network.stats import pendingDownload, pendingUpload from uisignaler import UISignaler -import knownnodes import paths from proofofwork import getPowType import queues import shutdown import state from statusbar import BMStatusBar -from network.asyncore_pollchoose import set_rates import sound - +# This is needed for tray icon +import bitmessage_icons_rc # noqa:F401 pylint: disable=unused-import try: from plugins.plugin import get_plugin, get_plugins @@ -64,49 +59,6 @@ except ImportError: get_plugins = False -def change_translation(newlocale): - global qmytranslator, qsystranslator - try: - if not qmytranslator.isEmpty(): - QtGui.QApplication.removeTranslator(qmytranslator) - except: - pass - try: - if not qsystranslator.isEmpty(): - QtGui.QApplication.removeTranslator(qsystranslator) - except: - pass - - qmytranslator = QtCore.QTranslator() - translationpath = os.path.join (paths.codePath(), 'translations', 'bitmessage_' + newlocale) - qmytranslator.load(translationpath) - QtGui.QApplication.installTranslator(qmytranslator) - - qsystranslator = QtCore.QTranslator() - if paths.frozen: - translationpath = os.path.join (paths.codePath(), 'translations', 'qt_' + newlocale) - else: - translationpath = os.path.join (str(QtCore.QLibraryInfo.location(QtCore.QLibraryInfo.TranslationsPath)), 'qt_' + newlocale) - qsystranslator.load(translationpath) - QtGui.QApplication.installTranslator(qsystranslator) - - lang = locale.normalize(l10n.getTranslationLanguage()) - langs = [lang.split(".")[0] + "." + l10n.encoding, lang.split(".")[0] + "." + 'UTF-8', lang] - if 'win32' in sys.platform or 'win64' in sys.platform: - langs = [l10n.getWindowsLocale(lang)] - for lang in langs: - try: - l10n.setlocale(locale.LC_ALL, lang) - if 'win32' not in sys.platform and 'win64' not in sys.platform: - l10n.encoding = locale.nl_langinfo(locale.CODESET) - else: - l10n.encoding = locale.getlocale()[1] - logger.info("Successfully set locale to %s", lang) - break - except: - logger.error("Failed to set locale to %s", lang, exc_info=True) - - # TODO: rewrite def powQueueSize(): """Returns the size of queues.workerQueue including current unfinished work""" @@ -122,9 +74,6 @@ def powQueueSize(): class MyForm(settingsmixin.SMainWindow): - # the last time that a message arrival sound was played - lastSoundTime = datetime.now() - timedelta(days=1) - # the maximum frequency of message sounds in seconds maxSoundFrequencySec = 60 @@ -132,6 +81,58 @@ class MyForm(settingsmixin.SMainWindow): REPLY_TYPE_CHAN = 1 REPLY_TYPE_UPD = 2 + def change_translation(self, newlocale=None): + """Change translation language for the application""" + if newlocale is None: + newlocale = l10n.getTranslationLanguage() + try: + if not self.qmytranslator.isEmpty(): + QtGui.QApplication.removeTranslator(self.qmytranslator) + except: + pass + try: + if not self.qsystranslator.isEmpty(): + QtGui.QApplication.removeTranslator(self.qsystranslator) + except: + pass + + self.qmytranslator = QtCore.QTranslator() + translationpath = os.path.join( + paths.codePath(), 'translations', 'bitmessage_' + newlocale) + self.qmytranslator.load(translationpath) + QtGui.QApplication.installTranslator(self.qmytranslator) + + self.qsystranslator = QtCore.QTranslator() + if paths.frozen: + translationpath = os.path.join( + paths.codePath(), 'translations', 'qt_' + newlocale) + else: + translationpath = os.path.join( + str(QtCore.QLibraryInfo.location( + QtCore.QLibraryInfo.TranslationsPath)), 'qt_' + newlocale) + self.qsystranslator.load(translationpath) + QtGui.QApplication.installTranslator(self.qsystranslator) + + lang = locale.normalize(l10n.getTranslationLanguage()) + langs = [ + lang.split(".")[0] + "." + l10n.encoding, + lang.split(".")[0] + "." + 'UTF-8', + lang + ] + if 'win32' in sys.platform or 'win64' in sys.platform: + langs = [l10n.getWindowsLocale(lang)] + for lang in langs: + try: + l10n.setlocale(locale.LC_ALL, lang) + if 'win32' not in sys.platform and 'win64' not in sys.platform: + l10n.encoding = locale.nl_langinfo(locale.CODESET) + else: + l10n.encoding = locale.getlocale()[1] + logger.info("Successfully set locale to %s", lang) + break + except: + logger.error("Failed to set locale to %s", lang, exc_info=True) + def init_file_menu(self): QtCore.QObject.connect(self.ui.actionExit, QtCore.SIGNAL( "triggered()"), self.quit) @@ -605,6 +606,13 @@ class MyForm(settingsmixin.SMainWindow): self.ui = Ui_MainWindow() self.ui.setupUi(self) + self.qmytranslator = self.qsystranslator = None + self.indicatorUpdate = None + self.actionStatus = None + + # the last time that a message arrival sound was played + self.lastSoundTime = datetime.now() - timedelta(days=1) + # Ask the user if we may delete their old version 1 addresses if they # have any. for addressInKeysFile in getSortedAccounts(): @@ -620,26 +628,13 @@ class MyForm(settingsmixin.SMainWindow): BMConfigParser().remove_section(addressInKeysFile) BMConfigParser().save() - # Configure Bitmessage to start on startup (or remove the - # configuration) based on the setting in the keys.dat file - if 'win32' in sys.platform or 'win64' in sys.platform: - # Auto-startup for Windows - RUN_PATH = "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run" - self.settings = QtCore.QSettings(RUN_PATH, QtCore.QSettings.NativeFormat) - self.settings.remove( - "PyBitmessage") # In case the user moves the program and the registry entry is no longer valid, this will delete the old registry entry. - if BMConfigParser().getboolean('bitmessagesettings', 'startonlogon'): - self.settings.setValue("PyBitmessage", sys.argv[0]) - elif 'darwin' in sys.platform: - # startup for mac - pass - elif 'linux' in sys.platform: - # startup for linux - pass + self.updateStartOnLogon() + + self.change_translation() # e.g. for editing labels self.recurDepth = 0 - + # switch back to this when replying self.replyFromTab = None @@ -786,6 +781,9 @@ class MyForm(settingsmixin.SMainWindow): self.ui.treeWidgetSubscriptions.keyPressEvent = self.treeWidgetKeyPressEvent self.ui.treeWidgetChans.keyPressEvent = self.treeWidgetKeyPressEvent + # Key press in addressbook + self.ui.tableWidgetAddressBook.keyPressEvent = self.addressbookKeyPressEvent + # Key press in messagelist self.ui.tableWidgetInbox.keyPressEvent = self.tableWidgetKeyPressEvent self.ui.tableWidgetInboxSubscriptions.keyPressEvent = self.tableWidgetKeyPressEvent @@ -828,6 +826,28 @@ class MyForm(settingsmixin.SMainWindow): finally: self._contact_selected = None + def updateStartOnLogon(self): + # Configure Bitmessage to start on startup (or remove the + # configuration) based on the setting in the keys.dat file + if 'win32' in sys.platform or 'win64' in sys.platform: + # Auto-startup for Windows + RUN_PATH = "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run" + self.settings = QtCore.QSettings( + RUN_PATH, QtCore.QSettings.NativeFormat) + # In case the user moves the program and the registry entry is + # no longer valid, this will delete the old registry entry. + self.settings.remove("PyBitmessage") + if BMConfigParser().getboolean( + 'bitmessagesettings', 'startonlogon' + ): + self.settings.setValue("PyBitmessage", sys.argv[0]) + elif 'darwin' in sys.platform: + # startup for mac + pass + elif 'linux' in sys.platform: + # startup for linux + pass + def updateTTL(self, sliderPosition): TTL = int(sliderPosition ** 3.199 + 3600) self.updateHumanFriendlyTTLDescription(TTL) @@ -1433,6 +1453,15 @@ class MyForm(settingsmixin.SMainWindow): def treeWidgetKeyPressEvent(self, event): return self.handleKeyPress(event, self.getCurrentTreeWidget()) + # addressbook + def addressbookKeyPressEvent(self, event): + """Handle keypress event in addressbook widget""" + if event.key() == QtCore.Qt.Key_Delete: + self.on_action_AddressBookDelete() + else: + return QtGui.QTableWidget.keyPressEvent( + self.ui.tableWidgetAddressBook, event) + # inbox / sent def tableWidgetKeyPressEvent(self, event): return self.handleKeyPress(event, self.getCurrentMessagelist()) @@ -1441,11 +1470,12 @@ class MyForm(settingsmixin.SMainWindow): def textEditKeyPressEvent(self, event): return self.handleKeyPress(event, self.getCurrentMessageTextedit()) - def handleKeyPress(self, event, focus = None): + def handleKeyPress(self, event, focus=None): + """This method handles keypress events for all widgets on MyForm""" messagelist = self.getCurrentMessagelist() folder = self.getCurrentFolder() if event.key() == QtCore.Qt.Key_Delete: - if isinstance (focus, MessageView) or isinstance(focus, QtGui.QTableWidget): + if isinstance(focus, MessageView) or isinstance(focus, QtGui.QTableWidget): if folder == "sent": self.on_action_SentTrash() else: @@ -1481,17 +1511,17 @@ class MyForm(settingsmixin.SMainWindow): self.ui.lineEditTo.setFocus() event.ignore() elif event.key() == QtCore.Qt.Key_F: - searchline = self.getCurrentSearchLine(retObj = True) + searchline = self.getCurrentSearchLine(retObj=True) if searchline: searchline.setFocus() event.ignore() if not event.isAccepted(): return - if isinstance (focus, MessageView): + if isinstance(focus, MessageView): return MessageView.keyPressEvent(focus, event) - elif isinstance (focus, QtGui.QTableWidget): + elif isinstance(focus, QtGui.QTableWidget): return QtGui.QTableWidget.keyPressEvent(focus, event) - elif isinstance (focus, QtGui.QTreeWidget): + elif isinstance(focus, QtGui.QTreeWidget): return QtGui.QTreeWidget.keyPressEvent(focus, event) # menu button 'manage keys' @@ -1622,7 +1652,6 @@ class MyForm(settingsmixin.SMainWindow): # The window state has just been changed to # Normal/Maximised/FullScreen pass - # QtGui.QWidget.changeEvent(self, event) def __icon_activated(self, reason): if reason == QtGui.QSystemTrayIcon.Trigger: @@ -2434,225 +2463,7 @@ class MyForm(settingsmixin.SMainWindow): dialogs.AboutDialog(self).exec_() def click_actionSettings(self): - self.settingsDialogInstance = settingsDialog(self) - if self._firstrun: - self.settingsDialogInstance.ui.tabWidgetSettings.setCurrentIndex(1) - if self.settingsDialogInstance.exec_(): - if self._firstrun: - BMConfigParser().remove_option( - 'bitmessagesettings', 'dontconnect') - BMConfigParser().set('bitmessagesettings', 'startonlogon', str( - self.settingsDialogInstance.ui.checkBoxStartOnLogon.isChecked())) - BMConfigParser().set('bitmessagesettings', 'minimizetotray', str( - self.settingsDialogInstance.ui.checkBoxMinimizeToTray.isChecked())) - BMConfigParser().set('bitmessagesettings', 'trayonclose', str( - self.settingsDialogInstance.ui.checkBoxTrayOnClose.isChecked())) - BMConfigParser().set('bitmessagesettings', 'hidetrayconnectionnotifications', str( - self.settingsDialogInstance.ui.checkBoxHideTrayConnectionNotifications.isChecked())) - BMConfigParser().set('bitmessagesettings', 'showtraynotifications', str( - self.settingsDialogInstance.ui.checkBoxShowTrayNotifications.isChecked())) - BMConfigParser().set('bitmessagesettings', 'startintray', str( - self.settingsDialogInstance.ui.checkBoxStartInTray.isChecked())) - BMConfigParser().set('bitmessagesettings', 'willinglysendtomobile', str( - self.settingsDialogInstance.ui.checkBoxWillinglySendToMobile.isChecked())) - BMConfigParser().set('bitmessagesettings', 'useidenticons', str( - self.settingsDialogInstance.ui.checkBoxUseIdenticons.isChecked())) - BMConfigParser().set('bitmessagesettings', 'replybelow', str( - self.settingsDialogInstance.ui.checkBoxReplyBelow.isChecked())) - - lang = str(self.settingsDialogInstance.ui.languageComboBox.itemData(self.settingsDialogInstance.ui.languageComboBox.currentIndex()).toString()) - BMConfigParser().set('bitmessagesettings', 'userlocale', lang) - change_translation(l10n.getTranslationLanguage()) - - if int(BMConfigParser().get('bitmessagesettings', 'port')) != int(self.settingsDialogInstance.ui.lineEditTCPPort.text()): - if not BMConfigParser().safeGetBoolean('bitmessagesettings', 'dontconnect'): - QtGui.QMessageBox.about(self, _translate("MainWindow", "Restart"), _translate( - "MainWindow", "You must restart Bitmessage for the port number change to take effect.")) - BMConfigParser().set('bitmessagesettings', 'port', str( - self.settingsDialogInstance.ui.lineEditTCPPort.text())) - if self.settingsDialogInstance.ui.checkBoxUPnP.isChecked() != BMConfigParser().safeGetBoolean('bitmessagesettings', 'upnp'): - BMConfigParser().set('bitmessagesettings', 'upnp', str(self.settingsDialogInstance.ui.checkBoxUPnP.isChecked())) - if self.settingsDialogInstance.ui.checkBoxUPnP.isChecked(): - import upnp - upnpThread = upnp.uPnPThread() - upnpThread.start() - #print 'self.settingsDialogInstance.ui.comboBoxProxyType.currentText()', self.settingsDialogInstance.ui.comboBoxProxyType.currentText() - #print 'self.settingsDialogInstance.ui.comboBoxProxyType.currentText())[0:5]', self.settingsDialogInstance.ui.comboBoxProxyType.currentText()[0:5] - if BMConfigParser().get('bitmessagesettings', 'socksproxytype') == 'none' and self.settingsDialogInstance.ui.comboBoxProxyType.currentText()[0:5] == 'SOCKS': - if shared.statusIconColor != 'red': - QtGui.QMessageBox.about(self, _translate("MainWindow", "Restart"), _translate( - "MainWindow", "Bitmessage will use your proxy from now on but you may want to manually restart Bitmessage now to close existing connections (if any).")) - if BMConfigParser().get('bitmessagesettings', 'socksproxytype')[0:5] == 'SOCKS' and self.settingsDialogInstance.ui.comboBoxProxyType.currentText()[0:5] != 'SOCKS': - self.statusbar.clearMessage() - state.resetNetworkProtocolAvailability() # just in case we changed something in the network connectivity - if self.settingsDialogInstance.ui.comboBoxProxyType.currentText()[0:5] == 'SOCKS': - BMConfigParser().set('bitmessagesettings', 'socksproxytype', str( - self.settingsDialogInstance.ui.comboBoxProxyType.currentText())) - else: - BMConfigParser().set('bitmessagesettings', 'socksproxytype', 'none') - BMConfigParser().set('bitmessagesettings', 'socksauthentication', str( - self.settingsDialogInstance.ui.checkBoxAuthentication.isChecked())) - BMConfigParser().set('bitmessagesettings', 'sockshostname', str( - self.settingsDialogInstance.ui.lineEditSocksHostname.text())) - BMConfigParser().set('bitmessagesettings', 'socksport', str( - self.settingsDialogInstance.ui.lineEditSocksPort.text())) - BMConfigParser().set('bitmessagesettings', 'socksusername', str( - self.settingsDialogInstance.ui.lineEditSocksUsername.text())) - BMConfigParser().set('bitmessagesettings', 'sockspassword', str( - self.settingsDialogInstance.ui.lineEditSocksPassword.text())) - BMConfigParser().set('bitmessagesettings', 'sockslisten', str( - self.settingsDialogInstance.ui.checkBoxSocksListen.isChecked())) - try: - # Rounding to integers just for aesthetics - BMConfigParser().set('bitmessagesettings', 'maxdownloadrate', str( - int(float(self.settingsDialogInstance.ui.lineEditMaxDownloadRate.text())))) - BMConfigParser().set('bitmessagesettings', 'maxuploadrate', str( - int(float(self.settingsDialogInstance.ui.lineEditMaxUploadRate.text())))) - except ValueError: - QtGui.QMessageBox.about(self, _translate("MainWindow", "Number needed"), _translate( - "MainWindow", "Your maximum download and upload rate must be numbers. Ignoring what you typed.")) - else: - set_rates(BMConfigParser().safeGetInt("bitmessagesettings", "maxdownloadrate"), - BMConfigParser().safeGetInt("bitmessagesettings", "maxuploadrate")) - - BMConfigParser().set('bitmessagesettings', 'maxoutboundconnections', str( - int(float(self.settingsDialogInstance.ui.lineEditMaxOutboundConnections.text())))) - - BMConfigParser().set('bitmessagesettings', 'namecoinrpctype', - self.settingsDialogInstance.getNamecoinType()) - BMConfigParser().set('bitmessagesettings', 'namecoinrpchost', str( - self.settingsDialogInstance.ui.lineEditNamecoinHost.text())) - BMConfigParser().set('bitmessagesettings', 'namecoinrpcport', str( - self.settingsDialogInstance.ui.lineEditNamecoinPort.text())) - BMConfigParser().set('bitmessagesettings', 'namecoinrpcuser', str( - self.settingsDialogInstance.ui.lineEditNamecoinUser.text())) - BMConfigParser().set('bitmessagesettings', 'namecoinrpcpassword', str( - self.settingsDialogInstance.ui.lineEditNamecoinPassword.text())) - self.resetNamecoinConnection() - - # Demanded difficulty tab - if float(self.settingsDialogInstance.ui.lineEditTotalDifficulty.text()) >= 1: - BMConfigParser().set('bitmessagesettings', 'defaultnoncetrialsperbyte', str(int(float( - self.settingsDialogInstance.ui.lineEditTotalDifficulty.text()) * defaults.networkDefaultProofOfWorkNonceTrialsPerByte))) - if float(self.settingsDialogInstance.ui.lineEditSmallMessageDifficulty.text()) >= 1: - BMConfigParser().set('bitmessagesettings', 'defaultpayloadlengthextrabytes', str(int(float( - self.settingsDialogInstance.ui.lineEditSmallMessageDifficulty.text()) * defaults.networkDefaultPayloadLengthExtraBytes))) - - if self.settingsDialogInstance.ui.comboBoxOpenCL.currentText().toUtf8() != BMConfigParser().safeGet("bitmessagesettings", "opencl"): - BMConfigParser().set('bitmessagesettings', 'opencl', str(self.settingsDialogInstance.ui.comboBoxOpenCL.currentText())) - queues.workerQueue.put(('resetPoW', '')) - - acceptableDifficultyChanged = False - - if float(self.settingsDialogInstance.ui.lineEditMaxAcceptableTotalDifficulty.text()) >= 1 or float(self.settingsDialogInstance.ui.lineEditMaxAcceptableTotalDifficulty.text()) == 0: - if BMConfigParser().get('bitmessagesettings','maxacceptablenoncetrialsperbyte') != str(int(float( - self.settingsDialogInstance.ui.lineEditMaxAcceptableTotalDifficulty.text()) * defaults.networkDefaultProofOfWorkNonceTrialsPerByte)): - # the user changed the max acceptable total difficulty - acceptableDifficultyChanged = True - BMConfigParser().set('bitmessagesettings', 'maxacceptablenoncetrialsperbyte', str(int(float( - self.settingsDialogInstance.ui.lineEditMaxAcceptableTotalDifficulty.text()) * defaults.networkDefaultProofOfWorkNonceTrialsPerByte))) - if float(self.settingsDialogInstance.ui.lineEditMaxAcceptableSmallMessageDifficulty.text()) >= 1 or float(self.settingsDialogInstance.ui.lineEditMaxAcceptableSmallMessageDifficulty.text()) == 0: - if BMConfigParser().get('bitmessagesettings','maxacceptablepayloadlengthextrabytes') != str(int(float( - self.settingsDialogInstance.ui.lineEditMaxAcceptableSmallMessageDifficulty.text()) * defaults.networkDefaultPayloadLengthExtraBytes)): - # the user changed the max acceptable small message difficulty - acceptableDifficultyChanged = True - BMConfigParser().set('bitmessagesettings', 'maxacceptablepayloadlengthextrabytes', str(int(float( - self.settingsDialogInstance.ui.lineEditMaxAcceptableSmallMessageDifficulty.text()) * defaults.networkDefaultPayloadLengthExtraBytes))) - if acceptableDifficultyChanged: - # It might now be possible to send msgs which were previously marked as toodifficult. - # Let us change them to 'msgqueued'. The singleWorker will try to send them and will again - # mark them as toodifficult if the receiver's required difficulty is still higher than - # we are willing to do. - sqlExecute('''UPDATE sent SET status='msgqueued' WHERE status='toodifficult' ''') - queues.workerQueue.put(('sendmessage', '')) - - #start:UI setting to stop trying to send messages after X days/months - # I'm open to changing this UI to something else if someone has a better idea. - if ((self.settingsDialogInstance.ui.lineEditDays.text()=='') and (self.settingsDialogInstance.ui.lineEditMonths.text()=='')):#We need to handle this special case. Bitmessage has its default behavior. The input is blank/blank - BMConfigParser().set('bitmessagesettings', 'stopresendingafterxdays', '') - BMConfigParser().set('bitmessagesettings', 'stopresendingafterxmonths', '') - shared.maximumLengthOfTimeToBotherResendingMessages = float('inf') - try: - float(self.settingsDialogInstance.ui.lineEditDays.text()) - lineEditDaysIsValidFloat = True - except: - lineEditDaysIsValidFloat = False - try: - float(self.settingsDialogInstance.ui.lineEditMonths.text()) - lineEditMonthsIsValidFloat = True - except: - lineEditMonthsIsValidFloat = False - if lineEditDaysIsValidFloat and not lineEditMonthsIsValidFloat: - self.settingsDialogInstance.ui.lineEditMonths.setText("0") - if lineEditMonthsIsValidFloat and not lineEditDaysIsValidFloat: - self.settingsDialogInstance.ui.lineEditDays.setText("0") - if lineEditDaysIsValidFloat or lineEditMonthsIsValidFloat: - if (float(self.settingsDialogInstance.ui.lineEditDays.text()) >=0 and float(self.settingsDialogInstance.ui.lineEditMonths.text()) >=0): - shared.maximumLengthOfTimeToBotherResendingMessages = (float(str(self.settingsDialogInstance.ui.lineEditDays.text())) * 24 * 60 * 60) + (float(str(self.settingsDialogInstance.ui.lineEditMonths.text())) * (60 * 60 * 24 *365)/12) - if shared.maximumLengthOfTimeToBotherResendingMessages < 432000: # If the time period is less than 5 hours, we give zero values to all fields. No message will be sent again. - QtGui.QMessageBox.about(self, _translate("MainWindow", "Will not resend ever"), _translate( - "MainWindow", "Note that the time limit you entered is less than the amount of time Bitmessage waits for the first resend attempt therefore your messages will never be resent.")) - BMConfigParser().set('bitmessagesettings', 'stopresendingafterxdays', '0') - BMConfigParser().set('bitmessagesettings', 'stopresendingafterxmonths', '0') - shared.maximumLengthOfTimeToBotherResendingMessages = 0 - else: - BMConfigParser().set('bitmessagesettings', 'stopresendingafterxdays', str(float( - self.settingsDialogInstance.ui.lineEditDays.text()))) - BMConfigParser().set('bitmessagesettings', 'stopresendingafterxmonths', str(float( - self.settingsDialogInstance.ui.lineEditMonths.text()))) - - BMConfigParser().save() - - if 'win32' in sys.platform or 'win64' in sys.platform: - # Auto-startup for Windows - RUN_PATH = "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run" - self.settings = QtCore.QSettings(RUN_PATH, QtCore.QSettings.NativeFormat) - if BMConfigParser().getboolean('bitmessagesettings', 'startonlogon'): - self.settings.setValue("PyBitmessage", sys.argv[0]) - else: - self.settings.remove("PyBitmessage") - elif 'darwin' in sys.platform: - # startup for mac - pass - elif 'linux' in sys.platform: - # startup for linux - pass - - if state.appdata != paths.lookupExeFolder() and self.settingsDialogInstance.ui.checkBoxPortableMode.isChecked(): # If we are NOT using portable mode now but the user selected that we should... - # Write the keys.dat file to disk in the new location - sqlStoredProcedure('movemessagstoprog') - with open(paths.lookupExeFolder() + 'keys.dat', 'wb') as configfile: - BMConfigParser().write(configfile) - # Write the knownnodes.dat file to disk in the new location - knownnodes.saveKnownNodes(paths.lookupExeFolder()) - os.remove(state.appdata + 'keys.dat') - os.remove(state.appdata + 'knownnodes.dat') - previousAppdataLocation = state.appdata - state.appdata = paths.lookupExeFolder() - debug.resetLogging() - try: - os.remove(previousAppdataLocation + 'debug.log') - os.remove(previousAppdataLocation + 'debug.log.1') - except: - pass - - if state.appdata == paths.lookupExeFolder() and not self.settingsDialogInstance.ui.checkBoxPortableMode.isChecked(): # If we ARE using portable mode now but the user selected that we shouldn't... - state.appdata = paths.lookupAppdataFolder() - if not os.path.exists(state.appdata): - os.makedirs(state.appdata) - sqlStoredProcedure('movemessagstoappdata') - # Write the keys.dat file to disk in the new location - BMConfigParser().save() - # Write the knownnodes.dat file to disk in the new location - knownnodes.saveKnownNodes(state.appdata) - os.remove(paths.lookupExeFolder() + 'keys.dat') - os.remove(paths.lookupExeFolder() + 'knownnodes.dat') - debug.resetLogging() - try: - os.remove(paths.lookupExeFolder() + 'debug.log') - os.remove(paths.lookupExeFolder() + 'debug.log.1') - except: - pass + dialogs.SettingsDialog(self, firstrun=self._firstrun).exec_() def on_action_Send(self): """Send message to current selected address""" @@ -3393,8 +3204,7 @@ class MyForm(settingsmixin.SMainWindow): 0].row() item = self.ui.tableWidgetAddressBook.item(currentRow, 0) sqlExecute( - 'DELETE FROM addressbook WHERE label=? AND address=?', - item.label, item.address) + 'DELETE FROM addressbook WHERE address=?', item.address) self.ui.tableWidgetAddressBook.removeRow(currentRow) self.rerenderMessagelistFromLabels() self.rerenderMessagelistToLabels() @@ -4253,237 +4063,6 @@ class MyForm(settingsmixin.SMainWindow): obj.loadSettings() -class settingsDialog(QtGui.QDialog): - - def __init__(self, parent): - QtGui.QWidget.__init__(self, parent) - self.ui = Ui_settingsDialog() - self.ui.setupUi(self) - self.parent = parent - self.ui.checkBoxStartOnLogon.setChecked( - BMConfigParser().getboolean('bitmessagesettings', 'startonlogon')) - self.ui.checkBoxMinimizeToTray.setChecked( - BMConfigParser().getboolean('bitmessagesettings', 'minimizetotray')) - self.ui.checkBoxTrayOnClose.setChecked( - BMConfigParser().safeGetBoolean('bitmessagesettings', 'trayonclose')) - self.ui.checkBoxHideTrayConnectionNotifications.setChecked( - BMConfigParser().getboolean("bitmessagesettings", "hidetrayconnectionnotifications")) - self.ui.checkBoxShowTrayNotifications.setChecked( - BMConfigParser().getboolean('bitmessagesettings', 'showtraynotifications')) - self.ui.checkBoxStartInTray.setChecked( - BMConfigParser().getboolean('bitmessagesettings', 'startintray')) - self.ui.checkBoxWillinglySendToMobile.setChecked( - BMConfigParser().safeGetBoolean('bitmessagesettings', 'willinglysendtomobile')) - self.ui.checkBoxUseIdenticons.setChecked( - BMConfigParser().safeGetBoolean('bitmessagesettings', 'useidenticons')) - self.ui.checkBoxReplyBelow.setChecked( - BMConfigParser().safeGetBoolean('bitmessagesettings', 'replybelow')) - - if state.appdata == paths.lookupExeFolder(): - self.ui.checkBoxPortableMode.setChecked(True) - else: - try: - import tempfile - tempfile.NamedTemporaryFile( - dir=paths.lookupExeFolder(), delete=True - ).close() # should autodelete - except: - self.ui.checkBoxPortableMode.setDisabled(True) - - if 'darwin' in sys.platform: - self.ui.checkBoxStartOnLogon.setDisabled(True) - self.ui.checkBoxStartOnLogon.setText(_translate( - "MainWindow", "Start-on-login not yet supported on your OS.")) - self.ui.checkBoxMinimizeToTray.setDisabled(True) - self.ui.checkBoxMinimizeToTray.setText(_translate( - "MainWindow", "Minimize-to-tray not yet supported on your OS.")) - self.ui.checkBoxShowTrayNotifications.setDisabled(True) - self.ui.checkBoxShowTrayNotifications.setText(_translate( - "MainWindow", "Tray notifications not yet supported on your OS.")) - elif 'linux' in sys.platform: - self.ui.checkBoxStartOnLogon.setDisabled(True) - self.ui.checkBoxStartOnLogon.setText(_translate( - "MainWindow", "Start-on-login not yet supported on your OS.")) - # On the Network settings tab: - self.ui.lineEditTCPPort.setText(str( - BMConfigParser().get('bitmessagesettings', 'port'))) - self.ui.checkBoxUPnP.setChecked( - BMConfigParser().safeGetBoolean('bitmessagesettings', 'upnp')) - self.ui.checkBoxAuthentication.setChecked(BMConfigParser().getboolean( - 'bitmessagesettings', 'socksauthentication')) - self.ui.checkBoxSocksListen.setChecked(BMConfigParser().getboolean( - 'bitmessagesettings', 'sockslisten')) - if str(BMConfigParser().get('bitmessagesettings', 'socksproxytype')) == 'none': - self.ui.comboBoxProxyType.setCurrentIndex(0) - self.ui.lineEditSocksHostname.setEnabled(False) - self.ui.lineEditSocksPort.setEnabled(False) - self.ui.lineEditSocksUsername.setEnabled(False) - self.ui.lineEditSocksPassword.setEnabled(False) - self.ui.checkBoxAuthentication.setEnabled(False) - self.ui.checkBoxSocksListen.setEnabled(False) - elif str(BMConfigParser().get('bitmessagesettings', 'socksproxytype')) == 'SOCKS4a': - self.ui.comboBoxProxyType.setCurrentIndex(1) - elif str(BMConfigParser().get('bitmessagesettings', 'socksproxytype')) == 'SOCKS5': - self.ui.comboBoxProxyType.setCurrentIndex(2) - - self.ui.lineEditSocksHostname.setText(str( - BMConfigParser().get('bitmessagesettings', 'sockshostname'))) - self.ui.lineEditSocksPort.setText(str( - BMConfigParser().get('bitmessagesettings', 'socksport'))) - self.ui.lineEditSocksUsername.setText(str( - BMConfigParser().get('bitmessagesettings', 'socksusername'))) - self.ui.lineEditSocksPassword.setText(str( - BMConfigParser().get('bitmessagesettings', 'sockspassword'))) - QtCore.QObject.connect(self.ui.comboBoxProxyType, QtCore.SIGNAL( - "currentIndexChanged(int)"), self.comboBoxProxyTypeChanged) - self.ui.lineEditMaxDownloadRate.setText(str( - BMConfigParser().get('bitmessagesettings', 'maxdownloadrate'))) - self.ui.lineEditMaxUploadRate.setText(str( - BMConfigParser().get('bitmessagesettings', 'maxuploadrate'))) - self.ui.lineEditMaxOutboundConnections.setText(str( - BMConfigParser().get('bitmessagesettings', 'maxoutboundconnections'))) - - # Demanded difficulty tab - self.ui.lineEditTotalDifficulty.setText(str((float(BMConfigParser().getint( - 'bitmessagesettings', 'defaultnoncetrialsperbyte')) / defaults.networkDefaultProofOfWorkNonceTrialsPerByte))) - self.ui.lineEditSmallMessageDifficulty.setText(str((float(BMConfigParser().getint( - 'bitmessagesettings', 'defaultpayloadlengthextrabytes')) / defaults.networkDefaultPayloadLengthExtraBytes))) - - # Max acceptable difficulty tab - self.ui.lineEditMaxAcceptableTotalDifficulty.setText(str((float(BMConfigParser().getint( - 'bitmessagesettings', 'maxacceptablenoncetrialsperbyte')) / defaults.networkDefaultProofOfWorkNonceTrialsPerByte))) - self.ui.lineEditMaxAcceptableSmallMessageDifficulty.setText(str((float(BMConfigParser().getint( - 'bitmessagesettings', 'maxacceptablepayloadlengthextrabytes')) / defaults.networkDefaultPayloadLengthExtraBytes))) - - # OpenCL - if openclpow.openclAvailable(): - self.ui.comboBoxOpenCL.setEnabled(True) - else: - self.ui.comboBoxOpenCL.setEnabled(False) - self.ui.comboBoxOpenCL.clear() - self.ui.comboBoxOpenCL.addItem("None") - self.ui.comboBoxOpenCL.addItems(openclpow.vendors) - self.ui.comboBoxOpenCL.setCurrentIndex(0) - for i in range(self.ui.comboBoxOpenCL.count()): - if self.ui.comboBoxOpenCL.itemText(i) == BMConfigParser().safeGet('bitmessagesettings', 'opencl'): - self.ui.comboBoxOpenCL.setCurrentIndex(i) - break - - # Namecoin integration tab - nmctype = BMConfigParser().get('bitmessagesettings', 'namecoinrpctype') - self.ui.lineEditNamecoinHost.setText(str( - BMConfigParser().get('bitmessagesettings', 'namecoinrpchost'))) - self.ui.lineEditNamecoinPort.setText(str( - BMConfigParser().get('bitmessagesettings', 'namecoinrpcport'))) - self.ui.lineEditNamecoinUser.setText(str( - BMConfigParser().get('bitmessagesettings', 'namecoinrpcuser'))) - self.ui.lineEditNamecoinPassword.setText(str( - BMConfigParser().get('bitmessagesettings', 'namecoinrpcpassword'))) - - if nmctype == "namecoind": - self.ui.radioButtonNamecoinNamecoind.setChecked(True) - elif nmctype == "nmcontrol": - self.ui.radioButtonNamecoinNmcontrol.setChecked(True) - self.ui.lineEditNamecoinUser.setEnabled(False) - self.ui.labelNamecoinUser.setEnabled(False) - self.ui.lineEditNamecoinPassword.setEnabled(False) - self.ui.labelNamecoinPassword.setEnabled(False) - else: - assert False - - QtCore.QObject.connect(self.ui.radioButtonNamecoinNamecoind, QtCore.SIGNAL( - "toggled(bool)"), self.namecoinTypeChanged) - QtCore.QObject.connect(self.ui.radioButtonNamecoinNmcontrol, QtCore.SIGNAL( - "toggled(bool)"), self.namecoinTypeChanged) - QtCore.QObject.connect(self.ui.pushButtonNamecoinTest, QtCore.SIGNAL( - "clicked()"), self.click_pushButtonNamecoinTest) - - #Message Resend tab - self.ui.lineEditDays.setText(str( - BMConfigParser().get('bitmessagesettings', 'stopresendingafterxdays'))) - self.ui.lineEditMonths.setText(str( - BMConfigParser().get('bitmessagesettings', 'stopresendingafterxmonths'))) - - - #'System' tab removed for now. - """try: - maxCores = BMConfigParser().getint('bitmessagesettings', 'maxcores') - except: - maxCores = 99999 - if maxCores <= 1: - self.ui.comboBoxMaxCores.setCurrentIndex(0) - elif maxCores == 2: - self.ui.comboBoxMaxCores.setCurrentIndex(1) - elif maxCores <= 4: - self.ui.comboBoxMaxCores.setCurrentIndex(2) - elif maxCores <= 8: - self.ui.comboBoxMaxCores.setCurrentIndex(3) - elif maxCores <= 16: - self.ui.comboBoxMaxCores.setCurrentIndex(4) - else: - self.ui.comboBoxMaxCores.setCurrentIndex(5)""" - - QtGui.QWidget.resize(self, QtGui.QWidget.sizeHint(self)) - - def comboBoxProxyTypeChanged(self, comboBoxIndex): - if comboBoxIndex == 0: - self.ui.lineEditSocksHostname.setEnabled(False) - self.ui.lineEditSocksPort.setEnabled(False) - self.ui.lineEditSocksUsername.setEnabled(False) - self.ui.lineEditSocksPassword.setEnabled(False) - self.ui.checkBoxAuthentication.setEnabled(False) - self.ui.checkBoxSocksListen.setEnabled(False) - elif comboBoxIndex == 1 or comboBoxIndex == 2: - self.ui.lineEditSocksHostname.setEnabled(True) - self.ui.lineEditSocksPort.setEnabled(True) - self.ui.checkBoxAuthentication.setEnabled(True) - self.ui.checkBoxSocksListen.setEnabled(True) - if self.ui.checkBoxAuthentication.isChecked(): - self.ui.lineEditSocksUsername.setEnabled(True) - self.ui.lineEditSocksPassword.setEnabled(True) - - # Check status of namecoin integration radio buttons and translate - # it to a string as in the options. - def getNamecoinType(self): - if self.ui.radioButtonNamecoinNamecoind.isChecked(): - return "namecoind" - if self.ui.radioButtonNamecoinNmcontrol.isChecked(): - return "nmcontrol" - assert False - - # Namecoin connection type was changed. - def namecoinTypeChanged(self, checked): - nmctype = self.getNamecoinType() - assert nmctype == "namecoind" or nmctype == "nmcontrol" - - isNamecoind = (nmctype == "namecoind") - self.ui.lineEditNamecoinUser.setEnabled(isNamecoind) - self.ui.labelNamecoinUser.setEnabled(isNamecoind) - self.ui.lineEditNamecoinPassword.setEnabled(isNamecoind) - self.ui.labelNamecoinPassword.setEnabled(isNamecoind) - - if isNamecoind: - self.ui.lineEditNamecoinPort.setText(defaults.namecoinDefaultRpcPort) - else: - self.ui.lineEditNamecoinPort.setText("9000") - - def click_pushButtonNamecoinTest(self): - """Test the namecoin settings specified in the settings dialog.""" - self.ui.labelNamecoinTestResult.setText(_translate( - "MainWindow", "Testing...")) - options = {} - options["type"] = self.getNamecoinType() - options["host"] = str(self.ui.lineEditNamecoinHost.text().toUtf8()) - options["port"] = str(self.ui.lineEditNamecoinPort.text().toUtf8()) - options["user"] = str(self.ui.lineEditNamecoinUser.text().toUtf8()) - options["password"] = str(self.ui.lineEditNamecoinPassword.text().toUtf8()) - nc = namecoin.namecoinConnection(options) - status, text = nc.test() - self.ui.labelNamecoinTestResult.setText(text) - if status == 'success': - self.parent.namecoin = nc - - # In order for the time columns on the Inbox and Sent tabs to be sorted # correctly (rather than alphabetically), we need to overload the < # operator and use this class instead of QTableWidgetItem. @@ -4558,7 +4137,6 @@ def init(): def run(): global myapp app = init() - change_translation(l10n.getTranslationLanguage()) app.setStyleSheet("QStatusBar::item { border: 0px solid black }") myapp = MyForm() diff --git a/src/bitmessageqt/dialogs.py b/src/bitmessageqt/dialogs.py index 1acdbc3f..b4bcd2fd 100644 --- a/src/bitmessageqt/dialogs.py +++ b/src/bitmessageqt/dialogs.py @@ -5,22 +5,25 @@ src/bitmessageqt/dialogs.py from PyQt4 import QtGui -from version import softwareVersion - import paths import widgets from address_dialogs import ( - AddAddressDialog, EmailGatewayDialog, NewAddressDialog, NewSubscriptionDialog, RegenerateAddressesDialog, + AddAddressDialog, EmailGatewayDialog, NewAddressDialog, + NewSubscriptionDialog, RegenerateAddressesDialog, SpecialAddressBehaviorDialog ) from newchandialog import NewChanDialog from retranslateui import RetranslateMixin +from settings import SettingsDialog from tr import _translate +from version import softwareVersion + __all__ = [ "NewChanDialog", "AddAddressDialog", "NewAddressDialog", "NewSubscriptionDialog", "RegenerateAddressesDialog", - "SpecialAddressBehaviorDialog", "EmailGatewayDialog" + "SpecialAddressBehaviorDialog", "EmailGatewayDialog", + "SettingsDialog" ] diff --git a/src/bitmessageqt/settings.py b/src/bitmessageqt/settings.py index 3a3db962..513f285b 100644 --- a/src/bitmessageqt/settings.py +++ b/src/bitmessageqt/settings.py @@ -1,630 +1,546 @@ -# -*- coding: utf-8 -*- -# pylint: disable=too-many-instance-attributes,too-many-locals,too-many-statements,attribute-defined-outside-init -""" -src/bitmessageqt/settings.py -============================ - -Form implementation generated from reading ui file 'settings.ui' - -Created: Thu Dec 25 23:21:20 2014 - by: PyQt4 UI code generator 4.10.3 - -WARNING! All changes made in this file will be lost! -""" - -from sys import platform +import os +import sys from PyQt4 import QtCore, QtGui -from . import bitmessage_icons_rc # pylint: disable=unused-import -from .languagebox import LanguageBox - -try: - _fromUtf8 = QtCore.QString.fromUtf8 -except AttributeError: - def _fromUtf8(s): - return s - -try: - _encoding = QtGui.QApplication.UnicodeUTF8 - - def _translate(context, text, disambig): - return QtGui.QApplication.translate(context, text, disambig, _encoding) -except AttributeError: - def _translate(context, text, disambig): - return QtGui.QApplication.translate(context, text, disambig) +import debug +import defaults +import knownnodes +import namecoin +import openclpow +import paths +import queues +import shared +import state +import tempfile +import widgets +from bmconfigparser import BMConfigParser +from helper_sql import sqlExecute, sqlStoredProcedure +from network.asyncore_pollchoose import set_rates +from tr import _translate -class Ui_settingsDialog(object): - """Encapsulate a UI settings dialog object""" +class SettingsDialog(QtGui.QDialog): + """The "Settings" dialog""" + def __init__(self, parent=None, firstrun=False): + super(SettingsDialog, self).__init__(parent) + widgets.load('settings.ui', self) - def setupUi(self, settingsDialog): - """Set up the UI""" + self.parent = parent + self.firstrun = firstrun + self.config = BMConfigParser() + self.net_restart_needed = False + self.timer = QtCore.QTimer() - settingsDialog.setObjectName(_fromUtf8("settingsDialog")) - settingsDialog.resize(521, 413) - self.gridLayout = QtGui.QGridLayout(settingsDialog) - self.gridLayout.setObjectName(_fromUtf8("gridLayout")) - self.buttonBox = QtGui.QDialogButtonBox(settingsDialog) - self.buttonBox.setOrientation(QtCore.Qt.Horizontal) - self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel | QtGui.QDialogButtonBox.Ok) - self.buttonBox.setObjectName(_fromUtf8("buttonBox")) - self.gridLayout.addWidget(self.buttonBox, 1, 0, 1, 1) - self.tabWidgetSettings = QtGui.QTabWidget(settingsDialog) - self.tabWidgetSettings.setObjectName(_fromUtf8("tabWidgetSettings")) - self.tabUserInterface = QtGui.QWidget() - self.tabUserInterface.setEnabled(True) - self.tabUserInterface.setObjectName(_fromUtf8("tabUserInterface")) - self.formLayout = QtGui.QFormLayout(self.tabUserInterface) - self.formLayout.setObjectName(_fromUtf8("formLayout")) - self.checkBoxStartOnLogon = QtGui.QCheckBox(self.tabUserInterface) - self.checkBoxStartOnLogon.setObjectName(_fromUtf8("checkBoxStartOnLogon")) - self.formLayout.setWidget(0, QtGui.QFormLayout.LabelRole, self.checkBoxStartOnLogon) - self.groupBoxTray = QtGui.QGroupBox(self.tabUserInterface) - self.groupBoxTray.setObjectName(_fromUtf8("groupBoxTray")) - self.formLayoutTray = QtGui.QFormLayout(self.groupBoxTray) - self.formLayoutTray.setObjectName(_fromUtf8("formLayoutTray")) - self.checkBoxStartInTray = QtGui.QCheckBox(self.groupBoxTray) - self.checkBoxStartInTray.setObjectName(_fromUtf8("checkBoxStartInTray")) - self.formLayoutTray.setWidget(0, QtGui.QFormLayout.SpanningRole, self.checkBoxStartInTray) - self.checkBoxMinimizeToTray = QtGui.QCheckBox(self.groupBoxTray) - self.checkBoxMinimizeToTray.setChecked(True) - self.checkBoxMinimizeToTray.setObjectName(_fromUtf8("checkBoxMinimizeToTray")) - self.formLayoutTray.setWidget(1, QtGui.QFormLayout.LabelRole, self.checkBoxMinimizeToTray) - self.checkBoxTrayOnClose = QtGui.QCheckBox(self.groupBoxTray) - self.checkBoxTrayOnClose.setChecked(True) - self.checkBoxTrayOnClose.setObjectName(_fromUtf8("checkBoxTrayOnClose")) - self.formLayoutTray.setWidget(2, QtGui.QFormLayout.LabelRole, self.checkBoxTrayOnClose) - self.formLayout.setWidget(1, QtGui.QFormLayout.SpanningRole, self.groupBoxTray) - self.checkBoxHideTrayConnectionNotifications = QtGui.QCheckBox(self.tabUserInterface) - self.checkBoxHideTrayConnectionNotifications.setChecked(False) - self.checkBoxHideTrayConnectionNotifications.setObjectName( - _fromUtf8("checkBoxHideTrayConnectionNotifications")) - self.formLayout.setWidget(2, QtGui.QFormLayout.LabelRole, self.checkBoxHideTrayConnectionNotifications) - self.checkBoxShowTrayNotifications = QtGui.QCheckBox(self.tabUserInterface) - self.checkBoxShowTrayNotifications.setObjectName(_fromUtf8("checkBoxShowTrayNotifications")) - self.formLayout.setWidget(3, QtGui.QFormLayout.LabelRole, self.checkBoxShowTrayNotifications) - self.checkBoxPortableMode = QtGui.QCheckBox(self.tabUserInterface) - self.checkBoxPortableMode.setObjectName(_fromUtf8("checkBoxPortableMode")) - self.formLayout.setWidget(4, QtGui.QFormLayout.LabelRole, self.checkBoxPortableMode) - self.PortableModeDescription = QtGui.QLabel(self.tabUserInterface) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.PortableModeDescription.sizePolicy().hasHeightForWidth()) - self.PortableModeDescription.setSizePolicy(sizePolicy) - self.PortableModeDescription.setWordWrap(True) - self.PortableModeDescription.setObjectName(_fromUtf8("PortableModeDescription")) - self.formLayout.setWidget(5, QtGui.QFormLayout.SpanningRole, self.PortableModeDescription) - self.checkBoxWillinglySendToMobile = QtGui.QCheckBox(self.tabUserInterface) - self.checkBoxWillinglySendToMobile.setObjectName(_fromUtf8("checkBoxWillinglySendToMobile")) - self.formLayout.setWidget(6, QtGui.QFormLayout.SpanningRole, self.checkBoxWillinglySendToMobile) - self.checkBoxUseIdenticons = QtGui.QCheckBox(self.tabUserInterface) - self.checkBoxUseIdenticons.setObjectName(_fromUtf8("checkBoxUseIdenticons")) - self.formLayout.setWidget(7, QtGui.QFormLayout.LabelRole, self.checkBoxUseIdenticons) - self.checkBoxReplyBelow = QtGui.QCheckBox(self.tabUserInterface) - self.checkBoxReplyBelow.setObjectName(_fromUtf8("checkBoxReplyBelow")) - self.formLayout.setWidget(8, QtGui.QFormLayout.LabelRole, self.checkBoxReplyBelow) - self.groupBox = QtGui.QGroupBox(self.tabUserInterface) - self.groupBox.setObjectName(_fromUtf8("groupBox")) - self.formLayout_2 = QtGui.QFormLayout(self.groupBox) - self.formLayout_2.setObjectName(_fromUtf8("formLayout_2")) - self.languageComboBox = LanguageBox(self.groupBox) - self.languageComboBox.setMinimumSize(QtCore.QSize(100, 0)) - self.languageComboBox.setObjectName(_fromUtf8("languageComboBox")) # pylint: disable=not-callable - self.formLayout_2.setWidget(0, QtGui.QFormLayout.LabelRole, self.languageComboBox) - self.formLayout.setWidget(9, QtGui.QFormLayout.FieldRole, self.groupBox) - self.tabWidgetSettings.addTab(self.tabUserInterface, _fromUtf8("")) - self.tabNetworkSettings = QtGui.QWidget() - self.tabNetworkSettings.setObjectName(_fromUtf8("tabNetworkSettings")) - self.gridLayout_4 = QtGui.QGridLayout(self.tabNetworkSettings) - self.gridLayout_4.setObjectName(_fromUtf8("gridLayout_4")) - self.groupBox1 = QtGui.QGroupBox(self.tabNetworkSettings) - self.groupBox1.setObjectName(_fromUtf8("groupBox1")) - self.gridLayout_3 = QtGui.QGridLayout(self.groupBox1) - self.gridLayout_3.setObjectName(_fromUtf8("gridLayout_3")) - self.label = QtGui.QLabel(self.groupBox1) - self.label.setObjectName(_fromUtf8("label")) - self.gridLayout_3.addWidget(self.label, 0, 0, 1, 1, QtCore.Qt.AlignRight) - self.lineEditTCPPort = QtGui.QLineEdit(self.groupBox1) - self.lineEditTCPPort.setMaximumSize(QtCore.QSize(70, 16777215)) - self.lineEditTCPPort.setObjectName(_fromUtf8("lineEditTCPPort")) - self.gridLayout_3.addWidget(self.lineEditTCPPort, 0, 1, 1, 1, QtCore.Qt.AlignLeft) - self.labelUPnP = QtGui.QLabel(self.groupBox1) - self.labelUPnP.setObjectName(_fromUtf8("labelUPnP")) - self.gridLayout_3.addWidget(self.labelUPnP, 0, 2, 1, 1, QtCore.Qt.AlignRight) - self.checkBoxUPnP = QtGui.QCheckBox(self.groupBox1) - self.checkBoxUPnP.setObjectName(_fromUtf8("checkBoxUPnP")) - self.gridLayout_3.addWidget(self.checkBoxUPnP, 0, 3, 1, 1, QtCore.Qt.AlignLeft) - self.gridLayout_4.addWidget(self.groupBox1, 0, 0, 1, 1) - self.groupBox_3 = QtGui.QGroupBox(self.tabNetworkSettings) - self.groupBox_3.setObjectName(_fromUtf8("groupBox_3")) - self.gridLayout_9 = QtGui.QGridLayout(self.groupBox_3) - self.gridLayout_9.setObjectName(_fromUtf8("gridLayout_9")) - spacerItem1 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_9.addItem(spacerItem1, 0, 0, 2, 1) - self.label_24 = QtGui.QLabel(self.groupBox_3) - self.label_24.setObjectName(_fromUtf8("label_24")) - self.gridLayout_9.addWidget(self.label_24, 0, 1, 1, 1) - self.lineEditMaxDownloadRate = QtGui.QLineEdit(self.groupBox_3) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.lineEditMaxDownloadRate.sizePolicy().hasHeightForWidth()) - self.lineEditMaxDownloadRate.setSizePolicy(sizePolicy) - self.lineEditMaxDownloadRate.setMaximumSize(QtCore.QSize(60, 16777215)) - self.lineEditMaxDownloadRate.setObjectName(_fromUtf8("lineEditMaxDownloadRate")) - self.gridLayout_9.addWidget(self.lineEditMaxDownloadRate, 0, 2, 1, 1) - self.label_25 = QtGui.QLabel(self.groupBox_3) - self.label_25.setObjectName(_fromUtf8("label_25")) - self.gridLayout_9.addWidget(self.label_25, 1, 1, 1, 1) - self.lineEditMaxUploadRate = QtGui.QLineEdit(self.groupBox_3) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.lineEditMaxUploadRate.sizePolicy().hasHeightForWidth()) - self.lineEditMaxUploadRate.setSizePolicy(sizePolicy) - self.lineEditMaxUploadRate.setMaximumSize(QtCore.QSize(60, 16777215)) - self.lineEditMaxUploadRate.setObjectName(_fromUtf8("lineEditMaxUploadRate")) - self.gridLayout_9.addWidget(self.lineEditMaxUploadRate, 1, 2, 1, 1) - self.label_26 = QtGui.QLabel(self.groupBox_3) - self.label_26.setObjectName(_fromUtf8("label_26")) - self.gridLayout_9.addWidget(self.label_26, 2, 1, 1, 1) - self.lineEditMaxOutboundConnections = QtGui.QLineEdit(self.groupBox_3) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.lineEditMaxOutboundConnections.sizePolicy().hasHeightForWidth()) - self.lineEditMaxOutboundConnections.setSizePolicy(sizePolicy) - self.lineEditMaxOutboundConnections.setMaximumSize(QtCore.QSize(60, 16777215)) - self.lineEditMaxOutboundConnections.setObjectName(_fromUtf8("lineEditMaxOutboundConnections")) self.lineEditMaxOutboundConnections.setValidator( QtGui.QIntValidator(0, 8, self.lineEditMaxOutboundConnections)) - self.gridLayout_9.addWidget(self.lineEditMaxOutboundConnections, 2, 2, 1, 1) - self.gridLayout_4.addWidget(self.groupBox_3, 2, 0, 1, 1) - self.groupBox_2 = QtGui.QGroupBox(self.tabNetworkSettings) - self.groupBox_2.setObjectName(_fromUtf8("groupBox_2")) - self.gridLayout_2 = QtGui.QGridLayout(self.groupBox_2) - self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2")) - self.label_2 = QtGui.QLabel(self.groupBox_2) - self.label_2.setObjectName(_fromUtf8("label_2")) - self.gridLayout_2.addWidget(self.label_2, 0, 0, 1, 1) - self.label_3 = QtGui.QLabel(self.groupBox_2) - self.label_3.setObjectName(_fromUtf8("label_3")) - self.gridLayout_2.addWidget(self.label_3, 1, 1, 1, 1) - self.lineEditSocksHostname = QtGui.QLineEdit(self.groupBox_2) - self.lineEditSocksHostname.setObjectName(_fromUtf8("lineEditSocksHostname")) - self.lineEditSocksHostname.setPlaceholderText(_fromUtf8("127.0.0.1")) - self.gridLayout_2.addWidget(self.lineEditSocksHostname, 1, 2, 1, 2) - self.label_4 = QtGui.QLabel(self.groupBox_2) - self.label_4.setObjectName(_fromUtf8("label_4")) - self.gridLayout_2.addWidget(self.label_4, 1, 4, 1, 1) - self.lineEditSocksPort = QtGui.QLineEdit(self.groupBox_2) - self.lineEditSocksPort.setObjectName(_fromUtf8("lineEditSocksPort")) - if platform in ['darwin', 'win32', 'win64']: - self.lineEditSocksPort.setPlaceholderText(_fromUtf8("9150")) + + self.adjust_from_config(self.config) + if firstrun: + # switch to "Network Settings" tab if user selected + # "Let me configure special network settings first" on first run + self.tabWidgetSettings.setCurrentIndex( + self.tabWidgetSettings.indexOf(self.tabNetworkSettings) + ) + QtGui.QWidget.resize(self, QtGui.QWidget.sizeHint(self)) + + def adjust_from_config(self, config): + """Adjust all widgets state according to config settings""" + # pylint: disable=too-many-branches,too-many-statements + self.checkBoxStartOnLogon.setChecked( + config.getboolean('bitmessagesettings', 'startonlogon')) + self.checkBoxMinimizeToTray.setChecked( + config.getboolean('bitmessagesettings', 'minimizetotray')) + self.checkBoxTrayOnClose.setChecked( + config.safeGetBoolean('bitmessagesettings', 'trayonclose')) + self.checkBoxHideTrayConnectionNotifications.setChecked( + config.getboolean("bitmessagesettings", "hidetrayconnectionnotifications")) + self.checkBoxShowTrayNotifications.setChecked( + config.getboolean('bitmessagesettings', 'showtraynotifications')) + self.checkBoxStartInTray.setChecked( + config.getboolean('bitmessagesettings', 'startintray')) + self.checkBoxWillinglySendToMobile.setChecked( + config.safeGetBoolean('bitmessagesettings', 'willinglysendtomobile')) + self.checkBoxUseIdenticons.setChecked( + config.safeGetBoolean('bitmessagesettings', 'useidenticons')) + self.checkBoxReplyBelow.setChecked( + config.safeGetBoolean('bitmessagesettings', 'replybelow')) + + if state.appdata == paths.lookupExeFolder(): + self.checkBoxPortableMode.setChecked(True) else: - self.lineEditSocksPort.setPlaceholderText(_fromUtf8("9050")) - self.gridLayout_2.addWidget(self.lineEditSocksPort, 1, 5, 1, 1) - self.checkBoxAuthentication = QtGui.QCheckBox(self.groupBox_2) - self.checkBoxAuthentication.setObjectName(_fromUtf8("checkBoxAuthentication")) - self.gridLayout_2.addWidget(self.checkBoxAuthentication, 2, 1, 1, 1) - self.label_5 = QtGui.QLabel(self.groupBox_2) - self.label_5.setObjectName(_fromUtf8("label_5")) - self.gridLayout_2.addWidget(self.label_5, 2, 2, 1, 1) - self.lineEditSocksUsername = QtGui.QLineEdit(self.groupBox_2) - self.lineEditSocksUsername.setEnabled(False) - self.lineEditSocksUsername.setObjectName(_fromUtf8("lineEditSocksUsername")) - self.gridLayout_2.addWidget(self.lineEditSocksUsername, 2, 3, 1, 1) - self.label_6 = QtGui.QLabel(self.groupBox_2) - self.label_6.setObjectName(_fromUtf8("label_6")) - self.gridLayout_2.addWidget(self.label_6, 2, 4, 1, 1) - self.lineEditSocksPassword = QtGui.QLineEdit(self.groupBox_2) - self.lineEditSocksPassword.setEnabled(False) - self.lineEditSocksPassword.setInputMethodHints( - QtCore.Qt.ImhHiddenText | QtCore.Qt.ImhNoAutoUppercase | QtCore.Qt.ImhNoPredictiveText) - self.lineEditSocksPassword.setEchoMode(QtGui.QLineEdit.Password) - self.lineEditSocksPassword.setObjectName(_fromUtf8("lineEditSocksPassword")) - self.gridLayout_2.addWidget(self.lineEditSocksPassword, 2, 5, 1, 1) - self.checkBoxSocksListen = QtGui.QCheckBox(self.groupBox_2) - self.checkBoxSocksListen.setObjectName(_fromUtf8("checkBoxSocksListen")) - self.gridLayout_2.addWidget(self.checkBoxSocksListen, 3, 1, 1, 4) - self.comboBoxProxyType = QtGui.QComboBox(self.groupBox_2) - self.comboBoxProxyType.setObjectName(_fromUtf8("comboBoxProxyType")) # pylint: disable=not-callable - self.comboBoxProxyType.addItem(_fromUtf8("")) - self.comboBoxProxyType.addItem(_fromUtf8("")) - self.comboBoxProxyType.addItem(_fromUtf8("")) - self.gridLayout_2.addWidget(self.comboBoxProxyType, 0, 1, 1, 1) - self.gridLayout_4.addWidget(self.groupBox_2, 1, 0, 1, 1) - spacerItem2 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) - self.gridLayout_4.addItem(spacerItem2, 3, 0, 1, 1) - self.tabWidgetSettings.addTab(self.tabNetworkSettings, _fromUtf8("")) - self.tabDemandedDifficulty = QtGui.QWidget() - self.tabDemandedDifficulty.setObjectName(_fromUtf8("tabDemandedDifficulty")) - self.gridLayout_6 = QtGui.QGridLayout(self.tabDemandedDifficulty) - self.gridLayout_6.setObjectName(_fromUtf8("gridLayout_6")) - self.label_9 = QtGui.QLabel(self.tabDemandedDifficulty) - self.label_9.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter) - self.label_9.setObjectName(_fromUtf8("label_9")) - self.gridLayout_6.addWidget(self.label_9, 1, 1, 1, 1) - self.label_10 = QtGui.QLabel(self.tabDemandedDifficulty) - self.label_10.setWordWrap(True) - self.label_10.setObjectName(_fromUtf8("label_10")) - self.gridLayout_6.addWidget(self.label_10, 2, 0, 1, 3) - self.label_11 = QtGui.QLabel(self.tabDemandedDifficulty) - self.label_11.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter) - self.label_11.setObjectName(_fromUtf8("label_11")) - self.gridLayout_6.addWidget(self.label_11, 3, 1, 1, 1) - self.label_8 = QtGui.QLabel(self.tabDemandedDifficulty) - self.label_8.setWordWrap(True) - self.label_8.setObjectName(_fromUtf8("label_8")) - self.gridLayout_6.addWidget(self.label_8, 0, 0, 1, 3) - spacerItem3 = QtGui.QSpacerItem(203, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_6.addItem(spacerItem3, 1, 0, 1, 1) - self.label_12 = QtGui.QLabel(self.tabDemandedDifficulty) - self.label_12.setWordWrap(True) - self.label_12.setObjectName(_fromUtf8("label_12")) - self.gridLayout_6.addWidget(self.label_12, 4, 0, 1, 3) - self.lineEditSmallMessageDifficulty = QtGui.QLineEdit(self.tabDemandedDifficulty) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.lineEditSmallMessageDifficulty.sizePolicy().hasHeightForWidth()) - self.lineEditSmallMessageDifficulty.setSizePolicy(sizePolicy) - self.lineEditSmallMessageDifficulty.setMaximumSize(QtCore.QSize(70, 16777215)) - self.lineEditSmallMessageDifficulty.setObjectName(_fromUtf8("lineEditSmallMessageDifficulty")) - self.gridLayout_6.addWidget(self.lineEditSmallMessageDifficulty, 3, 2, 1, 1) - self.lineEditTotalDifficulty = QtGui.QLineEdit(self.tabDemandedDifficulty) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.lineEditTotalDifficulty.sizePolicy().hasHeightForWidth()) - self.lineEditTotalDifficulty.setSizePolicy(sizePolicy) - self.lineEditTotalDifficulty.setMaximumSize(QtCore.QSize(70, 16777215)) - self.lineEditTotalDifficulty.setObjectName(_fromUtf8("lineEditTotalDifficulty")) - self.gridLayout_6.addWidget(self.lineEditTotalDifficulty, 1, 2, 1, 1) - spacerItem4 = QtGui.QSpacerItem(203, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_6.addItem(spacerItem4, 3, 0, 1, 1) - spacerItem5 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) - self.gridLayout_6.addItem(spacerItem5, 5, 0, 1, 1) - self.tabWidgetSettings.addTab(self.tabDemandedDifficulty, _fromUtf8("")) - self.tabMaxAcceptableDifficulty = QtGui.QWidget() - self.tabMaxAcceptableDifficulty.setObjectName(_fromUtf8("tabMaxAcceptableDifficulty")) - self.gridLayout_7 = QtGui.QGridLayout(self.tabMaxAcceptableDifficulty) - self.gridLayout_7.setObjectName(_fromUtf8("gridLayout_7")) - self.label_15 = QtGui.QLabel(self.tabMaxAcceptableDifficulty) - self.label_15.setWordWrap(True) - self.label_15.setObjectName(_fromUtf8("label_15")) - self.gridLayout_7.addWidget(self.label_15, 0, 0, 1, 3) - spacerItem6 = QtGui.QSpacerItem(102, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_7.addItem(spacerItem6, 1, 0, 1, 1) - self.label_13 = QtGui.QLabel(self.tabMaxAcceptableDifficulty) - self.label_13.setLayoutDirection(QtCore.Qt.LeftToRight) - self.label_13.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter) - self.label_13.setObjectName(_fromUtf8("label_13")) - self.gridLayout_7.addWidget(self.label_13, 1, 1, 1, 1) - self.lineEditMaxAcceptableTotalDifficulty = QtGui.QLineEdit(self.tabMaxAcceptableDifficulty) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.lineEditMaxAcceptableTotalDifficulty.sizePolicy().hasHeightForWidth()) - self.lineEditMaxAcceptableTotalDifficulty.setSizePolicy(sizePolicy) - self.lineEditMaxAcceptableTotalDifficulty.setMaximumSize(QtCore.QSize(70, 16777215)) - self.lineEditMaxAcceptableTotalDifficulty.setObjectName(_fromUtf8("lineEditMaxAcceptableTotalDifficulty")) - self.gridLayout_7.addWidget(self.lineEditMaxAcceptableTotalDifficulty, 1, 2, 1, 1) - spacerItem7 = QtGui.QSpacerItem(102, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_7.addItem(spacerItem7, 2, 0, 1, 1) - self.label_14 = QtGui.QLabel(self.tabMaxAcceptableDifficulty) - self.label_14.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter) - self.label_14.setObjectName(_fromUtf8("label_14")) - self.gridLayout_7.addWidget(self.label_14, 2, 1, 1, 1) - self.lineEditMaxAcceptableSmallMessageDifficulty = QtGui.QLineEdit(self.tabMaxAcceptableDifficulty) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.lineEditMaxAcceptableSmallMessageDifficulty.sizePolicy().hasHeightForWidth()) - self.lineEditMaxAcceptableSmallMessageDifficulty.setSizePolicy(sizePolicy) - self.lineEditMaxAcceptableSmallMessageDifficulty.setMaximumSize(QtCore.QSize(70, 16777215)) - self.lineEditMaxAcceptableSmallMessageDifficulty.setObjectName( - _fromUtf8("lineEditMaxAcceptableSmallMessageDifficulty")) - self.gridLayout_7.addWidget(self.lineEditMaxAcceptableSmallMessageDifficulty, 2, 2, 1, 1) - spacerItem8 = QtGui.QSpacerItem(20, 147, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) - self.gridLayout_7.addItem(spacerItem8, 3, 1, 1, 1) - self.labelOpenCL = QtGui.QLabel(self.tabMaxAcceptableDifficulty) - self.labelOpenCL.setObjectName(_fromUtf8("labelOpenCL")) - self.gridLayout_7.addWidget(self.labelOpenCL, 4, 0, 1, 1) - self.comboBoxOpenCL = QtGui.QComboBox(self.tabMaxAcceptableDifficulty) - self.comboBoxOpenCL.setObjectName = (_fromUtf8("comboBoxOpenCL")) - self.gridLayout_7.addWidget(self.comboBoxOpenCL, 4, 1, 1, 1) - self.tabWidgetSettings.addTab(self.tabMaxAcceptableDifficulty, _fromUtf8("")) - self.tabNamecoin = QtGui.QWidget() - self.tabNamecoin.setObjectName(_fromUtf8("tabNamecoin")) - self.gridLayout_8 = QtGui.QGridLayout(self.tabNamecoin) - self.gridLayout_8.setObjectName(_fromUtf8("gridLayout_8")) - spacerItem9 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_8.addItem(spacerItem9, 2, 0, 1, 1) - self.label_16 = QtGui.QLabel(self.tabNamecoin) - self.label_16.setWordWrap(True) - self.label_16.setObjectName(_fromUtf8("label_16")) - self.gridLayout_8.addWidget(self.label_16, 0, 0, 1, 3) - self.label_17 = QtGui.QLabel(self.tabNamecoin) - self.label_17.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter) - self.label_17.setObjectName(_fromUtf8("label_17")) - self.gridLayout_8.addWidget(self.label_17, 2, 1, 1, 1) - self.lineEditNamecoinHost = QtGui.QLineEdit(self.tabNamecoin) - self.lineEditNamecoinHost.setObjectName(_fromUtf8("lineEditNamecoinHost")) - self.gridLayout_8.addWidget(self.lineEditNamecoinHost, 2, 2, 1, 1) - spacerItem10 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_8.addItem(spacerItem10, 3, 0, 1, 1) - spacerItem11 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_8.addItem(spacerItem11, 4, 0, 1, 1) - self.label_18 = QtGui.QLabel(self.tabNamecoin) - self.label_18.setEnabled(True) - self.label_18.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter) - self.label_18.setObjectName(_fromUtf8("label_18")) - self.gridLayout_8.addWidget(self.label_18, 3, 1, 1, 1) - self.lineEditNamecoinPort = QtGui.QLineEdit(self.tabNamecoin) - self.lineEditNamecoinPort.setObjectName(_fromUtf8("lineEditNamecoinPort")) - self.gridLayout_8.addWidget(self.lineEditNamecoinPort, 3, 2, 1, 1) - spacerItem12 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) - self.gridLayout_8.addItem(spacerItem12, 8, 1, 1, 1) - self.labelNamecoinUser = QtGui.QLabel(self.tabNamecoin) - self.labelNamecoinUser.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter) - self.labelNamecoinUser.setObjectName(_fromUtf8("labelNamecoinUser")) - self.gridLayout_8.addWidget(self.labelNamecoinUser, 4, 1, 1, 1) - self.lineEditNamecoinUser = QtGui.QLineEdit(self.tabNamecoin) - self.lineEditNamecoinUser.setObjectName(_fromUtf8("lineEditNamecoinUser")) - self.gridLayout_8.addWidget(self.lineEditNamecoinUser, 4, 2, 1, 1) - spacerItem13 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_8.addItem(spacerItem13, 5, 0, 1, 1) - self.labelNamecoinPassword = QtGui.QLabel(self.tabNamecoin) - self.labelNamecoinPassword.setAlignment( - QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter) - self.labelNamecoinPassword.setObjectName(_fromUtf8("labelNamecoinPassword")) - self.gridLayout_8.addWidget(self.labelNamecoinPassword, 5, 1, 1, 1) - self.lineEditNamecoinPassword = QtGui.QLineEdit(self.tabNamecoin) - self.lineEditNamecoinPassword.setInputMethodHints( - QtCore.Qt.ImhHiddenText | QtCore.Qt.ImhNoAutoUppercase | QtCore.Qt.ImhNoPredictiveText) - self.lineEditNamecoinPassword.setEchoMode(QtGui.QLineEdit.Password) - self.lineEditNamecoinPassword.setObjectName(_fromUtf8("lineEditNamecoinPassword")) - self.gridLayout_8.addWidget(self.lineEditNamecoinPassword, 5, 2, 1, 1) - self.labelNamecoinTestResult = QtGui.QLabel(self.tabNamecoin) - self.labelNamecoinTestResult.setText(_fromUtf8("")) - self.labelNamecoinTestResult.setObjectName(_fromUtf8("labelNamecoinTestResult")) - self.gridLayout_8.addWidget(self.labelNamecoinTestResult, 7, 0, 1, 2) - self.pushButtonNamecoinTest = QtGui.QPushButton(self.tabNamecoin) - self.pushButtonNamecoinTest.setObjectName(_fromUtf8("pushButtonNamecoinTest")) - self.gridLayout_8.addWidget(self.pushButtonNamecoinTest, 7, 2, 1, 1) - self.horizontalLayout = QtGui.QHBoxLayout() - self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) - self.label_21 = QtGui.QLabel(self.tabNamecoin) - self.label_21.setObjectName(_fromUtf8("label_21")) - self.horizontalLayout.addWidget(self.label_21) - self.radioButtonNamecoinNamecoind = QtGui.QRadioButton(self.tabNamecoin) - self.radioButtonNamecoinNamecoind.setObjectName(_fromUtf8("radioButtonNamecoinNamecoind")) - self.horizontalLayout.addWidget(self.radioButtonNamecoinNamecoind) - self.radioButtonNamecoinNmcontrol = QtGui.QRadioButton(self.tabNamecoin) - self.radioButtonNamecoinNmcontrol.setObjectName(_fromUtf8("radioButtonNamecoinNmcontrol")) - self.horizontalLayout.addWidget(self.radioButtonNamecoinNmcontrol) - self.gridLayout_8.addLayout(self.horizontalLayout, 1, 0, 1, 3) - self.tabWidgetSettings.addTab(self.tabNamecoin, _fromUtf8("")) - self.tabResendsExpire = QtGui.QWidget() - self.tabResendsExpire.setObjectName(_fromUtf8("tabResendsExpire")) - self.gridLayout_5 = QtGui.QGridLayout(self.tabResendsExpire) - self.gridLayout_5.setObjectName(_fromUtf8("gridLayout_5")) - self.label_7 = QtGui.QLabel(self.tabResendsExpire) - self.label_7.setWordWrap(True) - self.label_7.setObjectName(_fromUtf8("label_7")) - self.gridLayout_5.addWidget(self.label_7, 0, 0, 1, 3) - spacerItem14 = QtGui.QSpacerItem(212, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_5.addItem(spacerItem14, 1, 0, 1, 1) - self.widget = QtGui.QWidget(self.tabResendsExpire) - self.widget.setMinimumSize(QtCore.QSize(231, 75)) - self.widget.setObjectName(_fromUtf8("widget")) - self.label_19 = QtGui.QLabel(self.widget) - self.label_19.setGeometry(QtCore.QRect(10, 20, 101, 20)) - self.label_19.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter) - self.label_19.setObjectName(_fromUtf8("label_19")) - self.label_20 = QtGui.QLabel(self.widget) - self.label_20.setGeometry(QtCore.QRect(30, 40, 80, 16)) - self.label_20.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter) - self.label_20.setObjectName(_fromUtf8("label_20")) - self.lineEditDays = QtGui.QLineEdit(self.widget) - self.lineEditDays.setGeometry(QtCore.QRect(113, 20, 51, 20)) - self.lineEditDays.setObjectName(_fromUtf8("lineEditDays")) - self.lineEditMonths = QtGui.QLineEdit(self.widget) - self.lineEditMonths.setGeometry(QtCore.QRect(113, 40, 51, 20)) - self.lineEditMonths.setObjectName(_fromUtf8("lineEditMonths")) - self.label_22 = QtGui.QLabel(self.widget) - self.label_22.setGeometry(QtCore.QRect(169, 23, 61, 16)) - self.label_22.setObjectName(_fromUtf8("label_22")) - self.label_23 = QtGui.QLabel(self.widget) - self.label_23.setGeometry(QtCore.QRect(170, 41, 71, 16)) - self.label_23.setObjectName(_fromUtf8("label_23")) - self.gridLayout_5.addWidget(self.widget, 1, 2, 1, 1) - spacerItem15 = QtGui.QSpacerItem(20, 129, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) - self.gridLayout_5.addItem(spacerItem15, 2, 1, 1, 1) - self.tabWidgetSettings.addTab(self.tabResendsExpire, _fromUtf8("")) - self.gridLayout.addWidget(self.tabWidgetSettings, 0, 0, 1, 1) + try: + tempfile.NamedTemporaryFile( + dir=paths.lookupExeFolder(), delete=True + ).close() # should autodelete + except: + self.checkBoxPortableMode.setDisabled(True) - self.retranslateUi(settingsDialog) - self.tabWidgetSettings.setCurrentIndex(0) - QtCore.QObject.connect( # pylint: disable=no-member - self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), settingsDialog.accept) - QtCore.QObject.connect( # pylint: disable=no-member - self.buttonBox, QtCore.SIGNAL(_fromUtf8("rejected()")), settingsDialog.reject) - QtCore.QObject.connect( # pylint: disable=no-member - self.checkBoxAuthentication, - QtCore.SIGNAL( - _fromUtf8("toggled(bool)")), - self.lineEditSocksUsername.setEnabled) - QtCore.QObject.connect( # pylint: disable=no-member - self.checkBoxAuthentication, - QtCore.SIGNAL( - _fromUtf8("toggled(bool)")), - self.lineEditSocksPassword.setEnabled) - QtCore.QMetaObject.connectSlotsByName(settingsDialog) - settingsDialog.setTabOrder(self.tabWidgetSettings, self.checkBoxStartOnLogon) - settingsDialog.setTabOrder(self.checkBoxStartOnLogon, self.checkBoxStartInTray) - settingsDialog.setTabOrder(self.checkBoxStartInTray, self.checkBoxMinimizeToTray) - settingsDialog.setTabOrder(self.checkBoxMinimizeToTray, self.lineEditTCPPort) - settingsDialog.setTabOrder(self.lineEditTCPPort, self.comboBoxProxyType) - settingsDialog.setTabOrder(self.comboBoxProxyType, self.lineEditSocksHostname) - settingsDialog.setTabOrder(self.lineEditSocksHostname, self.lineEditSocksPort) - settingsDialog.setTabOrder(self.lineEditSocksPort, self.checkBoxAuthentication) - settingsDialog.setTabOrder(self.checkBoxAuthentication, self.lineEditSocksUsername) - settingsDialog.setTabOrder(self.lineEditSocksUsername, self.lineEditSocksPassword) - settingsDialog.setTabOrder(self.lineEditSocksPassword, self.checkBoxSocksListen) - settingsDialog.setTabOrder(self.checkBoxSocksListen, self.buttonBox) + if 'darwin' in sys.platform: + self.checkBoxStartOnLogon.setDisabled(True) + self.checkBoxStartOnLogon.setText(_translate( + "MainWindow", "Start-on-login not yet supported on your OS.")) + self.checkBoxMinimizeToTray.setDisabled(True) + self.checkBoxMinimizeToTray.setText(_translate( + "MainWindow", "Minimize-to-tray not yet supported on your OS.")) + self.checkBoxShowTrayNotifications.setDisabled(True) + self.checkBoxShowTrayNotifications.setText(_translate( + "MainWindow", "Tray notifications not yet supported on your OS.")) + elif 'linux' in sys.platform: + self.checkBoxStartOnLogon.setDisabled(True) + self.checkBoxStartOnLogon.setText(_translate( + "MainWindow", "Start-on-login not yet supported on your OS.")) + # On the Network settings tab: + self.lineEditTCPPort.setText(str( + config.get('bitmessagesettings', 'port'))) + self.checkBoxUPnP.setChecked( + config.safeGetBoolean('bitmessagesettings', 'upnp')) + self.checkBoxAuthentication.setChecked( + config.getboolean('bitmessagesettings', 'socksauthentication')) + self.checkBoxSocksListen.setChecked( + config.getboolean('bitmessagesettings', 'sockslisten')) - def retranslateUi(self, settingsDialog): - """Re-translate the UI into the supported languages""" + proxy_type = config.safeGet( + 'bitmessagesettings', 'socksproxytype', 'none') + if proxy_type == 'none': + self.comboBoxProxyType.setCurrentIndex(0) + self.lineEditSocksHostname.setEnabled(False) + self.lineEditSocksPort.setEnabled(False) + self.lineEditSocksUsername.setEnabled(False) + self.lineEditSocksPassword.setEnabled(False) + self.checkBoxAuthentication.setEnabled(False) + self.checkBoxSocksListen.setEnabled(False) + elif proxy_type == 'SOCKS4a': + self.comboBoxProxyType.setCurrentIndex(1) + elif proxy_type == 'SOCKS5': + self.comboBoxProxyType.setCurrentIndex(2) - settingsDialog.setWindowTitle(_translate("settingsDialog", "Settings", None)) - self.checkBoxStartOnLogon.setText(_translate("settingsDialog", "Start Bitmessage on user login", None)) - self.groupBoxTray.setTitle(_translate("settingsDialog", "Tray", None)) - self.checkBoxStartInTray.setText( - _translate( - "settingsDialog", - "Start Bitmessage in the tray (don\'t show main window)", - None)) - self.checkBoxMinimizeToTray.setText(_translate("settingsDialog", "Minimize to tray", None)) - self.checkBoxTrayOnClose.setText(_translate("settingsDialog", "Close to tray", None)) - self.checkBoxHideTrayConnectionNotifications.setText( - _translate("settingsDialog", "Hide connection notifications", None)) - self.checkBoxShowTrayNotifications.setText( - _translate( - "settingsDialog", - "Show notification when message received", - None)) - self.checkBoxPortableMode.setText(_translate("settingsDialog", "Run in Portable Mode", None)) - self.PortableModeDescription.setText( - _translate( - "settingsDialog", - "In Portable Mode, messages and config files are stored in the same directory as the" - " program rather than the normal application-data folder. This makes it convenient to" - " run Bitmessage from a USB thumb drive.", - None)) - self.checkBoxWillinglySendToMobile.setText( - _translate( - "settingsDialog", - "Willingly include unencrypted destination address when sending to a mobile device", - None)) - self.checkBoxUseIdenticons.setText(_translate("settingsDialog", "Use Identicons", None)) - self.checkBoxReplyBelow.setText(_translate("settingsDialog", "Reply below Quote", None)) - self.groupBox.setTitle(_translate("settingsDialog", "Interface Language", None)) - self.languageComboBox.setItemText(0, _translate("settingsDialog", "System Settings", "system")) - self.tabWidgetSettings.setTabText( - self.tabWidgetSettings.indexOf( - self.tabUserInterface), - _translate( - "settingsDialog", "User Interface", None)) - self.groupBox1.setTitle(_translate("settingsDialog", "Listening port", None)) - self.label.setText(_translate("settingsDialog", "Listen for connections on port:", None)) - self.labelUPnP.setText(_translate("settingsDialog", "UPnP:", None)) - self.groupBox_3.setTitle(_translate("settingsDialog", "Bandwidth limit", None)) - self.label_24.setText(_translate("settingsDialog", "Maximum download rate (kB/s): [0: unlimited]", None)) - self.label_25.setText(_translate("settingsDialog", "Maximum upload rate (kB/s): [0: unlimited]", None)) - self.label_26.setText(_translate("settingsDialog", "Maximum outbound connections: [0: none]", None)) - self.groupBox_2.setTitle(_translate("settingsDialog", "Proxy server / Tor", None)) - self.label_2.setText(_translate("settingsDialog", "Type:", None)) - self.label_3.setText(_translate("settingsDialog", "Server hostname:", None)) - self.label_4.setText(_translate("settingsDialog", "Port:", None)) - self.checkBoxAuthentication.setText(_translate("settingsDialog", "Authentication", None)) - self.label_5.setText(_translate("settingsDialog", "Username:", None)) - self.label_6.setText(_translate("settingsDialog", "Pass:", None)) - self.checkBoxSocksListen.setText( - _translate( - "settingsDialog", - "Listen for incoming connections when using proxy", - None)) - self.comboBoxProxyType.setItemText(0, _translate("settingsDialog", "none", None)) - self.comboBoxProxyType.setItemText(1, _translate("settingsDialog", "SOCKS4a", None)) - self.comboBoxProxyType.setItemText(2, _translate("settingsDialog", "SOCKS5", None)) - self.tabWidgetSettings.setTabText( - self.tabWidgetSettings.indexOf( - self.tabNetworkSettings), - _translate( - "settingsDialog", "Network Settings", None)) - self.label_9.setText(_translate("settingsDialog", "Total difficulty:", None)) - self.label_10.setText( - _translate( - "settingsDialog", - "The \'Total difficulty\' affects the absolute amount of work the sender must complete." - " Doubling this value doubles the amount of work.", - None)) - self.label_11.setText(_translate("settingsDialog", "Small message difficulty:", None)) - self.label_8.setText(_translate( - "settingsDialog", - "When 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. ", - None)) - self.label_12.setText( - _translate( - "settingsDialog", - "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.", - None)) - self.tabWidgetSettings.setTabText( - self.tabWidgetSettings.indexOf( - self.tabDemandedDifficulty), - _translate( - "settingsDialog", "Demanded difficulty", None)) - self.label_15.setText( - _translate( - "settingsDialog", - "Here 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.", - None)) - self.label_13.setText(_translate("settingsDialog", "Maximum acceptable total difficulty:", None)) - self.label_14.setText(_translate("settingsDialog", "Maximum acceptable small message difficulty:", None)) - self.tabWidgetSettings.setTabText( - self.tabWidgetSettings.indexOf( - self.tabMaxAcceptableDifficulty), - _translate( - "settingsDialog", "Max acceptable difficulty", None)) - self.labelOpenCL.setText(_translate("settingsDialog", "Hardware GPU acceleration (OpenCL):", None)) - self.label_16.setText(_translate( - "settingsDialog", - "

Bitmessage 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." - "

(Getting your own Bitmessage address into Namecoin is still rather difficult).

" - "

Bitmessage can use either namecoind directly or a running nmcontrol instance.

", - None)) - self.label_17.setText(_translate("settingsDialog", "Host:", None)) - self.label_18.setText(_translate("settingsDialog", "Port:", None)) - self.labelNamecoinUser.setText(_translate("settingsDialog", "Username:", None)) - self.labelNamecoinPassword.setText(_translate("settingsDialog", "Password:", None)) - self.pushButtonNamecoinTest.setText(_translate("settingsDialog", "Test", None)) - self.label_21.setText(_translate("settingsDialog", "Connect to:", None)) - self.radioButtonNamecoinNamecoind.setText(_translate("settingsDialog", "Namecoind", None)) - self.radioButtonNamecoinNmcontrol.setText(_translate("settingsDialog", "NMControl", None)) - self.tabWidgetSettings.setTabText( - self.tabWidgetSettings.indexOf( - self.tabNamecoin), - _translate( - "settingsDialog", "Namecoin integration", None)) - self.label_7.setText(_translate( - "settingsDialog", - "

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.

Leave these input fields blank for the default behavior." - "

", - None)) - self.label_19.setText(_translate("settingsDialog", "Give up after", None)) - self.label_20.setText(_translate("settingsDialog", "and", None)) - self.label_22.setText(_translate("settingsDialog", "days", None)) - self.label_23.setText(_translate("settingsDialog", "months.", None)) - self.tabWidgetSettings.setTabText( - self.tabWidgetSettings.indexOf( - self.tabResendsExpire), - _translate( - "settingsDialog", "Resends Expire", None)) + self.lineEditSocksHostname.setText( + config.get('bitmessagesettings', 'sockshostname')) + self.lineEditSocksPort.setText(str( + config.get('bitmessagesettings', 'socksport'))) + self.lineEditSocksUsername.setText( + config.get('bitmessagesettings', 'socksusername')) + self.lineEditSocksPassword.setText( + config.get('bitmessagesettings', 'sockspassword')) + + self.lineEditMaxDownloadRate.setText(str( + config.get('bitmessagesettings', 'maxdownloadrate'))) + self.lineEditMaxUploadRate.setText(str( + config.get('bitmessagesettings', 'maxuploadrate'))) + self.lineEditMaxOutboundConnections.setText(str( + config.get('bitmessagesettings', 'maxoutboundconnections'))) + + # Demanded difficulty tab + self.lineEditTotalDifficulty.setText(str((float( + config.getint( + 'bitmessagesettings', 'defaultnoncetrialsperbyte') + ) / defaults.networkDefaultProofOfWorkNonceTrialsPerByte))) + self.lineEditSmallMessageDifficulty.setText(str((float( + config.getint( + 'bitmessagesettings', 'defaultpayloadlengthextrabytes') + ) / defaults.networkDefaultPayloadLengthExtraBytes))) + + # Max acceptable difficulty tab + self.lineEditMaxAcceptableTotalDifficulty.setText(str((float( + config.getint( + 'bitmessagesettings', 'maxacceptablenoncetrialsperbyte') + ) / defaults.networkDefaultProofOfWorkNonceTrialsPerByte))) + self.lineEditMaxAcceptableSmallMessageDifficulty.setText(str((float( + config.getint( + 'bitmessagesettings', 'maxacceptablepayloadlengthextrabytes') + ) / defaults.networkDefaultPayloadLengthExtraBytes))) + + # OpenCL + self.comboBoxOpenCL.setEnabled(openclpow.openclAvailable()) + self.comboBoxOpenCL.clear() + self.comboBoxOpenCL.addItem("None") + self.comboBoxOpenCL.addItems(openclpow.vendors) + self.comboBoxOpenCL.setCurrentIndex(0) + for i in range(self.comboBoxOpenCL.count()): + if self.comboBoxOpenCL.itemText(i) == config.safeGet( + 'bitmessagesettings', 'opencl'): + self.comboBoxOpenCL.setCurrentIndex(i) + break + + # Namecoin integration tab + nmctype = config.get('bitmessagesettings', 'namecoinrpctype') + self.lineEditNamecoinHost.setText( + config.get('bitmessagesettings', 'namecoinrpchost')) + self.lineEditNamecoinPort.setText(str( + config.get('bitmessagesettings', 'namecoinrpcport'))) + self.lineEditNamecoinUser.setText( + config.get('bitmessagesettings', 'namecoinrpcuser')) + self.lineEditNamecoinPassword.setText( + config.get('bitmessagesettings', 'namecoinrpcpassword')) + + if nmctype == "namecoind": + self.radioButtonNamecoinNamecoind.setChecked(True) + elif nmctype == "nmcontrol": + self.radioButtonNamecoinNmcontrol.setChecked(True) + self.lineEditNamecoinUser.setEnabled(False) + self.labelNamecoinUser.setEnabled(False) + self.lineEditNamecoinPassword.setEnabled(False) + self.labelNamecoinPassword.setEnabled(False) + else: + assert False + + # Message Resend tab + self.lineEditDays.setText(str( + config.get('bitmessagesettings', 'stopresendingafterxdays'))) + self.lineEditMonths.setText(str( + config.get('bitmessagesettings', 'stopresendingafterxmonths'))) + + def comboBoxProxyTypeChanged(self, comboBoxIndex): + """A callback for currentIndexChanged event of comboBoxProxyType""" + if comboBoxIndex == 0: + self.lineEditSocksHostname.setEnabled(False) + self.lineEditSocksPort.setEnabled(False) + self.lineEditSocksUsername.setEnabled(False) + self.lineEditSocksPassword.setEnabled(False) + self.checkBoxAuthentication.setEnabled(False) + self.checkBoxSocksListen.setEnabled(False) + elif comboBoxIndex in (1, 2): + self.lineEditSocksHostname.setEnabled(True) + self.lineEditSocksPort.setEnabled(True) + self.checkBoxAuthentication.setEnabled(True) + self.checkBoxSocksListen.setEnabled(True) + if self.checkBoxAuthentication.isChecked(): + self.lineEditSocksUsername.setEnabled(True) + self.lineEditSocksPassword.setEnabled(True) + + def getNamecoinType(self): + """ + Check status of namecoin integration radio buttons + and translate it to a string as in the options. + """ + if self.radioButtonNamecoinNamecoind.isChecked(): + return "namecoind" + if self.radioButtonNamecoinNmcontrol.isChecked(): + return "nmcontrol" + assert False + + # Namecoin connection type was changed. + def namecoinTypeChanged(self, checked): # pylint: disable=unused-argument + """A callback for toggled event of radioButtonNamecoinNamecoind""" + nmctype = self.getNamecoinType() + assert nmctype == "namecoind" or nmctype == "nmcontrol" + + isNamecoind = (nmctype == "namecoind") + self.lineEditNamecoinUser.setEnabled(isNamecoind) + self.labelNamecoinUser.setEnabled(isNamecoind) + self.lineEditNamecoinPassword.setEnabled(isNamecoind) + self.labelNamecoinPassword.setEnabled(isNamecoind) + + if isNamecoind: + self.lineEditNamecoinPort.setText(defaults.namecoinDefaultRpcPort) + else: + self.lineEditNamecoinPort.setText("9000") + + def click_pushButtonNamecoinTest(self): + """Test the namecoin settings specified in the settings dialog.""" + self.labelNamecoinTestResult.setText( + _translate("MainWindow", "Testing...")) + nc = namecoin.namecoinConnection({ + 'type': self.getNamecoinType(), + 'host': str(self.lineEditNamecoinHost.text().toUtf8()), + 'port': str(self.lineEditNamecoinPort.text().toUtf8()), + 'user': str(self.lineEditNamecoinUser.text().toUtf8()), + 'password': str(self.lineEditNamecoinPassword.text().toUtf8()) + }) + status, text = nc.test() + self.labelNamecoinTestResult.setText(text) + if status == 'success': + self.parent.namecoin = nc + + def accept(self): + """A callback for accepted event of buttonBox (OK button pressed)""" + # pylint: disable=too-many-branches,too-many-statements + super(SettingsDialog, self).accept() + if self.firstrun: + self.config.remove_option('bitmessagesettings', 'dontconnect') + self.config.set('bitmessagesettings', 'startonlogon', str( + self.checkBoxStartOnLogon.isChecked())) + self.config.set('bitmessagesettings', 'minimizetotray', str( + self.checkBoxMinimizeToTray.isChecked())) + self.config.set('bitmessagesettings', 'trayonclose', str( + self.checkBoxTrayOnClose.isChecked())) + self.config.set( + 'bitmessagesettings', 'hidetrayconnectionnotifications', + str(self.checkBoxHideTrayConnectionNotifications.isChecked())) + self.config.set('bitmessagesettings', 'showtraynotifications', str( + self.checkBoxShowTrayNotifications.isChecked())) + self.config.set('bitmessagesettings', 'startintray', str( + self.checkBoxStartInTray.isChecked())) + self.config.set('bitmessagesettings', 'willinglysendtomobile', str( + self.checkBoxWillinglySendToMobile.isChecked())) + self.config.set('bitmessagesettings', 'useidenticons', str( + self.checkBoxUseIdenticons.isChecked())) + self.config.set('bitmessagesettings', 'replybelow', str( + self.checkBoxReplyBelow.isChecked())) + + lang = str(self.languageComboBox.itemData( + self.languageComboBox.currentIndex()).toString()) + self.config.set('bitmessagesettings', 'userlocale', lang) + self.parent.change_translation() + + if int(self.config.get('bitmessagesettings', 'port')) != int( + self.lineEditTCPPort.text()): + self.config.set( + 'bitmessagesettings', 'port', str(self.lineEditTCPPort.text())) + if not self.config.safeGetBoolean('bitmessagesettings', 'dontconnect'): + self.net_restart_needed = True + + if self.checkBoxUPnP.isChecked() != self.config.safeGetBoolean( + 'bitmessagesettings', 'upnp'): + self.config.set( + 'bitmessagesettings', 'upnp', + str(self.checkBoxUPnP.isChecked())) + if self.checkBoxUPnP.isChecked(): + import upnp + upnpThread = upnp.uPnPThread() + upnpThread.start() + + proxy_type = self.config.safeGet( + 'bitmessagesettings', 'socksproxytype', 'none') + if ( + proxy_type == 'none' and + self.comboBoxProxyType.currentText()[0:5] == 'SOCKS' and + shared.statusIconColor != 'red' + ): + self.net_restart_needed = True + if ( + proxy_type[0:5] == 'SOCKS' and + self.comboBoxProxyType.currentText()[0:5] != 'SOCKS' + ): + self.net_restart_needed = True + self.parent.statusbar.clearMessage() + + self.config.set( + 'bitmessagesettings', 'socksproxytype', + str(self.comboBoxProxyType.currentText()) + if self.comboBoxProxyType.currentText()[0:5] == 'SOCKS' + else 'none' + ) + self.config.set('bitmessagesettings', 'socksauthentication', str( + self.checkBoxAuthentication.isChecked())) + self.config.set('bitmessagesettings', 'sockshostname', str( + self.lineEditSocksHostname.text())) + self.config.set('bitmessagesettings', 'socksport', str( + self.lineEditSocksPort.text())) + self.config.set('bitmessagesettings', 'socksusername', str( + self.lineEditSocksUsername.text())) + self.config.set('bitmessagesettings', 'sockspassword', str( + self.lineEditSocksPassword.text())) + self.config.set('bitmessagesettings', 'sockslisten', str( + self.checkBoxSocksListen.isChecked())) + try: + # Rounding to integers just for aesthetics + self.config.set('bitmessagesettings', 'maxdownloadrate', str( + int(float(self.lineEditMaxDownloadRate.text())))) + self.config.set('bitmessagesettings', 'maxuploadrate', str( + int(float(self.lineEditMaxUploadRate.text())))) + except ValueError: + QtGui.QMessageBox.about( + self, _translate("MainWindow", "Number needed"), + _translate( + "MainWindow", + "Your maximum download and upload rate must be numbers." + " Ignoring what you typed.") + ) + else: + set_rates( + self.config.safeGetInt('bitmessagesettings', 'maxdownloadrate'), + self.config.safeGetInt('bitmessagesettings', 'maxuploadrate')) + + self.config.set('bitmessagesettings', 'maxoutboundconnections', str( + int(float(self.lineEditMaxOutboundConnections.text())))) + + self.config.set( + 'bitmessagesettings', 'namecoinrpctype', self.getNamecoinType()) + self.config.set('bitmessagesettings', 'namecoinrpchost', str( + self.lineEditNamecoinHost.text())) + self.config.set('bitmessagesettings', 'namecoinrpcport', str( + self.lineEditNamecoinPort.text())) + self.config.set('bitmessagesettings', 'namecoinrpcuser', str( + self.lineEditNamecoinUser.text())) + self.config.set('bitmessagesettings', 'namecoinrpcpassword', str( + self.lineEditNamecoinPassword.text())) + self.parent.resetNamecoinConnection() + + # Demanded difficulty tab + if float(self.lineEditTotalDifficulty.text()) >= 1: + self.config.set( + 'bitmessagesettings', 'defaultnoncetrialsperbyte', + str(int( + float(self.lineEditTotalDifficulty.text()) * + defaults.networkDefaultProofOfWorkNonceTrialsPerByte))) + if float(self.lineEditSmallMessageDifficulty.text()) >= 1: + self.config.set( + 'bitmessagesettings', 'defaultpayloadlengthextrabytes', + str(int( + float(self.lineEditSmallMessageDifficulty.text()) * + defaults.networkDefaultPayloadLengthExtraBytes))) + + if self.comboBoxOpenCL.currentText().toUtf8() != self.config.safeGet( + 'bitmessagesettings', 'opencl'): + self.config.set( + 'bitmessagesettings', 'opencl', + str(self.comboBoxOpenCL.currentText())) + queues.workerQueue.put(('resetPoW', '')) + + acceptableDifficultyChanged = False + + if ( + float(self.lineEditMaxAcceptableTotalDifficulty.text()) >= 1 or + float(self.lineEditMaxAcceptableTotalDifficulty.text()) == 0 + ): + if self.config.get( + 'bitmessagesettings', 'maxacceptablenoncetrialsperbyte' + ) != str(int( + float(self.lineEditMaxAcceptableTotalDifficulty.text()) * + defaults.networkDefaultProofOfWorkNonceTrialsPerByte) + ): + # the user changed the max acceptable total difficulty + acceptableDifficultyChanged = True + self.config.set( + 'bitmessagesettings', 'maxacceptablenoncetrialsperbyte', + str(int( + float(self.lineEditMaxAcceptableTotalDifficulty.text()) * + defaults.networkDefaultProofOfWorkNonceTrialsPerByte)) + ) + if ( + float(self.lineEditMaxAcceptableSmallMessageDifficulty.text()) >= 1 or + float(self.lineEditMaxAcceptableSmallMessageDifficulty.text()) == 0 + ): + if self.config.get( + 'bitmessagesettings', 'maxacceptablepayloadlengthextrabytes' + ) != str(int( + float(self.lineEditMaxAcceptableSmallMessageDifficulty.text()) * + defaults.networkDefaultPayloadLengthExtraBytes) + ): + # the user changed the max acceptable small message difficulty + acceptableDifficultyChanged = True + self.config.set( + 'bitmessagesettings', 'maxacceptablepayloadlengthextrabytes', + str(int( + float(self.lineEditMaxAcceptableSmallMessageDifficulty.text()) * + defaults.networkDefaultPayloadLengthExtraBytes)) + ) + if acceptableDifficultyChanged: + # It might now be possible to send msgs which were previously + # marked as toodifficult. Let us change them to 'msgqueued'. + # The singleWorker will try to send them and will again mark + # them as toodifficult if the receiver's required difficulty + # is still higher than we are willing to do. + sqlExecute( + "UPDATE sent SET status='msgqueued'" + " WHERE status='toodifficult'") + queues.workerQueue.put(('sendmessage', '')) + + # UI setting to stop trying to send messages after X days/months + # I'm open to changing this UI to something else if someone has a better idea. + if self.lineEditDays.text() == '' and self.lineEditMonths.text() == '': + # We need to handle this special case. Bitmessage has its + # default behavior. The input is blank/blank + self.config.set('bitmessagesettings', 'stopresendingafterxdays', '') + self.config.set('bitmessagesettings', 'stopresendingafterxmonths', '') + shared.maximumLengthOfTimeToBotherResendingMessages = float('inf') + + try: + days = float(self.lineEditDays.text()) + except ValueError: + self.lineEditDays.setText("0") + days = 0.0 + try: + months = float(self.lineEditMonths.text()) + except ValueError: + self.lineEditMonths.setText("0") + months = 0.0 + + if days >= 0 and months >= 0: + shared.maximumLengthOfTimeToBotherResendingMessages = \ + days * 24 * 60 * 60 + months * 60 * 60 * 24 * 365 / 12 + if shared.maximumLengthOfTimeToBotherResendingMessages < 432000: + # If the time period is less than 5 hours, we give + # zero values to all fields. No message will be sent again. + QtGui.QMessageBox.about( + self, + _translate("MainWindow", "Will not resend ever"), + _translate( + "MainWindow", + "Note that the time limit you entered is less" + " than the amount of time Bitmessage waits for" + " the first resend attempt therefore your" + " messages will never be resent.") + ) + self.config.set( + 'bitmessagesettings', 'stopresendingafterxdays', '0') + self.config.set( + 'bitmessagesettings', 'stopresendingafterxmonths', '0') + shared.maximumLengthOfTimeToBotherResendingMessages = 0.0 + else: + self.config.set( + 'bitmessagesettings', 'stopresendingafterxdays', str(days)) + self.config.set( + 'bitmessagesettings', 'stopresendingafterxmonths', + str(months)) + + self.config.save() + + if self.net_restart_needed: + self.net_restart_needed = False + self.config.setTemp('bitmessagesettings', 'dontconnect', 'true') + self.timer.singleShot( + 5000, lambda: + self.config.setTemp( + 'bitmessagesettings', 'dontconnect', 'false') + ) + + self.parent.updateStartOnLogon() + + if ( + state.appdata != paths.lookupExeFolder() and + self.checkBoxPortableMode.isChecked() + ): + # If we are NOT using portable mode now but the user selected + # that we should... + # Write the keys.dat file to disk in the new location + sqlStoredProcedure('movemessagstoprog') + with open(paths.lookupExeFolder() + 'keys.dat', 'wb') as configfile: + self.config.write(configfile) + # Write the knownnodes.dat file to disk in the new location + knownnodes.saveKnownNodes(paths.lookupExeFolder()) + os.remove(state.appdata + 'keys.dat') + os.remove(state.appdata + 'knownnodes.dat') + previousAppdataLocation = state.appdata + state.appdata = paths.lookupExeFolder() + debug.resetLogging() + try: + os.remove(previousAppdataLocation + 'debug.log') + os.remove(previousAppdataLocation + 'debug.log.1') + except: + pass + + if ( + state.appdata == paths.lookupExeFolder() and + not self.checkBoxPortableMode.isChecked() + ): + # If we ARE using portable mode now but the user selected + # that we shouldn't... + state.appdata = paths.lookupAppdataFolder() + if not os.path.exists(state.appdata): + os.makedirs(state.appdata) + sqlStoredProcedure('movemessagstoappdata') + # Write the keys.dat file to disk in the new location + self.config.save() + # Write the knownnodes.dat file to disk in the new location + knownnodes.saveKnownNodes(state.appdata) + os.remove(paths.lookupExeFolder() + 'keys.dat') + os.remove(paths.lookupExeFolder() + 'knownnodes.dat') + debug.resetLogging() + try: + os.remove(paths.lookupExeFolder() + 'debug.log') + os.remove(paths.lookupExeFolder() + 'debug.log.1') + except: + pass diff --git a/src/bitmessageqt/settings.ui b/src/bitmessageqt/settings.ui index 4aeba3ce..307c06c2 100644 --- a/src/bitmessageqt/settings.ui +++ b/src/bitmessageqt/settings.ui @@ -37,6 +37,18 @@ User Interface + + 8 + + + 8 + + + 8 + + + 8 + @@ -44,20 +56,43 @@ - - - - Start Bitmessage in the tray (don't show main window) + + + + Tray + + + + + Start Bitmessage in the tray (don't show main window) + + + + + + + Minimize to tray + + + true + + + + + + + Close to tray + + + + - + - Minimize to tray - - - true + Hide connection notifications @@ -117,90 +152,15 @@ Interface Language - - - + + + 100 0 - - - System Settings - - - - - English - - - - - Esperanto - - - - - Français - - - - - Deutsch - - - - - Español - - - - - русский - - - - - Norsk - - - - - العربية - - - - - 简体中文 - - - - - 日本語 - - - - - Nederlands - - - - - Česky - - - - - Pirate English - - - - - Other (set in keys.dat) - - @@ -213,6 +173,18 @@ Network Settings + + 8 + + + 8 + + + 8 + + + 8 + @@ -220,26 +192,13 @@ - - - Qt::Horizontal - - - - 125 - 20 - - - - - Listen for connections on port: - + @@ -249,6 +208,26 @@ + + + + UPnP + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + @@ -466,6 +445,18 @@ Demanded difficulty + + 8 + + + 8 + + + 8 + + + 8 + @@ -594,6 +585,18 @@ Max acceptable difficulty + + 8 + + + 8 + + + 8 + + + 8 + @@ -698,6 +701,33 @@ + + + + + + Hardware GPU acceleration (OpenCL): + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + @@ -705,6 +735,18 @@ Namecoin integration + + 8 + + + 8 + + + 8 + + + 8 + @@ -888,6 +930,18 @@ Resends Expire + + 8 + + + 8 + + + 8 + + + 8 + @@ -912,91 +966,69 @@ - + 231 75 + + - - - 10 - 20 - 101 - 20 - - Give up after - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + + + - - - 30 - 40 - 80 - 16 - - and - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - 113 - 20 - 51 - 20 - - + + + + + + 55 + 100 + + - - - - 113 - 40 - 51 - 20 - - + + + + + + 55 + 100 + + - - - - 169 - 23 - 61 - 16 - - + + + days - - - - 170 - 41 - 71 - 16 - - + + + months. + + @@ -1017,7 +1049,14 @@ - + + + + LanguageBox + QComboBox +
bitmessageqt.languagebox
+
+
tabWidgetSettings checkBoxStartOnLogon @@ -1101,5 +1140,53 @@ + + comboBoxProxyType + currentIndexChanged(int) + settingsDialog + comboBoxProxyTypeChanged + + + 20 + 20 + + + 20 + 20 + + + + + radioButtonNamecoinNamecoind + toggled(bool) + settingsDialog + namecoinTypeChanged + + + 20 + 20 + + + 20 + 20 + + + + + pushButtonNamecoinTest + clicked() + settingsDialog + click_pushButtonNamecoinTest + + + 20 + 20 + + + 20 + 20 + + + diff --git a/src/bmconfigparser.py b/src/bmconfigparser.py index 1ee64e94..726d32eb 100644 --- a/src/bmconfigparser.py +++ b/src/bmconfigparser.py @@ -46,6 +46,8 @@ class BMConfigParser(ConfigParser.SafeConfigParser): """Singleton class inherited from ConfigParser.SafeConfigParser with additional methods specific to bitmessage config.""" + _temp = {} + def set(self, section, option, value=None): if self._optcre is self.OPTCRE or value: if not isinstance(value, basestring): @@ -59,6 +61,10 @@ class BMConfigParser(ConfigParser.SafeConfigParser): if section == "bitmessagesettings" and option == "timeformat": return ConfigParser.ConfigParser.get( self, section, option, raw, variables) + try: + return self._temp[section][option] + except KeyError: + pass return ConfigParser.ConfigParser.get( self, section, option, True, variables) except ConfigParser.InterpolationError: @@ -70,6 +76,13 @@ class BMConfigParser(ConfigParser.SafeConfigParser): except (KeyError, ValueError, AttributeError): raise e + def setTemp(self, section, option, value=None): + """Temporary set option to value, not saving.""" + try: + self._temp[section][option] = value + except KeyError: + self._temp[section] = {option: value} + def safeGetBoolean(self, section, field): try: return self.getboolean(section, field) diff --git a/src/helper_bootstrap.py b/src/helper_bootstrap.py deleted file mode 100644 index 1710b09c..00000000 --- a/src/helper_bootstrap.py +++ /dev/null @@ -1,84 +0,0 @@ -import socket - -import knownnodes -import socks -import state -from bmconfigparser import BMConfigParser -from debug import logger - - -def dns(): - """ - DNS bootstrap. This could be programmed to use the SOCKS proxy to do the - DNS lookup some day but for now we will just rely on the entries in - defaultKnownNodes.py. Hopefully either they are up to date or the user - has run Bitmessage recently without SOCKS turned on and received good - bootstrap nodes using that method. - """ - - def try_add_known_node(stream, addr, port, method=''): - try: - socket.inet_aton(addr) - except (TypeError, socket.error): - return - logger.info( - 'Adding %s to knownNodes based on %s DNS bootstrap method', - addr, method) - knownnodes.addKnownNode(stream, state.Peer(addr, port)) - - proxy_type = BMConfigParser().get('bitmessagesettings', 'socksproxytype') - - if proxy_type == 'none': - for port in [8080, 8444]: - try: - for item in socket.getaddrinfo( - 'bootstrap%s.bitmessage.org' % port, 80): - try_add_known_node(1, item[4][0], port) - except: - logger.error( - 'bootstrap%s.bitmessage.org DNS bootstrapping failed.', - port, exc_info=True - ) - elif proxy_type == 'SOCKS5': - knownnodes.createDefaultKnownNodes(onion=True) - logger.debug('Adding default onion knownNodes.') - for port in [8080, 8444]: - logger.debug("Resolving %i through SOCKS...", port) - address_family = socket.AF_INET - sock = socks.socksocket(address_family, socket.SOCK_STREAM) - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - sock.settimeout(20) - proxytype = socks.PROXY_TYPE_SOCKS5 - sockshostname = BMConfigParser().get( - 'bitmessagesettings', 'sockshostname') - socksport = BMConfigParser().getint( - 'bitmessagesettings', 'socksport') - # Do domain name lookups through the proxy; - # though this setting doesn't really matter since we won't - # be doing any domain name lookups anyway. - rdns = True - if BMConfigParser().getboolean( - 'bitmessagesettings', 'socksauthentication'): - socksusername = BMConfigParser().get( - 'bitmessagesettings', 'socksusername') - sockspassword = BMConfigParser().get( - 'bitmessagesettings', 'sockspassword') - sock.setproxy( - proxytype, sockshostname, socksport, rdns, - socksusername, sockspassword) - else: - sock.setproxy( - proxytype, sockshostname, socksport, rdns) - try: - ip = sock.resolve("bootstrap" + str(port) + ".bitmessage.org") - sock.shutdown(socket.SHUT_RDWR) - sock.close() - except: - logger.error("SOCKS DNS resolving failed", exc_info=True) - else: - try_add_known_node(1, ip, port, 'SOCKS') - else: - logger.info( - 'DNS bootstrap skipped because the proxy type does not support' - ' DNS resolution.' - ) diff --git a/src/knownnodes.py b/src/knownnodes.py index f4e00b90..ba21bac7 100644 --- a/src/knownnodes.py +++ b/src/knownnodes.py @@ -11,7 +11,6 @@ import time import state from bmconfigparser import BMConfigParser from debug import logger -from helper_bootstrap import dns knownNodesLock = threading.Lock() knownNodes = {stream: {} for stream in range(1, 4)} @@ -35,10 +34,6 @@ DEFAULT_NODES = ( state.Peer('178.11.46.221', 8444) ) -DEFAULT_NODES_ONION = ( - state.Peer('quzwelsuziwqgpt2.onion', 8444), -) - def json_serialize_knownnodes(output): """ @@ -67,8 +62,7 @@ def json_deserialize_knownnodes(source): if ( not (knownNodesActual or info.get('self')) and - peer not in DEFAULT_NODES and - peer not in DEFAULT_NODES_ONION + peer not in DEFAULT_NODES ): knownNodesActual = True @@ -103,9 +97,9 @@ def addKnownNode(stream, peer, lastseen=None, is_self=False): } -def createDefaultKnownNodes(onion=False): +def createDefaultKnownNodes(): past = time.time() - 2418600 # 28 days - 10 min - for peer in DEFAULT_NODES_ONION if onion else DEFAULT_NODES: + for peer in DEFAULT_NODES: addKnownNode(1, peer, past) saveKnownNodes() @@ -177,39 +171,37 @@ def trimKnownNodes(recAddrStream=1): del knownNodes[recAddrStream][oldest] +def dns(): + """Add DNS names to knownnodes""" + for port in [8080, 8444]: + addKnownNode( + 1, state.Peer('bootstrap%s.bitmessage.org' % port, port)) + + def cleanupKnownNodes(): """ Cleanup knownnodes: remove old nodes and nodes with low rating """ now = int(time.time()) needToWriteKnownNodesToDisk = False - dns_done = False - spawnConnections = not BMConfigParser().safeGetBoolean( - 'bitmessagesettings', 'dontconnect' - ) and BMConfigParser().safeGetBoolean( - 'bitmessagesettings', 'sendoutgoingconnections') with knownNodesLock: for stream in knownNodes: if stream not in state.streamsInWhichIAmParticipating: continue keys = knownNodes[stream].keys() - if len(keys) <= 1: # leave at least one node - if not dns_done and spawnConnections: - dns() - dns_done = True - continue for node in keys: + if len(knownNodes[stream]) <= 1: # leave at least one node + break try: - # scrap old nodes - if (now - knownNodes[stream][node]["lastseen"] > - 2419200): # 28 days + age = now - knownNodes[stream][node]["lastseen"] + # scrap old nodes (age > 28 days) + if age > 2419200: needToWriteKnownNodesToDisk = True del knownNodes[stream][node] continue - # scrap old nodes with low rating - if (now - knownNodes[stream][node]["lastseen"] > 10800 and - knownNodes[stream][node]["rating"] <= + # scrap old nodes (age > 3 hours) with low rating + if (age > 10800 and knownNodes[stream][node]["rating"] <= knownNodesForgetRating): needToWriteKnownNodesToDisk = True del knownNodes[stream][node] diff --git a/src/messagetypes/__init__.py b/src/messagetypes/__init__.py index 06783eac..7319dfd5 100644 --- a/src/messagetypes/__init__.py +++ b/src/messagetypes/__init__.py @@ -1,3 +1,7 @@ +""" +src/messagetypes/__init__.py +============================ +""" from importlib import import_module from os import path, listdir from string import lower @@ -6,12 +10,15 @@ from debug import logger import messagetypes import paths -class MsgBase(object): - def encode(self): + +class MsgBase(object): # pylint: disable=too-few-public-methods + """Base class for message types""" + def __init__(self): self.data = {"": lower(type(self).__name__)} def constructObject(data): + """Constructing an object""" whitelist = ["message"] if data[""] not in whitelist: return None @@ -32,6 +39,7 @@ def constructObject(data): else: return returnObj + if paths.frozen is not None: import messagetypes.message import messagetypes.vote diff --git a/src/messagetypes/message.py b/src/messagetypes/message.py index f52c6b35..cd5bf762 100644 --- a/src/messagetypes/message.py +++ b/src/messagetypes/message.py @@ -1,24 +1,30 @@ +""" +src/messagetypes/message.py +=========================== +""" from debug import logger from messagetypes import MsgBase class Message(MsgBase): - def __init__(self): - return + """Encapsulate a message""" + # pylint: disable=attribute-defined-outside-init def decode(self, data): + """Decode a message""" # UTF-8 and variable type validator - if type(data["subject"]) is str: + if isinstance(data["subject"], str): self.subject = unicode(data["subject"], 'utf-8', 'replace') else: self.subject = unicode(str(data["subject"]), 'utf-8', 'replace') - if type(data["body"]) is str: + if isinstance(data["body"], str): self.body = unicode(data["body"], 'utf-8', 'replace') else: self.body = unicode(str(data["body"]), 'utf-8', 'replace') def encode(self, data): - super(Message, self).encode() + """Encode a message""" + super(Message, self).__init__() try: self.data["subject"] = data["subject"] self.data["body"] = data["body"] @@ -27,5 +33,6 @@ class Message(MsgBase): return self.data def process(self): + """Process a message""" logger.debug("Subject: %i bytes", len(self.subject)) logger.debug("Body: %i bytes", len(self.body)) diff --git a/src/messagetypes/vote.py b/src/messagetypes/vote.py index df8d267f..e128e9ba 100644 --- a/src/messagetypes/vote.py +++ b/src/messagetypes/vote.py @@ -1,23 +1,31 @@ +""" +src/messagetypes/vote.py +======================== +""" from debug import logger from messagetypes import MsgBase + class Vote(MsgBase): - def __init__(self): - return + """Module used to vote""" def decode(self, data): + """decode a vote""" + # pylint: disable=attribute-defined-outside-init self.msgid = data["msgid"] self.vote = data["vote"] def encode(self, data): - super(Vote, self).encode() + """Encode a vote""" + super(Vote, self).__init__() try: self.data["msgid"] = data["msgid"] self.data["vote"] = data["vote"] except KeyError as e: - logger.error("Missing key %s", e.name) + logger.error("Missing key %s", e) return self.data def process(self): + """Encode a vote""" logger.debug("msgid: %s", self.msgid) logger.debug("vote: %s", self.vote) diff --git a/src/network/connectionchooser.py b/src/network/connectionchooser.py index e116ec53..53ce30b7 100644 --- a/src/network/connectionchooser.py +++ b/src/network/connectionchooser.py @@ -1,3 +1,4 @@ +# pylint: disable=too-many-branches import random # nosec import knownnodes @@ -38,7 +39,10 @@ def chooseConnection(stream): for _ in range(50): peer = random.choice(knownnodes.knownNodes[stream].keys()) try: - rating = knownnodes.knownNodes[stream][peer]['rating'] + peer_info = knownnodes.knownNodes[stream][peer] + if peer_info.get('self'): + continue + rating = peer_info["rating"] except TypeError: logger.warning('Error in %s', peer) rating = 0 @@ -46,7 +50,8 @@ def chooseConnection(stream): # onion addresses have a higher priority when SOCKS if peer.host.endswith('.onion') and rating > 0: rating = 1 - else: + # TODO: need better check + elif not peer.host.startswith('bootstrap'): encodedAddr = protocol.encodeHost(peer.host) # don't connect to local IPs when using SOCKS if not protocol.checkIPAddress(encodedAddr, False): diff --git a/src/network/connectionpool.py b/src/network/connectionpool.py index 461c2b77..4d16df49 100644 --- a/src/network/connectionpool.py +++ b/src/network/connectionpool.py @@ -8,7 +8,6 @@ import socket import time import asyncore_pollchoose as asyncore -import helper_bootstrap import helper_random import knownnodes import protocol @@ -19,7 +18,8 @@ from debug import logger from proxy import Proxy from singleton import Singleton from tcp import ( - TCPServer, Socks5BMConnection, Socks4aBMConnection, TCPConnection) + bootstrap, Socks4aBMConnection, Socks5BMConnection, + TCPConnection, TCPServer) from udp import UDPSocket @@ -160,7 +160,35 @@ class BMConnectionPool(object): udpSocket = UDPSocket(host=bind, announcing=True) self.udpSockets[udpSocket.listening.host] = udpSocket - def loop(self): # pylint: disable=too-many-branches, too-many-statements + def startBootstrappers(self): + """Run the process of resolving bootstrap hostnames""" + proxy_type = BMConfigParser().safeGet( + 'bitmessagesettings', 'socksproxytype') + # A plugins may be added here + hostname = None + if not proxy_type or proxy_type == 'none': + connection_base = TCPConnection + elif proxy_type == 'SOCKS5': + connection_base = Socks5BMConnection + hostname = helper_random.randomchoice([ + 'quzwelsuziwqgpt2.onion', None + ]) + elif proxy_type == 'SOCKS4a': + connection_base = Socks4aBMConnection # FIXME: I cannot test + else: + # This should never happen because socksproxytype setting + # is handled in bitmessagemain before starting the connectionpool + return + + bootstrapper = bootstrap(connection_base) + if not hostname: + port = helper_random.randomchoice([8080, 8444]) + hostname = 'bootstrap%s.bitmessage.org' % port + else: + port = 8444 + self.addConnection(bootstrapper(hostname, port)) + + def loop(self): # pylint: disable=too-many-branches,too-many-statements """Main Connectionpool's loop""" # defaults to empty loop if outbound connections are maxed spawnConnections = False @@ -185,7 +213,8 @@ class BMConnectionPool(object): # pylint: disable=too-many-nested-blocks if spawnConnections: if not knownnodes.knownNodesActual: - helper_bootstrap.dns() + self.startBootstrappers() + knownnodes.knownNodesActual = True if not self.bootstrapped: self.bootstrapped = True Proxy.proxy = ( diff --git a/src/network/socks5.py b/src/network/socks5.py index 86616f30..e0cb7202 100644 --- a/src/network/socks5.py +++ b/src/network/socks5.py @@ -8,6 +8,7 @@ src/network/socks5.py import socket import struct +import state from proxy import GeneralProxyError, Proxy, ProxyError @@ -160,9 +161,6 @@ class Socks5(Proxy): class Socks5Connection(Socks5): """Child socks5 class used for making outbound connections.""" - def __init__(self, address): - Socks5.__init__(self, address=address) - def state_auth_done(self): """Request connection to be made""" # Now we can request the actual connection @@ -172,9 +170,9 @@ class Socks5Connection(Socks5): try: self.ipaddr = socket.inet_aton(self.destination[0]) self.append_write_buf(chr(0x01).encode() + self.ipaddr) - except socket.error: + except socket.error: # may be IPv6! # Well it's not an IP number, so it's probably a DNS name. - if Proxy._remote_dns: # pylint: disable=protected-access + if self._remote_dns: # Resolve remotely self.ipaddr = None self.append_write_buf(chr(0x03).encode() + chr( @@ -202,7 +200,7 @@ class Socks5Resolver(Socks5): def __init__(self, host): self.host = host self.port = 8444 - Socks5.__init__(self, address=(self.host, self.port)) + Socks5.__init__(self, address=state.Peer(self.host, self.port)) def state_auth_done(self): """Perform resolving""" diff --git a/src/network/tcp.py b/src/network/tcp.py index 5ebd6a21..da02df2f 100644 --- a/src/network/tcp.py +++ b/src/network/tcp.py @@ -73,11 +73,14 @@ class TCPConnection(BMProto, TLSDispatcher): logger.debug( 'Connecting to %s:%i', self.destination.host, self.destination.port) - encodedAddr = protocol.encodeHost(self.destination.host) - self.local = all([ - protocol.checkIPAddress(encodedAddr, True), - not protocol.checkSocksIP(self.destination.host) - ]) + try: + self.local = ( + protocol.checkIPAddress( + protocol.encodeHost(self.destination.host), True) and + not protocol.checkSocksIP(self.destination.host) + ) + except socket.error: + pass # it's probably a hostname ObjectTracker.__init__(self) # pylint: disable=non-parent-init-called self.bm_proto_reset() self.set_state("bm_header", expectBytes=protocol.Header.size) @@ -322,6 +325,39 @@ class Socks4aBMConnection(Socks4aConnection, TCPConnection): return True +def bootstrap(connection_class): + """Make bootstrapper class for connection type (connection_class)""" + class Bootstrapper(connection_class): + """Base class for bootstrappers""" + _connection_base = connection_class + + def __init__(self, host, port): + self._connection_base.__init__(self, state.Peer(host, port)) + self.close_reason = self._succeed = False + + def bm_command_addr(self): + """ + Got addr message - the bootstrap succeed. + Let BMProto process the addr message and switch state to 'close' + """ + BMProto.bm_command_addr(self) + self._succeed = True + # pylint: disable=attribute-defined-outside-init + self.close_reason = "Thanks for bootstrapping!" + self.set_state("close") + + def handle_close(self): + """ + After closing the connection switch knownnodes.knownNodesActual + back to False if the bootstrapper failed. + """ + self._connection_base.handle_close(self) + if not self._succeed: + knownnodes.knownNodesActual = False + + return Bootstrapper + + class TCPServer(AdvancedDispatcher): """TCP connection server for Bitmessage protocol""" diff --git a/src/plugins/indicator_libmessaging.py b/src/plugins/indicator_libmessaging.py index 36178663..ab2e833e 100644 --- a/src/plugins/indicator_libmessaging.py +++ b/src/plugins/indicator_libmessaging.py @@ -1,4 +1,8 @@ # -*- coding: utf-8 -*- +""" +src/plugins/indicator_libmessaging.py +===================================== +""" import gi gi.require_version('MessagingMenu', '1.0') # noqa:E402 @@ -9,6 +13,7 @@ from pybitmessage.tr import _translate class IndicatorLibmessaging(object): + """Plugin for libmessage indicator""" def __init__(self, form): try: self.app = MessagingMenu.App(desktop_id='pybitmessage.desktop') @@ -32,15 +37,18 @@ class IndicatorLibmessaging(object): if self.app: self.app.unregister() - def activate(self, app, source): + def activate(self, app, source): # pylint: disable=unused-argument + """Activate the libmessaging indicator plugin""" self.form.appIndicatorInbox( self.new_message_item if source == 'messages' else self.new_broadcast_item ) - # show the number of unread messages and subscriptions - # on the messaging menu def show_unread(self, draw_attention=False): + """ + show the number of unread messages and subscriptions + on the messaging menu + """ for source, count in zip( ('messages', 'subscriptions'), self.form.getUnread() diff --git a/src/plugins/menu_qrcode.py b/src/plugins/menu_qrcode.py index c5a21ac3..1c2ebe44 100644 --- a/src/plugins/menu_qrcode.py +++ b/src/plugins/menu_qrcode.py @@ -1,5 +1,8 @@ # -*- coding: utf-8 -*- """ +src/plugins/menu_qrcode.py +========================== + A menu plugin showing QR-Code for bitmessage address in modal dialog. """ @@ -12,9 +15,10 @@ from pybitmessage.tr import _translate # http://stackoverflow.com/questions/20452486 -class Image(qrcode.image.base.BaseImage): +class Image(qrcode.image.base.BaseImage): # pylint: disable=abstract-method """Image output class for qrcode using QPainter""" - def __init__(self, border, width, box_size): + + def __init__(self, border, width, box_size): # pylint: disable=super-init-not-called self.border = border self.width = width self.box_size = box_size diff --git a/src/plugins/notification_notify2.py b/src/plugins/notification_notify2.py index 3fd935c4..b4cd045d 100644 --- a/src/plugins/notification_notify2.py +++ b/src/plugins/notification_notify2.py @@ -1,4 +1,8 @@ # -*- coding: utf-8 -*- +""" +src/plugins/notification_notify2.py +=================================== +""" import gi gi.require_version('Notify', '0.7') @@ -6,10 +10,13 @@ from gi.repository import Notify Notify.init('pybitmessage') + def connect_plugin(title, subtitle, category, label, icon): + """Plugin for notify2""" if not icon: icon = 'mail-message-new' if category == 2 else 'pybitmessage' connect_plugin.notification.update(title, subtitle, icon) connect_plugin.notification.show() + connect_plugin.notification = Notify.Notification.new("Init", "Init") diff --git a/src/plugins/plugin.py b/src/plugins/plugin.py index 6601adaf..e671a73f 100644 --- a/src/plugins/plugin.py +++ b/src/plugins/plugin.py @@ -1,5 +1,8 @@ # -*- coding: utf-8 -*- - +""" +src/plugins/plugin.py +=================================== +""" import pkg_resources diff --git a/src/plugins/proxyconfig_stem.py b/src/plugins/proxyconfig_stem.py index 75605c07..5bb9a726 100644 --- a/src/plugins/proxyconfig_stem.py +++ b/src/plugins/proxyconfig_stem.py @@ -1,5 +1,8 @@ # -*- coding: utf-8 -*- - +""" +src/plugins/proxyconfig_stem.py +=================================== +""" import os import logging import random # noseq @@ -8,6 +11,7 @@ import tempfile import stem import stem.control import stem.process +import stem.version class DebugLogger(object): @@ -28,14 +32,14 @@ class DebugLogger(object): # Plugin's debug or unexpected log line from tor self._logger.debug(line) else: - self._logger.log(self._levels.get(level, 10), '(tor)' + line) + self._logger.log(self._levels.get(level, 10), '(tor) %s', line) -def connect_plugin(config): +def connect_plugin(config): # pylint: disable=too-many-branches """Run stem proxy configurator""" logwrite = DebugLogger() if config.safeGet('bitmessagesettings', 'sockshostname') not in ( - 'localhost', '127.0.0.1', '' + 'localhost', '127.0.0.1', '' ): # remote proxy is choosen for outbound connections, # nothing to do here, but need to set socksproxytype to SOCKS5! @@ -60,8 +64,14 @@ def connect_plugin(config): # So if there is a system wide tor, use it for outbound connections. try: stem.process.launch_tor_with_config( - tor_config, take_ownership=True, init_msg_handler=logwrite) + tor_config, take_ownership=True, timeout=20, + init_msg_handler=logwrite) except OSError: + if not attempt: + try: + stem.version.get_system_tor_version() + except IOError: + return continue else: logwrite('Started tor on port %s' % port) @@ -108,3 +118,5 @@ def connect_plugin(config): onionhostname, 'keytype', response.private_key_type) config.save() config.set('bitmessagesettings', 'socksproxytype', 'SOCKS5') + + return True diff --git a/src/plugins/sound_canberra.py b/src/plugins/sound_canberra.py index 094901ed..dbb4baed 100644 --- a/src/plugins/sound_canberra.py +++ b/src/plugins/sound_canberra.py @@ -1,4 +1,8 @@ # -*- coding: utf-8 -*- +""" +src/plugins/proxyconfig_stem.py +=================================== +""" from pybitmessage.bitmessageqt import sound @@ -14,7 +18,8 @@ _theme = { } -def connect_plugin(category, label=None): +def connect_plugin(category, label=None): # pylint: disable=unused-argument + """This function implements the entry point.""" try: _canberra.play(0, pycanberra.CA_PROP_EVENT_ID, _theme[category], None) except (KeyError, pycanberra.CanberraException): diff --git a/src/plugins/sound_gstreamer.py b/src/plugins/sound_gstreamer.py index 062da3f9..32a0aa65 100644 --- a/src/plugins/sound_gstreamer.py +++ b/src/plugins/sound_gstreamer.py @@ -1,5 +1,8 @@ # -*- coding: utf-8 -*- - +""" +src/plugins/sound_gstreamer.py +=================================== +""" import gi gi.require_version('Gst', '1.0') from gi.repository import Gst # noqa: E402 @@ -9,6 +12,7 @@ _player = Gst.ElementFactory.make("playbin", "player") def connect_plugin(sound_file): + """Entry point for sound file""" _player.set_state(Gst.State.NULL) _player.set_property("uri", "file://" + sound_file) _player.set_state(Gst.State.PLAYING) diff --git a/src/plugins/sound_playfile.py b/src/plugins/sound_playfile.py index c8216d07..6396c319 100644 --- a/src/plugins/sound_playfile.py +++ b/src/plugins/sound_playfile.py @@ -1,10 +1,14 @@ # -*- coding: utf-8 -*- - +""" +src/plugins/sound_playfile.py +=================================== +""" try: import winsound def connect_plugin(sound_file): + """Plugin's entry point""" winsound.PlaySound(sound_file, winsound.SND_FILENAME) except ImportError: import os @@ -18,7 +22,8 @@ except ImportError: args, stdout=FNULL, stderr=subprocess.STDOUT, close_fds=True) def connect_plugin(sound_file): - global play_cmd + """This function implements the entry point.""" + global play_cmd # pylint: disable=global-statement ext = os.path.splitext(sound_file)[-1] try: diff --git a/src/protocol.py b/src/protocol.py index ab81e5e5..1031b950 100644 --- a/src/protocol.py +++ b/src/protocol.py @@ -264,7 +264,10 @@ def assembleVersionMessage(remoteHost, remotePort, participatingStreams, server= else: # use first 16 bytes if host data is longer # for example in case of onion v3 service - payload += encodeHost(remoteHost)[:16] + try: + payload += encodeHost(remoteHost)[:16] + except socket.error: + payload += encodeHost('127.0.0.1') payload += pack('>H', remotePort) # remote IPv6 and port # bitflags of the services I offer. diff --git a/src/pyelliptic/__init__.py b/src/pyelliptic/__init__.py index 1d6a928f..7aa666e0 100644 --- a/src/pyelliptic/__init__.py +++ b/src/pyelliptic/__init__.py @@ -1,3 +1,7 @@ +""" +src/pyelliptic/__init__.py +===================================== +""" # Copyright (C) 2010 # Author: Yann GUIBET # Contact: diff --git a/src/pyelliptic/cipher.py b/src/pyelliptic/cipher.py index bc1af6b0..d02b743a 100644 --- a/src/pyelliptic/cipher.py +++ b/src/pyelliptic/cipher.py @@ -1,5 +1,9 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +""" +src/pyelliptic/cipher.py +======================== +""" # Copyright (C) 2011 Yann GUIBET # See LICENSE for details. @@ -7,7 +11,8 @@ from openssl import OpenSSL -class Cipher: +# pylint: disable=redefined-builtin +class Cipher(object): """ Symmetric encryption @@ -44,30 +49,34 @@ class Cipher: @staticmethod def get_blocksize(ciphername): + """This Method returns cipher blocksize""" cipher = OpenSSL.get_cipher(ciphername) return cipher.get_blocksize() @staticmethod def gen_IV(ciphername): + """Generate random initialization vector""" cipher = OpenSSL.get_cipher(ciphername) return OpenSSL.rand(cipher.get_blocksize()) def update(self, input): + """Update result with more data""" i = OpenSSL.c_int(0) buffer = OpenSSL.malloc(b"", len(input) + self.cipher.get_blocksize()) inp = OpenSSL.malloc(input, len(input)) if OpenSSL.EVP_CipherUpdate(self.ctx, OpenSSL.byref(buffer), OpenSSL.byref(i), inp, len(input)) == 0: raise Exception("[OpenSSL] EVP_CipherUpdate FAIL ...") - return buffer.raw[0:i.value] + return buffer.raw[0:i.value] # pylint: disable=invalid-slice-index def final(self): + """Returning the final value""" i = OpenSSL.c_int(0) buffer = OpenSSL.malloc(b"", self.cipher.get_blocksize()) if (OpenSSL.EVP_CipherFinal_ex(self.ctx, OpenSSL.byref(buffer), OpenSSL.byref(i))) == 0: raise Exception("[OpenSSL] EVP_CipherFinal_ex FAIL ...") - return buffer.raw[0:i.value] + return buffer.raw[0:i.value] # pylint: disable=invalid-slice-index def ciphering(self, input): """ @@ -77,6 +86,7 @@ class Cipher: return buff + self.final() def __del__(self): + # pylint: disable=protected-access if OpenSSL._hexversion > 0x10100000 and not OpenSSL._libreSSL: OpenSSL.EVP_CIPHER_CTX_reset(self.ctx) else: diff --git a/src/pyelliptic/hash.py b/src/pyelliptic/hash.py index f2240500..c21dd6a4 100644 --- a/src/pyelliptic/hash.py +++ b/src/pyelliptic/hash.py @@ -1,6 +1,9 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- - +""" +src/pyelliptic/hash.py +===================== +""" # Copyright (C) 2011 Yann GUIBET # See LICENSE for details. @@ -27,10 +30,10 @@ def _equals_str(a, b): def equals(a, b): + """Compare two strings or bytearrays""" if isinstance(a, str): return _equals_str(a, b) - else: - return _equals_bytes(a, b) + return _equals_bytes(a, b) def hmac_sha256(k, m): @@ -58,6 +61,7 @@ def hmac_sha512(k, m): def pbkdf2(password, salt=None, i=10000, keylen=64): + """Key derivation function using SHA256""" if salt is None: salt = OpenSSL.rand(8) p_password = OpenSSL.malloc(password, len(password)) diff --git a/src/pyelliptic/openssl.py b/src/pyelliptic/openssl.py index ac3996b1..0aa30589 100644 --- a/src/pyelliptic/openssl.py +++ b/src/pyelliptic/openssl.py @@ -1,10 +1,14 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- - +""" +src/pyelliptic/openssl.py +===================== +""" # Copyright (C) 2011 Yann GUIBET # See LICENSE for details. # # Software slightly changed by Jonathan Warren +# pylint: disable=protected-access import sys import ctypes @@ -13,6 +17,9 @@ OpenSSL = None from kivy.utils import platform class CipherName: + """Class returns cipher name, pointer and blocksize""" + + # pylint: disable=old-style-class def __init__(self, name, pointer, blocksize): self._name = name self._pointer = pointer @@ -24,16 +31,20 @@ class CipherName: " | Function pointer : " + str(self._pointer) def get_pointer(self): + """This method returns cipher pointer""" return self._pointer() def get_name(self): + """This method returns cipher name""" return self._name def get_blocksize(self): + """This method returns cipher blocksize""" return self._blocksize def get_version(library): + """This function return version, hexversion and cflages""" version = None hexversion = None cflags = None @@ -68,6 +79,7 @@ class _OpenSSL: """ Wrapper for OpenSSL using ctypes """ + # pylint: disable=too-many-statements, too-many-instance-attributes, old-style-class def __init__(self, library): """ Build the wrapper @@ -594,6 +606,7 @@ class _OpenSSL: """ returns the name of a elliptic curve with his id """ + # pylint: disable=redefined-builtin res = None for i in self.curves: if self.curves[i] == id: @@ -607,6 +620,7 @@ class _OpenSSL: """ OpenSSL random function """ + # pylint: disable=redefined-builtin buffer = self.malloc(0, size) # This pyelliptic library, by default, didn't check the return value of RAND_bytes. It is # evidently possible that it returned an error and not-actually-random data. However, in @@ -623,6 +637,7 @@ class _OpenSSL: """ returns a create_string_buffer (ctypes) """ + # pylint: disable=redefined-builtin buffer = None if data != 0: if sys.version_info.major == 3 and isinstance(data, type('')): @@ -634,6 +649,8 @@ class _OpenSSL: def loadOpenSSL(): + """This function finds and load the OpenSSL library""" + # pylint: disable=global-statement global OpenSSL from os import path, environ from ctypes.util import find_library diff --git a/src/socks/BUGS b/src/socks/BUGS deleted file mode 100644 index fa8ccfad..00000000 --- a/src/socks/BUGS +++ /dev/null @@ -1,25 +0,0 @@ -SocksiPy version 1.00 -A Python SOCKS module. -(C) 2006 Dan-Haim. All rights reserved. -See LICENSE file for details. - - -KNOWN BUGS AND ISSUES ----------------------- - -There are no currently known bugs in this module. -There are some limits though: - -1) Only outgoing connections are supported - This module currently only supports -outgoing TCP connections, though some servers may support incoming connections -as well. UDP is not supported either. - -2) GSSAPI Socks5 authenticaion is not supported. - - -If you find any new bugs, please contact the author at: - -negativeiq@users.sourceforge.net - - -Thank you! diff --git a/src/socks/LICENSE b/src/socks/LICENSE deleted file mode 100644 index 04b6b1f3..00000000 --- a/src/socks/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -Copyright 2006 Dan-Haim. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. -3. Neither the name of Dan Haim nor the names of his contributors may be used - to endorse or promote products derived from this software without specific - prior written permission. - -THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED -WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO -EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT -OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE. diff --git a/src/socks/README b/src/socks/README deleted file mode 100644 index a52f55f3..00000000 --- a/src/socks/README +++ /dev/null @@ -1,201 +0,0 @@ -SocksiPy version 1.00 -A Python SOCKS module. -(C) 2006 Dan-Haim. All rights reserved. -See LICENSE file for details. - - -WHAT IS A SOCKS PROXY? -A SOCKS proxy is a proxy server at the TCP level. In other words, it acts as -a tunnel, relaying all traffic going through it without modifying it. -SOCKS proxies can be used to relay traffic using any network protocol that -uses TCP. - -WHAT IS SOCKSIPY? -This Python module allows you to create TCP connections through a SOCKS -proxy without any special effort. - -PROXY COMPATIBILITY -SocksiPy is compatible with three different types of proxies: -1. SOCKS Version 4 (Socks4), including the Socks4a extension. -2. SOCKS Version 5 (Socks5). -3. HTTP Proxies which support tunneling using the CONNECT method. - -SYSTEM REQUIREMENTS -Being written in Python, SocksiPy can run on any platform that has a Python -interpreter and TCP/IP support. -This module has been tested with Python 2.3 and should work with greater versions -just as well. - - -INSTALLATION -------------- - -Simply copy the file "socks.py" to your Python's lib/site-packages directory, -and you're ready to go. - - -USAGE ------- - -First load the socks module with the command: - ->>> import socks ->>> - -The socks module provides a class called "socksocket", which is the base to -all of the module's functionality. -The socksocket object has the same initialization parameters as the normal socket -object to ensure maximal compatibility, however it should be noted that socksocket -will only function with family being AF_INET and type being SOCK_STREAM. -Generally, it is best to initialize the socksocket object with no parameters - ->>> s = socks.socksocket() ->>> - -The socksocket object has an interface which is very similiar to socket's (in fact -the socksocket class is derived from socket) with a few extra methods. -To select the proxy server you would like to use, use the setproxy method, whose -syntax is: - -setproxy(proxytype, addr[, port[, rdns[, username[, password]]]]) - -Explaination of the parameters: - -proxytype - The type of the proxy server. This can be one of three possible -choices: PROXY_TYPE_SOCKS4, PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP for Socks4, -Socks5 and HTTP servers respectively. - -addr - The IP address or DNS name of the proxy server. - -port - The port of the proxy server. Defaults to 1080 for socks and 8080 for http. - -rdns - This is a boolean flag than modifies the behavior regarding DNS resolving. -If it is set to True, DNS resolving will be preformed remotely, on the server. -If it is set to False, DNS resolving will be preformed locally. Please note that -setting this to True with Socks4 servers actually use an extension to the protocol, -called Socks4a, which may not be supported on all servers (Socks5 and http servers -always support DNS). The default is True. - -username - For Socks5 servers, this allows simple username / password authentication -with the server. For Socks4 servers, this parameter will be sent as the userid. -This parameter is ignored if an HTTP server is being used. If it is not provided, -authentication will not be used (servers may accept unauthentication requests). - -password - This parameter is valid only for Socks5 servers and specifies the -respective password for the username provided. - -Example of usage: - ->>> s.setproxy(socks.PROXY_TYPE_SOCKS5,"socks.example.com") ->>> - -After the setproxy method has been called, simply call the connect method with the -traditional parameters to establish a connection through the proxy: - ->>> s.connect(("www.sourceforge.net",80)) ->>> - -Connection will take a bit longer to allow negotiation with the proxy server. -Please note that calling connect without calling setproxy earlier will connect -without a proxy (just like a regular socket). - -Errors: Any errors in the connection process will trigger exceptions. The exception -may either be generated by the underlying socket layer or may be custom module -exceptions, whose details follow: - -class ProxyError - This is a base exception class. It is not raised directly but -rather all other exception classes raised by this module are derived from it. -This allows an easy way to catch all proxy-related errors. - -class GeneralProxyError - When thrown, it indicates a problem which does not fall -into another category. The parameter is a tuple containing an error code and a -description of the error, from the following list: -1 - invalid data - This error means that unexpected data has been received from -the server. The most common reason is that the server specified as the proxy is -not really a Socks4/Socks5/HTTP proxy, or maybe the proxy type specified is wrong. -4 - bad proxy type - This will be raised if the type of the proxy supplied to the -setproxy function was not PROXY_TYPE_SOCKS4/PROXY_TYPE_SOCKS5/PROXY_TYPE_HTTP. -5 - bad input - This will be raised if the connect method is called with bad input -parameters. - -class Socks5AuthError - This indicates that the connection through a Socks5 server -failed due to an authentication problem. The parameter is a tuple containing a -code and a description message according to the following list: - -1 - authentication is required - This will happen if you use a Socks5 server which -requires authentication without providing a username / password at all. -2 - all offered authentication methods were rejected - This will happen if the proxy -requires a special authentication method which is not supported by this module. -3 - unknown username or invalid password - Self descriptive. - -class Socks5Error - This will be raised for Socks5 errors which are not related to -authentication. The parameter is a tuple containing a code and a description of the -error, as given by the server. The possible errors, according to the RFC are: - -1 - General SOCKS server failure - If for any reason the proxy server is unable to -fulfill your request (internal server error). -2 - connection not allowed by ruleset - If the address you're trying to connect to -is blacklisted on the server or requires authentication. -3 - Network unreachable - The target could not be contacted. A router on the network -had replied with a destination net unreachable error. -4 - Host unreachable - The target could not be contacted. A router on the network -had replied with a destination host unreachable error. -5 - Connection refused - The target server has actively refused the connection -(the requested port is closed). -6 - TTL expired - The TTL value of the SYN packet from the proxy to the target server -has expired. This usually means that there are network problems causing the packet -to be caught in a router-to-router "ping-pong". -7 - Command not supported - The client has issued an invalid command. When using this -module, this error should not occur. -8 - Address type not supported - The client has provided an invalid address type. -When using this module, this error should not occur. - -class Socks4Error - This will be raised for Socks4 errors. The parameter is a tuple -containing a code and a description of the error, as given by the server. The -possible error, according to the specification are: - -1 - Request rejected or failed - Will be raised in the event of an failure for any -reason other then the two mentioned next. -2 - request rejected because SOCKS server cannot connect to identd on the client - -The Socks server had tried an ident lookup on your computer and has failed. In this -case you should run an identd server and/or configure your firewall to allow incoming -connections to local port 113 from the remote server. -3 - request rejected because the client program and identd report different user-ids - -The Socks server had performed an ident lookup on your computer and has received a -different userid than the one you have provided. Change your userid (through the -username parameter of the setproxy method) to match and try again. - -class HTTPError - This will be raised for HTTP errors. The parameter is a tuple -containing the HTTP status code and the description of the server. - - -After establishing the connection, the object behaves like a standard socket. -Call the close method to close the connection. - -In addition to the socksocket class, an additional function worth mentioning is the -setdefaultproxy function. The parameters are the same as the setproxy method. -This function will set default proxy settings for newly created socksocket objects, -in which the proxy settings haven't been changed via the setproxy method. -This is quite useful if you wish to force 3rd party modules to use a socks proxy, -by overriding the socket object. -For example: - ->>> socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5,"socks.example.com") ->>> socket.socket = socks.socksocket ->>> urllib.urlopen("http://www.sourceforge.net/") - - -PROBLEMS ---------- - -If you have any problems using this module, please first refer to the BUGS file -(containing current bugs and issues). If your problem is not mentioned you may -contact the author at the following E-Mail address: - -negativeiq@users.sourceforge.net - -Please allow some time for your question to be received and handled. - - -Dan-Haim, -Author. diff --git a/src/socks/__init__.py b/src/socks/__init__.py deleted file mode 100644 index 7fd2cba3..00000000 --- a/src/socks/__init__.py +++ /dev/null @@ -1,476 +0,0 @@ -"""SocksiPy - Python SOCKS module. -Version 1.00 - -Copyright 2006 Dan-Haim. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - 3. Neither the name of Dan Haim nor the names of his contributors may be used - to endorse or promote products derived from this software without specific - prior written permission. - -THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED -WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO -EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT -OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE. - - -This module provides a standard socket-like interface for Python -for tunneling connections through SOCKS proxies. - -""" - -""" - -Minor modifications made by Christopher Gilbert (http://motomastyle.com/) -for use in PyLoris (http://pyloris.sourceforge.net/) - -Minor modifications made by Mario Vilas (http://breakingcode.wordpress.com/) -mainly to merge bug fixes found in Sourceforge - -""" - -import socket -import struct -import sys - -PROXY_TYPE_SOCKS4 = 1 -PROXY_TYPE_SOCKS5 = 2 -PROXY_TYPE_HTTP = 3 - -_defaultproxy = None -_orgsocket = socket.socket - -class ProxyError(Exception): pass -class GeneralProxyError(ProxyError): pass -class Socks5AuthError(ProxyError): pass -class Socks5Error(ProxyError): pass -class Socks4Error(ProxyError): pass -class HTTPError(ProxyError): pass - -_generalerrors = ("success", - "invalid data", - "not connected", - "not available", - "bad proxy type", - "bad input", - "timed out", - "network unreachable", - "connection refused", - "host unreachable") - -_socks5errors = ("succeeded", - "general SOCKS server failure", - "connection not allowed by ruleset", - "Network unreachable", - "Host unreachable", - "Connection refused", - "TTL expired", - "Command not supported", - "Address type not supported", - "Unknown error") - -_socks5autherrors = ("succeeded", - "authentication is required", - "all offered authentication methods were rejected", - "unknown username or invalid password", - "unknown error") - -_socks4errors = ("request granted", - "request rejected or failed", - "request rejected because SOCKS server cannot connect to identd on the client", - "request rejected because the client program and identd report different user-ids", - "unknown error") - -def setdefaultproxy(proxytype=None, addr=None, port=None, rdns=True, username=None, password=None): - """setdefaultproxy(proxytype, addr[, port[, rdns[, username[, password]]]]) - Sets a default proxy which all further socksocket objects will use, - unless explicitly changed. - """ - global _defaultproxy - _defaultproxy = (proxytype, addr, port, rdns, username, password) - -def wrapmodule(module): - """wrapmodule(module) - Attempts to replace a module's socket library with a SOCKS socket. Must set - a default proxy using setdefaultproxy(...) first. - This will only work on modules that import socket directly into the namespace; - most of the Python Standard Library falls into this category. - """ - if _defaultproxy != None: - module.socket.socket = socksocket - else: - raise GeneralProxyError((4, "no proxy specified")) - -class socksocket(socket.socket): - """socksocket([family[, type[, proto]]]) -> socket object - Open a SOCKS enabled socket. The parameters are the same as - those of the standard socket init. In order for SOCKS to work, - you must specify family=AF_INET, type=SOCK_STREAM and proto=0. - """ - - def __init__(self, family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, _sock=None): - _orgsocket.__init__(self, family, type, proto, _sock) - if _defaultproxy != None: - self.__proxy = _defaultproxy - else: - self.__proxy = (None, None, None, None, None, None) - self.__proxysockname = None - self.__proxypeername = None - - def __recvall(self, count): - """__recvall(count) -> data - Receive EXACTLY the number of bytes requested from the socket. - Blocks until the required number of bytes have been received. - """ - try: - data = self.recv(count) - except socket.timeout: - raise GeneralProxyError((6, "timed out")) - while len(data) < count: - d = self.recv(count-len(data)) - if not d: raise GeneralProxyError((0, "connection closed unexpectedly")) - data = data + d - return data - - def setproxy(self, proxytype=None, addr=None, port=None, rdns=True, username=None, password=None): - """setproxy(proxytype, addr[, port[, rdns[, username[, password]]]]) - Sets the proxy to be used. - - proxytype - The type of the proxy to be used. Three types - are supported: PROXY_TYPE_SOCKS4 (including socks4a), - PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP - - addr - The address of the server (IP or DNS). - - port - The port of the server. Defaults to 1080 for SOCKS - servers and 8080 for HTTP proxy servers. - - rdns - Should DNS queries be preformed on the remote side - (rather than the local side). The default is True. - Note: This has no effect with SOCKS4 servers. - - username - Username to authenticate with to the server. - The default is no authentication. - - password - Password to authenticate with to the server. - Only relevant when username is also provided. - - """ - self.__proxy = (proxytype, addr, port, rdns, username, password) - - def __negotiatesocks5(self): - """__negotiatesocks5(self,destaddr,destport) - Negotiates a connection through a SOCKS5 server. - """ - # First we'll send the authentication packages we support. - if (self.__proxy[4]!=None) and (self.__proxy[5]!=None): - # The username/password details were supplied to the - # setproxy method so we support the USERNAME/PASSWORD - # authentication (in addition to the standard none). - self.sendall(struct.pack('BBBB', 0x05, 0x02, 0x00, 0x02)) - else: - # No username/password were entered, therefore we - # only support connections with no authentication. - self.sendall(struct.pack('BBB', 0x05, 0x01, 0x00)) - # We'll receive the server's response to determine which - # method was selected - chosenauth = self.__recvall(2) - if chosenauth[0:1] != chr(0x05).encode(): - self.close() - raise GeneralProxyError((1, _generalerrors[1])) - # Check the chosen authentication method - if chosenauth[1:2] == chr(0x00).encode(): - # No authentication is required - pass - elif chosenauth[1:2] == chr(0x02).encode(): - # Okay, we need to perform a basic username/password - # authentication. - self.sendall(chr(0x01).encode() + chr(len(self.__proxy[4])) + self.__proxy[4] + chr(len(self.__proxy[5])) + self.__proxy[5]) - authstat = self.__recvall(2) - if authstat[0:1] != chr(0x01).encode(): - # Bad response - self.close() - raise GeneralProxyError((1, _generalerrors[1])) - if authstat[1:2] != chr(0x00).encode(): - # Authentication failed - self.close() - raise Socks5AuthError((3, _socks5autherrors[3])) - # Authentication succeeded - else: - # Reaching here is always bad - self.close() - if chosenauth[1] == chr(0xFF).encode(): - raise Socks5AuthError((2, _socks5autherrors[2])) - else: - raise GeneralProxyError((1, _generalerrors[1])) - - def __connectsocks5(self, destaddr, destport): - # Now we can request the actual connection - req = struct.pack('BBB', 0x05, 0x01, 0x00) - # If the given destination address is an IP address, we'll - # use the IPv4 address request even if remote resolving was specified. - try: - ipaddr = socket.inet_aton(destaddr) - req = req + chr(0x01).encode() + ipaddr - except socket.error: - # Well it's not an IP number, so it's probably a DNS name. - if self.__proxy[3]: - # Resolve remotely - ipaddr = None - req = req + chr(0x03).encode() + chr(len(destaddr)).encode() + destaddr - else: - # Resolve locally - ipaddr = socket.inet_aton(socket.gethostbyname(destaddr)) - req = req + chr(0x01).encode() + ipaddr - req = req + struct.pack(">H", destport) - self.sendall(req) - # Get the response - resp = self.__recvall(4) - if resp[0:1] != chr(0x05).encode(): - self.close() - raise GeneralProxyError((1, _generalerrors[1])) - elif resp[1:2] != chr(0x00).encode(): - # Connection failed - self.close() - if ord(resp[1:2])<=8: - raise Socks5Error((ord(resp[1:2]), _socks5errors[ord(resp[1:2])])) - else: - raise Socks5Error((9, _socks5errors[9])) - # Get the bound address/port - elif resp[3:4] == chr(0x01).encode(): - boundaddr = self.__recvall(4) - elif resp[3:4] == chr(0x03).encode(): - resp = resp + self.recv(1) - boundaddr = self.__recvall(ord(resp[4:5])) - else: - self.close() - raise GeneralProxyError((1,_generalerrors[1])) - boundport = struct.unpack(">H", self.__recvall(2))[0] - self.__proxysockname = (boundaddr, boundport) - if ipaddr != None: - self.__proxypeername = (socket.inet_ntoa(ipaddr), destport) - else: - self.__proxypeername = (destaddr, destport) - - def __resolvesocks5(self, host): - # Now we can request the actual connection - req = struct.pack('BBB', 0x05, 0xF0, 0x00) - req += chr(0x03).encode() + chr(len(host)).encode() + host - req = req + struct.pack(">H", 8444) - self.sendall(req) - # Get the response - ip = "" - resp = self.__recvall(4) - if resp[0:1] != chr(0x05).encode(): - self.close() - raise GeneralProxyError((1, _generalerrors[1])) - elif resp[1:2] != chr(0x00).encode(): - # Connection failed - self.close() - if ord(resp[1:2])<=8: - raise Socks5Error((ord(resp[1:2]), _socks5errors[ord(resp[1:2])])) - else: - raise Socks5Error((9, _socks5errors[9])) - # Get the bound address/port - elif resp[3:4] == chr(0x01).encode(): - ip = socket.inet_ntoa(self.__recvall(4)) - elif resp[3:4] == chr(0x03).encode(): - resp = resp + self.recv(1) - ip = self.__recvall(ord(resp[4:5])) - else: - self.close() - raise GeneralProxyError((1,_generalerrors[1])) - boundport = struct.unpack(">H", self.__recvall(2))[0] - return ip - - def getproxysockname(self): - """getsockname() -> address info - Returns the bound IP address and port number at the proxy. - """ - return self.__proxysockname - - def getproxypeername(self): - """getproxypeername() -> address info - Returns the IP and port number of the proxy. - """ - return _orgsocket.getpeername(self) - - def getpeername(self): - """getpeername() -> address info - Returns the IP address and port number of the destination - machine (note: getproxypeername returns the proxy) - """ - return self.__proxypeername - - def getproxytype(self): - return self.__proxy[0] - - def __negotiatesocks4(self,destaddr,destport): - """__negotiatesocks4(self,destaddr,destport) - Negotiates a connection through a SOCKS4 server. - """ - # Check if the destination address provided is an IP address - rmtrslv = False - try: - ipaddr = socket.inet_aton(destaddr) - except socket.error: - # It's a DNS name. Check where it should be resolved. - if self.__proxy[3]: - ipaddr = struct.pack("BBBB", 0x00, 0x00, 0x00, 0x01) - rmtrslv = True - else: - ipaddr = socket.inet_aton(socket.gethostbyname(destaddr)) - # Construct the request packet - req = struct.pack(">BBH", 0x04, 0x01, destport) + ipaddr - # The username parameter is considered userid for SOCKS4 - if self.__proxy[4] != None: - req = req + self.__proxy[4] - req = req + chr(0x00).encode() - # DNS name if remote resolving is required - # NOTE: This is actually an extension to the SOCKS4 protocol - # called SOCKS4A and may not be supported in all cases. - if rmtrslv: - req = req + destaddr + chr(0x00).encode() - self.sendall(req) - # Get the response from the server - resp = self.__recvall(8) - if resp[0:1] != chr(0x00).encode(): - # Bad data - self.close() - raise GeneralProxyError((1,_generalerrors[1])) - if resp[1:2] != chr(0x5A).encode(): - # Server returned an error - self.close() - if ord(resp[1:2]) in (91, 92, 93): - self.close() - raise Socks4Error((ord(resp[1:2]), _socks4errors[ord(resp[1:2]) - 90])) - else: - raise Socks4Error((94, _socks4errors[4])) - # Get the bound address/port - self.__proxysockname = (socket.inet_ntoa(resp[4:]), struct.unpack(">H", resp[2:4])[0]) - if rmtrslv != None: - self.__proxypeername = (socket.inet_ntoa(ipaddr), destport) - else: - self.__proxypeername = (destaddr, destport) - - def __negotiatehttp(self, destaddr, destport): - """__negotiatehttp(self,destaddr,destport) - Negotiates a connection through an HTTP server. - """ - # If we need to resolve locally, we do this now - if not self.__proxy[3]: - addr = socket.gethostbyname(destaddr) - else: - addr = destaddr - self.sendall(("CONNECT " + addr + ":" + str(destport) + " HTTP/1.1\r\n" + "Host: " + destaddr + "\r\n\r\n").encode()) - # We read the response until we get the string "\r\n\r\n" - resp = self.recv(1) - while resp.find("\r\n\r\n".encode()) == -1: - resp = resp + self.recv(1) - # We just need the first line to check if the connection - # was successful - statusline = resp.splitlines()[0].split(" ".encode(), 2) - if statusline[0] not in ("HTTP/1.0".encode(), "HTTP/1.1".encode()): - self.close() - raise GeneralProxyError((1, _generalerrors[1])) - try: - statuscode = int(statusline[1]) - except ValueError: - self.close() - raise GeneralProxyError((1, _generalerrors[1])) - if statuscode != 200: - self.close() - raise HTTPError((statuscode, statusline[2])) - self.__proxysockname = ("0.0.0.0", 0) - self.__proxypeername = (addr, destport) - - def connect(self, destpair): - """connect(self, despair) - Connects to the specified destination through a proxy. - destpar - A tuple of the IP/DNS address and the port number. - (identical to socket's connect). - To select the proxy server use setproxy(). - """ - # Do a minimal input check first - if (not type(destpair) in (list,tuple)) or (len(destpair) < 2) or (type(destpair[0]) != type('')) or (type(destpair[1]) != int): - raise GeneralProxyError((5, _generalerrors[5])) - if self.__proxy[0] == PROXY_TYPE_SOCKS5: - if self.__proxy[2] != None: - portnum = self.__proxy[2] - else: - portnum = 1080 - try: - _orgsocket.connect(self, (self.__proxy[1], portnum)) - except socket.error as e: - # ENETUNREACH, WSAENETUNREACH - if e[0] in [101, 10051]: - raise GeneralProxyError((7, _generalerrors[7])) - # ECONNREFUSED, WSAECONNREFUSED - if e[0] in [111, 10061]: - raise GeneralProxyError((8, _generalerrors[8])) - # EHOSTUNREACH, WSAEHOSTUNREACH - if e[0] in [113, 10065]: - raise GeneralProxyError((9, _generalerrors[9])) - raise - self.__negotiatesocks5() - self.__connectsocks5(destpair[0], destpair[1]) - elif self.__proxy[0] == PROXY_TYPE_SOCKS4: - if self.__proxy[2] != None: - portnum = self.__proxy[2] - else: - portnum = 1080 - _orgsocket.connect(self,(self.__proxy[1], portnum)) - self.__negotiatesocks4(destpair[0], destpair[1]) - elif self.__proxy[0] == PROXY_TYPE_HTTP: - if self.__proxy[2] != None: - portnum = self.__proxy[2] - else: - portnum = 8080 - try: - _orgsocket.connect(self,(self.__proxy[1], portnum)) - except socket.error as e: - # ENETUNREACH, WSAENETUNREACH - if e[0] in [101, 10051]: - raise GeneralProxyError((7, _generalerrors[7])) - # ECONNREFUSED, WSAECONNREFUSED - if e[0] in [111, 10061]: - raise GeneralProxyError((8, _generalerrors[8])) - # EHOSTUNREACH, WSAEHOSTUNREACH - if e[0] in [113, 10065]: - raise GeneralProxyError((9, _generalerrors[9])) - raise - self.__negotiatehttp(destpair[0], destpair[1]) - elif self.__proxy[0] == None: - _orgsocket.connect(self, (destpair[0], destpair[1])) - else: - raise GeneralProxyError((4, _generalerrors[4])) - - def resolve(self, host): - if self.__proxy[0] == PROXY_TYPE_SOCKS5: - if self.__proxy[2] != None: - portnum = self.__proxy[2] - else: - portnum = 1080 - _orgsocket.connect(self, (self.__proxy[1], portnum)) - self.__negotiatesocks5() - return self.__resolvesocks5(host) - else: - return None diff --git a/src/state.py b/src/state.py index 71adbaf6..0311a342 100644 --- a/src/state.py +++ b/src/state.py @@ -9,9 +9,6 @@ extPort = None # for Tor hidden service socksIP = None -# Network protocols availability, initialised below -networkProtocolAvailability = None - appdata = '' # holds the location of the application data storage directory # Set to 1 by the doCleanShutdown function. @@ -54,14 +51,6 @@ discoveredPeers = {} Peer = collections.namedtuple('Peer', ['host', 'port']) - -def resetNetworkProtocolAvailability(): - global networkProtocolAvailability - networkProtocolAvailability = {'IPv4': None, 'IPv6': None, 'onion': None} - - -resetNetworkProtocolAvailability() - dandelion = 0 testmode = False diff --git a/src/storage/filesystem.py b/src/storage/filesystem.py index d64894a9..43ba03fc 100644 --- a/src/storage/filesystem.py +++ b/src/storage/filesystem.py @@ -1,21 +1,26 @@ +""" +src/storage/filesystem.py +========================= +""" from binascii import hexlify, unhexlify from os import listdir, makedirs, path, remove, rmdir import string from threading import RLock import time -import traceback from paths import lookupAppdataFolder from storage import InventoryStorage, InventoryItem -class FilesystemInventory(InventoryStorage): + +class FilesystemInventory(InventoryStorage): # pylint: disable=too-many-ancestors, abstract-method + """Module for using filesystem (directory with files) for inventory storage""" topDir = "inventory" objectDir = "objects" metadataFilename = "metadata" dataFilename = "data" def __init__(self): - super(self.__class__, self).__init__() + super(FilesystemInventory, self).__init__() self.baseDir = path.join(lookupAppdataFolder(), FilesystemInventory.topDir) for createDir in [self.baseDir, path.join(self.baseDir, "objects")]: if path.exists(createDir): @@ -23,72 +28,101 @@ class FilesystemInventory(InventoryStorage): raise IOError("%s exists but it's not a directory" % (createDir)) else: makedirs(createDir) - self.lock = RLock() # Guarantees that two receiveDataThreads don't receive and process the same message concurrently (probably sent by a malicious individual) + # Guarantees that two receiveDataThreads don't receive and process the same message + # concurrently (probably sent by a malicious individual) + self.lock = RLock() self._inventory = {} self._load() - def __contains__(self, hash): + def __contains__(self, hashval): retval = False for streamDict in self._inventory.values(): - if hash in streamDict: + if hashval in streamDict: return True return False - def __getitem__(self, hash): + def __getitem__(self, hashval): for streamDict in self._inventory.values(): try: - retval = streamDict[hash] + retval = streamDict[hashval] except KeyError: continue if retval.payload is None: - retval = InventoryItem(retval.type, retval.stream, self.getData(hash), retval.expires, retval.tag) + retval = InventoryItem(retval.type, retval.stream, self.getData(hashval), retval.expires, retval.tag) return retval - raise KeyError(hash) + raise KeyError(hashval) - def __setitem__(self, hash, value): + def __setitem__(self, hashval, value): with self.lock: value = InventoryItem(*value) try: - makedirs(path.join(self.baseDir, FilesystemInventory.objectDir, hexlify(hash))) + makedirs(path.join(self.baseDir, FilesystemInventory.objectDir, hexlify(hashval))) except OSError: pass try: - with open(path.join(self.baseDir, FilesystemInventory.objectDir, hexlify(hash), FilesystemInventory.metadataFilename), 'w') as f: + with open( + path.join( + self.baseDir, + FilesystemInventory.objectDir, + hexlify(hashval), + FilesystemInventory.metadataFilename, + ), + "w", + ) as f: f.write("%s,%s,%s,%s," % (value.type, value.stream, value.expires, hexlify(value.tag))) - with open(path.join(self.baseDir, FilesystemInventory.objectDir, hexlify(hash), FilesystemInventory.dataFilename), 'w') as f: + with open( + path.join( + self.baseDir, + FilesystemInventory.objectDir, + hexlify(hashval), + FilesystemInventory.dataFilename, + ), + "w", + ) as f: f.write(value.payload) except IOError: raise KeyError try: - self._inventory[value.stream][hash] = value + self._inventory[value.stream][hashval] = value except KeyError: self._inventory[value.stream] = {} - self._inventory[value.stream][hash] = value + self._inventory[value.stream][hashval] = value - def delHashId(self, hash): - for stream in self._inventory.keys(): + def delHashId(self, hashval): + """Remove object from inventory""" + for stream in self._inventory: try: - del self._inventory[stream][hash] + del self._inventory[stream][hashval] except KeyError: pass with self.lock: try: - remove(path.join(self.baseDir, FilesystemInventory.objectDir, hexlify(hash), FilesystemInventory.metadataFilename)) + remove( + path.join( + self.baseDir, + FilesystemInventory.objectDir, + hexlify(hashval), + FilesystemInventory.metadataFilename)) except IOError: pass try: - remove(path.join(self.baseDir, FilesystemInventory.objectDir, hexlify(hash), FilesystemInventory.dataFilename)) + remove( + path.join( + self.baseDir, + FilesystemInventory.objectDir, + hexlify(hashval), + FilesystemInventory.dataFilename)) except IOError: pass try: - rmdir(path.join(self.baseDir, FilesystemInventory.objectDir, hexlify(hash))) + rmdir(path.join(self.baseDir, FilesystemInventory.objectDir, hexlify(hashval))) except IOError: pass def __iter__(self): elems = [] for streamDict in self._inventory.values(): - elems.extend (streamDict.keys()) + elems.extend(streamDict.keys()) return elems.__iter__() def __len__(self): @@ -103,44 +137,66 @@ class FilesystemInventory(InventoryStorage): try: objectType, streamNumber, expiresTime, tag = self.getMetadata(hashId) try: - newInventory[streamNumber][hashId] = InventoryItem(objectType, streamNumber, None, expiresTime, tag) + newInventory[streamNumber][hashId] = InventoryItem( + objectType, streamNumber, None, expiresTime, tag) except KeyError: newInventory[streamNumber] = {} - newInventory[streamNumber][hashId] = InventoryItem(objectType, streamNumber, None, expiresTime, tag) + newInventory[streamNumber][hashId] = InventoryItem( + objectType, streamNumber, None, expiresTime, tag) except KeyError: print "error loading %s" % (hexlify(hashId)) - pass self._inventory = newInventory # for i, v in self._inventory.items(): # print "loaded stream: %s, %i items" % (i, len(v)) def stream_list(self): + """Return list of streams""" return self._inventory.keys() def object_list(self): + """Return inventory vectors (hashes) from a directory""" return [unhexlify(x) for x in listdir(path.join(self.baseDir, FilesystemInventory.objectDir))] def getData(self, hashId): + """Get object data""" try: - with open(path.join(self.baseDir, FilesystemInventory.objectDir, hexlify(hashId), FilesystemInventory.dataFilename), 'r') as f: + with open( + path.join( + self.baseDir, + FilesystemInventory.objectDir, + hexlify(hashId), + FilesystemInventory.dataFilename, + ), + "r", + ) as f: return f.read() except IOError: raise AttributeError def getMetadata(self, hashId): + """Get object metadata""" try: - with open(path.join(self.baseDir, FilesystemInventory.objectDir, hexlify(hashId), FilesystemInventory.metadataFilename), 'r') as f: + with open( + path.join( + self.baseDir, + FilesystemInventory.objectDir, + hexlify(hashId), + FilesystemInventory.metadataFilename, + ), + "r", + ) as f: objectType, streamNumber, expiresTime, tag, undef = string.split(f.read(), ",", 4) return [int(objectType), int(streamNumber), int(expiresTime), unhexlify(tag)] except IOError: raise KeyError def by_type_and_tag(self, objectType, tag): + """Get a list of objects filtered by object type and tag""" retval = [] for stream, streamDict in self._inventory: for hashId, item in streamDict: if item.type == objectType and item.tag == tag: - try: + try: if item.payload is None: item.payload = self.getData(hashId) except IOError: @@ -149,12 +205,14 @@ class FilesystemInventory(InventoryStorage): return retval def hashes_by_stream(self, stream): + """Return inventory vectors (hashes) for a stream""" try: return self._inventory[stream].keys() except KeyError: return [] def unexpired_hashes_by_stream(self, stream): + """Return unexpired hashes in the inventory for a particular stream""" t = int(time.time()) try: return [x for x, value in self._inventory[stream].items() if value.expires > t] @@ -162,9 +220,11 @@ class FilesystemInventory(InventoryStorage): return [] def flush(self): + """Flush the inventory and create a new, empty one""" self._load() def clean(self): + """Clean out old items from the inventory""" minTime = int(time.time()) - (60 * 60 * 30) deletes = [] for stream, streamDict in self._inventory.items(): diff --git a/src/storage/sqlite.py b/src/storage/sqlite.py index 438cbdcb..0c2b4afa 100644 --- a/src/storage/sqlite.py +++ b/src/storage/sqlite.py @@ -1,45 +1,59 @@ -import collections -from threading import current_thread, enumerate as threadingEnumerate, RLock -import Queue +""" +src/storage/sqlite.py +========================= +""" import sqlite3 import time +from threading import RLock -from helper_sql import * +from helper_sql import sqlQuery, SqlBulkExecute, sqlExecute from storage import InventoryStorage, InventoryItem -class SqliteInventory(InventoryStorage): - def __init__(self): - super(self.__class__, self).__init__() - self._inventory = {} #of objects (like msg payloads and pubkey payloads) Does not include protocol headers (the first 24 bytes of each packet). - self._objects = {} # cache for existing objects, used for quick lookups if we have an object. This is used for example whenever we receive an inv message from a peer to check to see what items are new to us. We don't delete things out of it; instead, the singleCleaner thread clears and refills it. - self.lock = RLock() # Guarantees that two receiveDataThreads don't receive and process the same message concurrently (probably sent by a malicious individual) - def __contains__(self, hash): +class SqliteInventory(InventoryStorage): # pylint: disable=too-many-ancestors + """Inventory using SQLite""" + def __init__(self): + super(SqliteInventory, self).__init__() + # of objects (like msg payloads and pubkey payloads) + # Does not include protocol headers (the first 24 bytes of each packet). + self._inventory = {} + # cache for existing objects, used for quick lookups if we have an object. + # This is used for example whenever we receive an inv message from a peer + # to check to see what items are new to us. + # We don't delete things out of it; instead, the singleCleaner thread clears and refills it. + self._objects = {} + # Guarantees that two receiveDataThreads don't receive and process the same message concurrently + # (probably sent by a malicious individual) + self.lock = RLock() + + def __contains__(self, hash_): with self.lock: - if hash in self._objects: + if hash_ in self._objects: return True - rows = sqlQuery('SELECT streamnumber FROM inventory WHERE hash=?', sqlite3.Binary(hash)) + rows = sqlQuery('SELECT streamnumber FROM inventory WHERE hash=?', sqlite3.Binary(hash_)) if not rows: return False - self._objects[hash] = rows[0][0] + self._objects[hash_] = rows[0][0] return True - def __getitem__(self, hash): + def __getitem__(self, hash_): with self.lock: - if hash in self._inventory: - return self._inventory[hash] - rows = sqlQuery('SELECT objecttype, streamnumber, payload, expirestime, tag FROM inventory WHERE hash=?', sqlite3.Binary(hash)) + if hash_ in self._inventory: + return self._inventory[hash_] + rows = sqlQuery( + 'SELECT objecttype, streamnumber, payload, expirestime, tag FROM inventory WHERE hash=?', + sqlite3.Binary(hash_)) if not rows: - raise KeyError(hash) + raise KeyError(hash_) return InventoryItem(*rows[0]) - def __setitem__(self, hash, value): + def __setitem__(self, hash_, value): with self.lock: value = InventoryItem(*value) - self._inventory[hash] = value - self._objects[hash] = value.stream + self._inventory[hash_] = value + self._objects[hash_] = value.stream - def __delitem__(self, hash): + def __delitem__(self, hash_): raise NotImplementedError def __iter__(self): @@ -55,18 +69,22 @@ class SqliteInventory(InventoryStorage): def by_type_and_tag(self, objectType, tag): with self.lock: values = [value for value in self._inventory.values() if value.type == objectType and value.tag == tag] - values += (InventoryItem(*value) for value in sqlQuery('SELECT objecttype, streamnumber, payload, expirestime, tag FROM inventory WHERE objecttype=? AND tag=?', objectType, sqlite3.Binary(tag))) + values += (InventoryItem(*value) for value in sqlQuery( + 'SELECT objecttype, streamnumber, payload, expirestime, tag \ + FROM inventory WHERE objecttype=? AND tag=?', objectType, sqlite3.Binary(tag))) return values def unexpired_hashes_by_stream(self, stream): with self.lock: t = int(time.time()) hashes = [x for x, value in self._inventory.items() if value.stream == stream and value.expires > t] - hashes += (str(payload) for payload, in sqlQuery('SELECT hash FROM inventory WHERE streamnumber=? AND expirestime>?', stream, t)) + hashes += (str(payload) for payload, in sqlQuery( + 'SELECT hash FROM inventory WHERE streamnumber=? AND expirestime>?', stream, t)) return hashes def flush(self): - with self.lock: # If you use both the inventoryLock and the sqlLock, always use the inventoryLock OUTSIDE of the sqlLock. + with self.lock: + # If you use both the inventoryLock and the sqlLock, always use the inventoryLock OUTSIDE of the sqlLock. with SqlBulkExecute() as sql: for objectHash, value in self._inventory.items(): sql.execute('INSERT INTO inventory VALUES (?, ?, ?, ?, ?, ?)', sqlite3.Binary(objectHash), *value) @@ -74,8 +92,7 @@ class SqliteInventory(InventoryStorage): def clean(self): with self.lock: - sqlExecute('DELETE FROM inventory WHERE expirestime