Changes based on style and lint checks. (final_code_quality_10)
This commit is contained in:
parent
a7a21e79ed
commit
b27b7239a5
|
@ -1,84 +0,0 @@
|
||||||
#!/usr/bin/env python2.7
|
|
||||||
from PyQt4 import QtCore, QtGui
|
|
||||||
|
|
||||||
class MigrationWizardIntroPage(QtGui.QWizardPage):
|
|
||||||
def __init__(self):
|
|
||||||
super(QtGui.QWizardPage, self).__init__()
|
|
||||||
self.setTitle("Migrating configuration")
|
|
||||||
|
|
||||||
label = QtGui.QLabel("This wizard will help you to migrate your configuration. "
|
|
||||||
"You can still keep using PyBitMessage once you migrate, the changes are backwards compatible.")
|
|
||||||
label.setWordWrap(True)
|
|
||||||
|
|
||||||
layout = QtGui.QVBoxLayout()
|
|
||||||
layout.addWidget(label)
|
|
||||||
self.setLayout(layout)
|
|
||||||
|
|
||||||
def nextId(self):
|
|
||||||
return 1
|
|
||||||
|
|
||||||
|
|
||||||
class MigrationWizardAddressesPage(QtGui.QWizardPage):
|
|
||||||
def __init__(self, addresses):
|
|
||||||
super(QtGui.QWizardPage, self).__init__()
|
|
||||||
self.setTitle("Addresses")
|
|
||||||
|
|
||||||
label = QtGui.QLabel("Please select addresses that you are already using with mailchuck. ")
|
|
||||||
label.setWordWrap(True)
|
|
||||||
|
|
||||||
layout = QtGui.QVBoxLayout()
|
|
||||||
layout.addWidget(label)
|
|
||||||
self.setLayout(layout)
|
|
||||||
|
|
||||||
def nextId(self):
|
|
||||||
return 10
|
|
||||||
|
|
||||||
|
|
||||||
class MigrationWizardGPUPage(QtGui.QWizardPage):
|
|
||||||
def __init__(self):
|
|
||||||
super(QtGui.QWizardPage, self).__init__()
|
|
||||||
self.setTitle("GPU")
|
|
||||||
|
|
||||||
label = QtGui.QLabel("Are you using a GPU? ")
|
|
||||||
label.setWordWrap(True)
|
|
||||||
|
|
||||||
layout = QtGui.QVBoxLayout()
|
|
||||||
layout.addWidget(label)
|
|
||||||
self.setLayout(layout)
|
|
||||||
|
|
||||||
def nextId(self):
|
|
||||||
return 10
|
|
||||||
|
|
||||||
|
|
||||||
class MigrationWizardConclusionPage(QtGui.QWizardPage):
|
|
||||||
def __init__(self):
|
|
||||||
super(QtGui.QWizardPage, self).__init__()
|
|
||||||
self.setTitle("All done!")
|
|
||||||
|
|
||||||
label = QtGui.QLabel("You successfully migrated.")
|
|
||||||
label.setWordWrap(True)
|
|
||||||
|
|
||||||
layout = QtGui.QVBoxLayout()
|
|
||||||
layout.addWidget(label)
|
|
||||||
self.setLayout(layout)
|
|
||||||
|
|
||||||
|
|
||||||
class Ui_MigrationWizard(QtGui.QWizard):
|
|
||||||
def __init__(self, addresses):
|
|
||||||
super(QtGui.QWizard, self).__init__()
|
|
||||||
|
|
||||||
self.pages = {}
|
|
||||||
|
|
||||||
page = MigrationWizardIntroPage()
|
|
||||||
self.setPage(0, page)
|
|
||||||
self.setStartId(0)
|
|
||||||
page = MigrationWizardAddressesPage(addresses)
|
|
||||||
self.setPage(1, page)
|
|
||||||
page = MigrationWizardGPUPage()
|
|
||||||
self.setPage(2, page)
|
|
||||||
page = MigrationWizardConclusionPage()
|
|
||||||
self.setPage(10, page)
|
|
||||||
|
|
||||||
self.setWindowTitle("Migration from PyBitMessage wizard")
|
|
||||||
self.adjustSize()
|
|
||||||
self.show()
|
|
|
@ -1,10 +1,18 @@
|
||||||
from HTMLParser import HTMLParser
|
"""
|
||||||
|
src/bitmessageqt/safehtmlparser.py
|
||||||
|
==================================
|
||||||
|
"""
|
||||||
|
# pylint: disable=attribute-defined-outside-init
|
||||||
|
|
||||||
import inspect
|
import inspect
|
||||||
import re
|
import re
|
||||||
from urllib import quote, quote_plus
|
from HTMLParser import HTMLParser
|
||||||
|
from urllib import quote_plus
|
||||||
from urlparse import urlparse
|
from urlparse import urlparse
|
||||||
|
|
||||||
|
|
||||||
class SafeHTMLParser(HTMLParser):
|
class SafeHTMLParser(HTMLParser):
|
||||||
|
"""HTML parser with sanitisation"""
|
||||||
# from html5lib.sanitiser
|
# from html5lib.sanitiser
|
||||||
acceptable_elements = ['a', 'abbr', 'acronym', 'address', 'area',
|
acceptable_elements = ['a', 'abbr', 'acronym', 'address', 'area',
|
||||||
'article', 'aside', 'audio', 'b', 'big', 'blockquote', 'br', 'button',
|
'article', 'aside', 'audio', 'b', 'big', 'blockquote', 'br', 'button',
|
||||||
|
@ -20,21 +28,29 @@ class SafeHTMLParser(HTMLParser):
|
||||||
'sub', 'sup', 'table', 'tbody', 'td', 'textarea', 'time', 'tfoot',
|
'sub', 'sup', 'table', 'tbody', 'td', 'textarea', 'time', 'tfoot',
|
||||||
'th', 'thead', 'tr', 'tt', 'u', 'ul', 'var', 'video']
|
'th', 'thead', 'tr', 'tt', 'u', 'ul', 'var', 'video']
|
||||||
replaces_pre = [["&", "&"], ["\"", """], ["<", "<"], [">", ">"]]
|
replaces_pre = [["&", "&"], ["\"", """], ["<", "<"], [">", ">"]]
|
||||||
replaces_post = [["\n", "<br/>"], ["\t", " "], [" ", " "], [" ", " "], ["<br/> ", "<br/> "]]
|
replaces_post = [
|
||||||
src_schemes = [ "data" ]
|
["\n", "<br/>"],
|
||||||
#uriregex1 = re.compile(r'(?i)\b((?:(https?|ftp|bitcoin):(?:/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:\'".,<>?]))')
|
["\t", " "],
|
||||||
uriregex1 = re.compile(r'((https?|ftp|bitcoin):(?:/{1,3}|[a-z0-9%])(?:[a-zA-Z]|[0-9]|[$-_@.&+#]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)')
|
[" ", " "],
|
||||||
|
[" ", " "],
|
||||||
|
["<br/> ", "<br/> "]]
|
||||||
|
src_schemes = ["data"]
|
||||||
|
uriregex1 = re.compile(
|
||||||
|
r'((https?|ftp|bitcoin):'
|
||||||
|
r'(?:/{1,3}|[a-z0-9%])(?:[a-zA-Z]|[0-9]|[$-_@.&+#]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)')
|
||||||
uriregex2 = re.compile(r'<a href="([^"]+)&')
|
uriregex2 = re.compile(r'<a href="([^"]+)&')
|
||||||
emailregex = re.compile(r'\b([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,})\b')
|
emailregex = re.compile(r'\b([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,})\b')
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def replace_pre(text):
|
def replace_pre(text):
|
||||||
|
"""Perform substring replacement before regex replacements"""
|
||||||
for a in SafeHTMLParser.replaces_pre:
|
for a in SafeHTMLParser.replaces_pre:
|
||||||
text = text.replace(a[0], a[1])
|
text = text.replace(a[0], a[1])
|
||||||
return text
|
return text
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def replace_post(text):
|
def replace_post(text):
|
||||||
|
"""Perform substring replacement after regex replacements"""
|
||||||
for a in SafeHTMLParser.replaces_post:
|
for a in SafeHTMLParser.replaces_post:
|
||||||
text = text.replace(a[0], a[1])
|
text = text.replace(a[0], a[1])
|
||||||
if len(text) > 1 and text[0] == " ":
|
if len(text) > 1 and text[0] == " ":
|
||||||
|
@ -46,6 +62,7 @@ class SafeHTMLParser(HTMLParser):
|
||||||
self.reset_safe()
|
self.reset_safe()
|
||||||
|
|
||||||
def reset_safe(self):
|
def reset_safe(self):
|
||||||
|
"""Reset runtime variables specific to this class"""
|
||||||
self.elements = set()
|
self.elements = set()
|
||||||
self.raw = u""
|
self.raw = u""
|
||||||
self.sanitised = u""
|
self.sanitised = u""
|
||||||
|
@ -53,7 +70,8 @@ class SafeHTMLParser(HTMLParser):
|
||||||
self.allow_picture = False
|
self.allow_picture = False
|
||||||
self.allow_external_src = False
|
self.allow_external_src = False
|
||||||
|
|
||||||
def add_if_acceptable(self, tag, attrs = None):
|
def add_if_acceptable(self, tag, attrs=None):
|
||||||
|
"""Add tag if it passes sanitisation"""
|
||||||
if tag not in SafeHTMLParser.acceptable_elements:
|
if tag not in SafeHTMLParser.acceptable_elements:
|
||||||
return
|
return
|
||||||
self.sanitised += "<"
|
self.sanitised += "<"
|
||||||
|
@ -69,35 +87,42 @@ class SafeHTMLParser(HTMLParser):
|
||||||
if url.scheme not in SafeHTMLParser.src_schemes:
|
if url.scheme not in SafeHTMLParser.src_schemes:
|
||||||
val = ""
|
val = ""
|
||||||
self.sanitised += " " + quote_plus(attr)
|
self.sanitised += " " + quote_plus(attr)
|
||||||
if not (val is None):
|
if val is not None:
|
||||||
self.sanitised += "=\"" + val + "\""
|
self.sanitised += "=\"" + val + "\""
|
||||||
if inspect.stack()[1][3] == "handle_startendtag":
|
if inspect.stack()[1][3] == "handle_startendtag":
|
||||||
self.sanitised += "/"
|
self.sanitised += "/"
|
||||||
self.sanitised += ">"
|
self.sanitised += ">"
|
||||||
|
|
||||||
def handle_starttag(self, tag, attrs):
|
def handle_starttag(self, tag, attrs):
|
||||||
|
""""""
|
||||||
if tag in SafeHTMLParser.acceptable_elements:
|
if tag in SafeHTMLParser.acceptable_elements:
|
||||||
self.has_html = True
|
self.has_html = True
|
||||||
self.add_if_acceptable(tag, attrs)
|
self.add_if_acceptable(tag, attrs)
|
||||||
|
|
||||||
def handle_endtag(self, tag):
|
def handle_endtag(self, tag):
|
||||||
|
""""""
|
||||||
self.add_if_acceptable(tag)
|
self.add_if_acceptable(tag)
|
||||||
|
|
||||||
def handle_startendtag(self, tag, attrs):
|
def handle_startendtag(self, tag, attrs):
|
||||||
|
""""""
|
||||||
if tag in SafeHTMLParser.acceptable_elements:
|
if tag in SafeHTMLParser.acceptable_elements:
|
||||||
self.has_html = True
|
self.has_html = True
|
||||||
self.add_if_acceptable(tag, attrs)
|
self.add_if_acceptable(tag, attrs)
|
||||||
|
|
||||||
def handle_data(self, data):
|
def handle_data(self, data):
|
||||||
|
""""""
|
||||||
self.sanitised += data
|
self.sanitised += data
|
||||||
|
|
||||||
def handle_charref(self, name):
|
def handle_charref(self, name):
|
||||||
|
""""""
|
||||||
self.sanitised += "&#" + name + ";"
|
self.sanitised += "&#" + name + ";"
|
||||||
|
|
||||||
def handle_entityref(self, name):
|
def handle_entityref(self, name):
|
||||||
|
""""""
|
||||||
self.sanitised += "&" + name + ";"
|
self.sanitised += "&" + name + ";"
|
||||||
|
|
||||||
def feed(self, data):
|
def feed(self, data):
|
||||||
|
""""""
|
||||||
try:
|
try:
|
||||||
data = unicode(data, 'utf-8')
|
data = unicode(data, 'utf-8')
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
|
@ -112,7 +137,8 @@ class SafeHTMLParser(HTMLParser):
|
||||||
tmp = SafeHTMLParser.replace_post(tmp)
|
tmp = SafeHTMLParser.replace_post(tmp)
|
||||||
self.raw += tmp
|
self.raw += tmp
|
||||||
|
|
||||||
def is_html(self, text = None, allow_picture = False):
|
def is_html(self, text=None, allow_picture=False):
|
||||||
|
"""Detect if string contains HTML tags"""
|
||||||
if text:
|
if text:
|
||||||
self.reset()
|
self.reset()
|
||||||
self.reset_safe()
|
self.reset_safe()
|
||||||
|
|
|
@ -1,24 +1,31 @@
|
||||||
|
"""
|
||||||
|
src/bitmessageqt/support.py
|
||||||
|
===========================
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
import ctypes
|
import ctypes
|
||||||
from PyQt4 import QtCore, QtGui
|
|
||||||
import ssl
|
import ssl
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
from PyQt4 import QtCore, QtGui
|
||||||
|
|
||||||
|
from version import softwareVersion
|
||||||
|
|
||||||
import account
|
import account
|
||||||
from bmconfigparser import BMConfigParser
|
|
||||||
from debug import logger
|
|
||||||
import defaults
|
import defaults
|
||||||
from foldertree import AccountMixin
|
import network.stats
|
||||||
from helper_sql import *
|
|
||||||
from l10n import getTranslationLanguage
|
|
||||||
from openclpow import openclAvailable, openclEnabled
|
|
||||||
import paths
|
import paths
|
||||||
import proofofwork
|
import proofofwork
|
||||||
from pyelliptic.openssl import OpenSSL
|
|
||||||
import queues
|
import queues
|
||||||
import network.stats
|
|
||||||
import state
|
import state
|
||||||
from version import softwareVersion
|
from bmconfigparser import BMConfigParser
|
||||||
|
from foldertree import AccountMixin
|
||||||
|
from helper_sql import sqlExecute, sqlQuery
|
||||||
|
from l10n import getTranslationLanguage
|
||||||
|
from openclpow import openclEnabled
|
||||||
|
from pyelliptic.openssl import OpenSSL
|
||||||
|
|
||||||
# this is BM support address going to Peter Surda
|
# this is BM support address going to Peter Surda
|
||||||
OLD_SUPPORT_ADDRESS = 'BM-2cTkCtMYkrSPwFTpgcBrMrf5d8oZwvMZWK'
|
OLD_SUPPORT_ADDRESS = 'BM-2cTkCtMYkrSPwFTpgcBrMrf5d8oZwvMZWK'
|
||||||
|
@ -26,7 +33,10 @@ SUPPORT_ADDRESS = 'BM-2cUdgkDDAahwPAU6oD2A7DnjqZz3hgY832'
|
||||||
SUPPORT_LABEL = 'PyBitmessage support'
|
SUPPORT_LABEL = 'PyBitmessage support'
|
||||||
SUPPORT_MY_LABEL = 'My new address'
|
SUPPORT_MY_LABEL = 'My new address'
|
||||||
SUPPORT_SUBJECT = 'Support request'
|
SUPPORT_SUBJECT = 'Support request'
|
||||||
SUPPORT_MESSAGE = '''You can use this message to send a report to one of the PyBitmessage core developers regarding PyBitmessage or the mailchuck.com email service. If you are using PyBitmessage involuntarily, for example because your computer was infected with ransomware, this is not an appropriate venue for resolving such issues.
|
SUPPORT_MESSAGE = (
|
||||||
|
'''You can use this message to send a report to one of the PyBitmessage core developers regarding PyBitmessage'''
|
||||||
|
''' or the mailchuck.com email service. If you are using PyBitmessage involuntarily, for example because your'''
|
||||||
|
''' computer was infected with ransomware, this is not an appropriate venue for resolving such issues.
|
||||||
|
|
||||||
Please describe what you are trying to do:
|
Please describe what you are trying to do:
|
||||||
|
|
||||||
|
@ -51,38 +61,63 @@ Locale: {}
|
||||||
SOCKS: {}
|
SOCKS: {}
|
||||||
UPnP: {}
|
UPnP: {}
|
||||||
Connected hosts: {}
|
Connected hosts: {}
|
||||||
'''
|
''')
|
||||||
|
|
||||||
|
|
||||||
def checkAddressBook(myapp):
|
def checkAddressBook(myapp):
|
||||||
|
"""
|
||||||
|
Check if the support bitmessage address is in addressbook, and add it if necessary. Also delete the old one which
|
||||||
|
is considered compromised.
|
||||||
|
"""
|
||||||
sqlExecute('''DELETE from addressbook WHERE address=?''', OLD_SUPPORT_ADDRESS)
|
sqlExecute('''DELETE from addressbook WHERE address=?''', OLD_SUPPORT_ADDRESS)
|
||||||
queryreturn = sqlQuery('''SELECT * FROM addressbook WHERE address=?''', SUPPORT_ADDRESS)
|
queryreturn = sqlQuery('''SELECT * FROM addressbook WHERE address=?''', SUPPORT_ADDRESS)
|
||||||
if queryreturn == []:
|
if queryreturn == []:
|
||||||
sqlExecute('''INSERT INTO addressbook VALUES (?,?)''', str(QtGui.QApplication.translate("Support", SUPPORT_LABEL)), SUPPORT_ADDRESS)
|
sqlExecute(
|
||||||
|
'''INSERT INTO addressbook VALUES (?,?)''', str(
|
||||||
|
QtGui.QApplication.translate(
|
||||||
|
"Support", SUPPORT_LABEL)), SUPPORT_ADDRESS)
|
||||||
myapp.rerenderAddressBook()
|
myapp.rerenderAddressBook()
|
||||||
|
|
||||||
|
|
||||||
def checkHasNormalAddress():
|
def checkHasNormalAddress():
|
||||||
|
"""Check if a normal (non-chan, non-subscription) address exists"""
|
||||||
for address in account.getSortedAccounts():
|
for address in account.getSortedAccounts():
|
||||||
acct = account.accountClass(address)
|
acct = account.accountClass(address)
|
||||||
if acct.type == AccountMixin.NORMAL and BMConfigParser().safeGetBoolean(address, 'enabled'):
|
if acct.type == AccountMixin.NORMAL and BMConfigParser().safeGetBoolean(address, 'enabled'):
|
||||||
return address
|
return address
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def createAddressIfNeeded(myapp):
|
def createAddressIfNeeded(myapp):
|
||||||
|
"""Create a normal address (for sending the support request) in case it doesn't exist already"""
|
||||||
if not checkHasNormalAddress():
|
if not checkHasNormalAddress():
|
||||||
queues.addressGeneratorQueue.put(('createRandomAddress', 4, 1, str(QtGui.QApplication.translate("Support", SUPPORT_MY_LABEL)), 1, "", False, defaults.networkDefaultProofOfWorkNonceTrialsPerByte, defaults.networkDefaultPayloadLengthExtraBytes))
|
queues.addressGeneratorQueue.put(('createRandomAddress',
|
||||||
|
4,
|
||||||
|
1,
|
||||||
|
str(QtGui.QApplication.translate("Support",
|
||||||
|
SUPPORT_MY_LABEL)),
|
||||||
|
1,
|
||||||
|
"",
|
||||||
|
False,
|
||||||
|
defaults.networkDefaultProofOfWorkNonceTrialsPerByte,
|
||||||
|
defaults.networkDefaultPayloadLengthExtraBytes))
|
||||||
while state.shutdown == 0 and not checkHasNormalAddress():
|
while state.shutdown == 0 and not checkHasNormalAddress():
|
||||||
time.sleep(.2)
|
time.sleep(.2)
|
||||||
myapp.rerenderComboBoxSendFrom()
|
myapp.rerenderComboBoxSendFrom()
|
||||||
return checkHasNormalAddress()
|
return checkHasNormalAddress()
|
||||||
|
|
||||||
|
|
||||||
def createSupportMessage(myapp):
|
def createSupportMessage(myapp):
|
||||||
|
"""Create a support message (pre-fill from system info)"""
|
||||||
|
# pylint: disable=too-many-locals
|
||||||
checkAddressBook(myapp)
|
checkAddressBook(myapp)
|
||||||
address = createAddressIfNeeded(myapp)
|
address = createAddressIfNeeded(myapp)
|
||||||
if state.shutdown:
|
if state.shutdown:
|
||||||
return
|
return
|
||||||
|
|
||||||
myapp.ui.lineEditSubject.setText(str(QtGui.QApplication.translate("Support", SUPPORT_SUBJECT)))
|
myapp.ui.lineEditSubject.setText(str(QtGui.QApplication.translate("Support", SUPPORT_SUBJECT)))
|
||||||
addrIndex = myapp.ui.comboBoxSendFrom.findData(address, QtCore.Qt.UserRole, QtCore.Qt.MatchFixedString | QtCore.Qt.MatchCaseSensitive)
|
addrIndex = myapp.ui.comboBoxSendFrom.findData(
|
||||||
|
address, QtCore.Qt.UserRole, QtCore.Qt.MatchFixedString | QtCore.Qt.MatchCaseSensitive)
|
||||||
if addrIndex == -1: # something is very wrong
|
if addrIndex == -1: # something is very wrong
|
||||||
return
|
return
|
||||||
myapp.ui.comboBoxSendFrom.setCurrentIndex(addrIndex)
|
myapp.ui.comboBoxSendFrom.setCurrentIndex(addrIndex)
|
||||||
|
@ -95,7 +130,7 @@ def createSupportMessage(myapp):
|
||||||
|
|
||||||
os = sys.platform
|
os = sys.platform
|
||||||
if os == "win32":
|
if os == "win32":
|
||||||
windowsversion = sys.getwindowsversion()
|
windowsversion = sys.getwindowsversion() # pylint: disable=no-member
|
||||||
os = "Windows " + str(windowsversion[0]) + "." + str(windowsversion[1])
|
os = "Windows " + str(windowsversion[0]) + "." + str(windowsversion[1])
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
|
@ -107,7 +142,8 @@ def createSupportMessage(myapp):
|
||||||
architecture = "32" if ctypes.sizeof(ctypes.c_voidp) == 4 else "64"
|
architecture = "32" if ctypes.sizeof(ctypes.c_voidp) == 4 else "64"
|
||||||
pythonversion = sys.version
|
pythonversion = sys.version
|
||||||
|
|
||||||
opensslversion = "%s (Python internal), %s (external for PyElliptic)" % (ssl.OPENSSL_VERSION, OpenSSL._version)
|
opensslversion = "%s (Python internal), %s (external for PyElliptic)" % (
|
||||||
|
ssl.OPENSSL_VERSION, OpenSSL._version) # pylint: disable=protected-access
|
||||||
|
|
||||||
frozen = "N/A"
|
frozen = "N/A"
|
||||||
if paths.frozen:
|
if paths.frozen:
|
||||||
|
@ -123,7 +159,9 @@ def createSupportMessage(myapp):
|
||||||
upnp = BMConfigParser().safeGet('bitmessagesettings', 'upnp', "N/A")
|
upnp = BMConfigParser().safeGet('bitmessagesettings', 'upnp', "N/A")
|
||||||
connectedhosts = len(network.stats.connectedHostsList())
|
connectedhosts = len(network.stats.connectedHostsList())
|
||||||
|
|
||||||
myapp.ui.textEditMessage.setText(str(QtGui.QApplication.translate("Support", SUPPORT_MESSAGE)).format(version, os, architecture, pythonversion, opensslversion, frozen, portablemode, cpow, openclpow, locale, socks, upnp, connectedhosts))
|
myapp.ui.textEditMessage.setText(str(QtGui.QApplication.translate("Support", SUPPORT_MESSAGE)).format(
|
||||||
|
version, os, architecture, pythonversion, opensslversion, frozen,
|
||||||
|
portablemode, cpow, openclpow, locale, socks, upnp, connectedhosts))
|
||||||
|
|
||||||
# single msg tab
|
# single msg tab
|
||||||
myapp.ui.tabWidgetSend.setCurrentIndex(
|
myapp.ui.tabWidgetSend.setCurrentIndex(
|
||||||
|
|
|
@ -1,27 +1,33 @@
|
||||||
from ConfigParser import NoOptionError, NoSectionError
|
"""
|
||||||
|
src/network/connectionpool.py
|
||||||
|
=============================
|
||||||
|
"""
|
||||||
|
|
||||||
import errno
|
import errno
|
||||||
|
import re
|
||||||
import socket
|
import socket
|
||||||
import time
|
import time
|
||||||
import random
|
from ConfigParser import NoOptionError, NoSectionError
|
||||||
import re
|
|
||||||
|
|
||||||
|
import helper_bootstrap
|
||||||
|
import helper_random
|
||||||
|
import knownnodes
|
||||||
|
import protocol
|
||||||
|
import state
|
||||||
from bmconfigparser import BMConfigParser
|
from bmconfigparser import BMConfigParser
|
||||||
from debug import logger
|
from debug import logger
|
||||||
import helper_bootstrap
|
|
||||||
import knownnodes
|
|
||||||
from network.proxy import Proxy
|
|
||||||
from network.tcp import TCPServer, Socks5BMConnection, Socks4aBMConnection, TCPConnection
|
|
||||||
from network.udp import UDPSocket
|
|
||||||
from network.connectionchooser import chooseConnection
|
|
||||||
import network.asyncore_pollchoose as asyncore
|
import network.asyncore_pollchoose as asyncore
|
||||||
import protocol
|
from network.connectionchooser import chooseConnection
|
||||||
|
from network.proxy import Proxy
|
||||||
|
from network.tcp import Socks4aBMConnection, Socks5BMConnection, TCPConnection, TCPServer
|
||||||
|
from network.udp import UDPSocket
|
||||||
from singleton import Singleton
|
from singleton import Singleton
|
||||||
import state
|
|
||||||
import helper_random
|
|
||||||
|
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class BMConnectionPool(object):
|
class BMConnectionPool(object):
|
||||||
|
"""Pool of all existing connections"""
|
||||||
|
# pylint: disable=too-many-instance-attributes
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
asyncore.set_rates(
|
asyncore.set_rates(
|
||||||
BMConfigParser().safeGetInt("bitmessagesettings", "maxdownloadrate"),
|
BMConfigParser().safeGetInt("bitmessagesettings", "maxdownloadrate"),
|
||||||
|
@ -36,9 +42,11 @@ class BMConnectionPool(object):
|
||||||
self.bootstrapped = False
|
self.bootstrapped = False
|
||||||
|
|
||||||
def connectToStream(self, streamNumber):
|
def connectToStream(self, streamNumber):
|
||||||
|
"""Connect to a bitmessage stream"""
|
||||||
self.streams.append(streamNumber)
|
self.streams.append(streamNumber)
|
||||||
|
|
||||||
def getConnectionByAddr(self, addr):
|
def getConnectionByAddr(self, addr):
|
||||||
|
"""Return an (existing) connection object based on a `Peer` object (IP and port)"""
|
||||||
if addr in self.inboundConnections:
|
if addr in self.inboundConnections:
|
||||||
return self.inboundConnections[addr]
|
return self.inboundConnections[addr]
|
||||||
try:
|
try:
|
||||||
|
@ -56,6 +64,7 @@ class BMConnectionPool(object):
|
||||||
raise KeyError
|
raise KeyError
|
||||||
|
|
||||||
def isAlreadyConnected(self, nodeid):
|
def isAlreadyConnected(self, nodeid):
|
||||||
|
"""Check if we're already connected to this peer"""
|
||||||
for i in self.inboundConnections.values() + self.outboundConnections.values():
|
for i in self.inboundConnections.values() + self.outboundConnections.values():
|
||||||
try:
|
try:
|
||||||
if nodeid == i.nodeid:
|
if nodeid == i.nodeid:
|
||||||
|
@ -65,6 +74,7 @@ class BMConnectionPool(object):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def addConnection(self, connection):
|
def addConnection(self, connection):
|
||||||
|
"""Add a connection object to our internal dict"""
|
||||||
if isinstance(connection, UDPSocket):
|
if isinstance(connection, UDPSocket):
|
||||||
return
|
return
|
||||||
if connection.isOutbound:
|
if connection.isOutbound:
|
||||||
|
@ -76,6 +86,7 @@ class BMConnectionPool(object):
|
||||||
self.inboundConnections[connection.destination.host] = connection
|
self.inboundConnections[connection.destination.host] = connection
|
||||||
|
|
||||||
def removeConnection(self, connection):
|
def removeConnection(self, connection):
|
||||||
|
"""Remove a connection from our internal dict"""
|
||||||
if isinstance(connection, UDPSocket):
|
if isinstance(connection, UDPSocket):
|
||||||
del self.udpSockets[connection.listening.host]
|
del self.udpSockets[connection.listening.host]
|
||||||
elif isinstance(connection, TCPServer):
|
elif isinstance(connection, TCPServer):
|
||||||
|
@ -96,18 +107,19 @@ class BMConnectionPool(object):
|
||||||
connection.close()
|
connection.close()
|
||||||
|
|
||||||
def getListeningIP(self):
|
def getListeningIP(self):
|
||||||
|
"""What IP are we supposed to be listening on?"""
|
||||||
|
# pylint: disable=no-self-use
|
||||||
if BMConfigParser().safeGet("bitmessagesettings", "onionhostname").endswith(".onion"):
|
if BMConfigParser().safeGet("bitmessagesettings", "onionhostname").endswith(".onion"):
|
||||||
host = BMConfigParser().safeGet("bitmessagesettings", "onionbindip")
|
host = BMConfigParser().safeGet("bitmessagesettings", "onionbindip")
|
||||||
else:
|
else:
|
||||||
host = '127.0.0.1'
|
host = '127.0.0.1'
|
||||||
if BMConfigParser().safeGetBoolean("bitmessagesettings", "sockslisten") or \
|
if BMConfigParser().safeGetBoolean("bitmessagesettings", "sockslisten") or \
|
||||||
BMConfigParser().get("bitmessagesettings", "socksproxytype") == "none":
|
BMConfigParser().get("bitmessagesettings", "socksproxytype") == "none":
|
||||||
# python doesn't like bind + INADDR_ANY?
|
|
||||||
#host = socket.INADDR_ANY
|
|
||||||
host = BMConfigParser().get("network", "bind")
|
host = BMConfigParser().get("network", "bind")
|
||||||
return host
|
return host
|
||||||
|
|
||||||
def startListening(self, bind=None):
|
def startListening(self, bind=None):
|
||||||
|
"""Open a listening socket and start accepting connections on it"""
|
||||||
if bind is None:
|
if bind is None:
|
||||||
bind = self.getListeningIP()
|
bind = self.getListeningIP()
|
||||||
port = BMConfigParser().safeGetInt("bitmessagesettings", "port")
|
port = BMConfigParser().safeGetInt("bitmessagesettings", "port")
|
||||||
|
@ -116,6 +128,10 @@ class BMConnectionPool(object):
|
||||||
self.listeningSockets[ls.destination] = ls
|
self.listeningSockets[ls.destination] = ls
|
||||||
|
|
||||||
def startUDPSocket(self, bind=None):
|
def startUDPSocket(self, bind=None):
|
||||||
|
"""
|
||||||
|
Open an UDP socket. Depending on settings, it can either only accept incoming UDP packets, or also be able to
|
||||||
|
send them.
|
||||||
|
"""
|
||||||
if bind is None:
|
if bind is None:
|
||||||
host = self.getListeningIP()
|
host = self.getListeningIP()
|
||||||
udpSocket = UDPSocket(host=host, announcing=True)
|
udpSocket = UDPSocket(host=host, announcing=True)
|
||||||
|
@ -127,7 +143,8 @@ class BMConnectionPool(object):
|
||||||
self.udpSockets[udpSocket.listening.host] = udpSocket
|
self.udpSockets[udpSocket.listening.host] = udpSocket
|
||||||
|
|
||||||
def loop(self):
|
def loop(self):
|
||||||
# defaults to empty loop if outbound connections are maxed
|
"""defaults to empty loop if outbound connections are maxed"""
|
||||||
|
# pylint: disable=too-many-statements,too-many-branches,too-many-nested-blocks
|
||||||
spawnConnections = False
|
spawnConnections = False
|
||||||
acceptConnections = True
|
acceptConnections = True
|
||||||
if BMConfigParser().safeGetBoolean('bitmessagesettings', 'dontconnect'):
|
if BMConfigParser().safeGetBoolean('bitmessagesettings', 'dontconnect'):
|
||||||
|
@ -135,7 +152,7 @@ class BMConnectionPool(object):
|
||||||
elif BMConfigParser().safeGetBoolean('bitmessagesettings', 'sendoutgoingconnections'):
|
elif BMConfigParser().safeGetBoolean('bitmessagesettings', 'sendoutgoingconnections'):
|
||||||
spawnConnections = True
|
spawnConnections = True
|
||||||
if BMConfigParser().get('bitmessagesettings', 'socksproxytype')[0:5] == 'SOCKS' and \
|
if BMConfigParser().get('bitmessagesettings', 'socksproxytype')[0:5] == 'SOCKS' and \
|
||||||
(not BMConfigParser().getboolean('bitmessagesettings', 'sockslisten') and \
|
(not BMConfigParser().getboolean('bitmessagesettings', 'sockslisten') and
|
||||||
".onion" not in BMConfigParser().get('bitmessagesettings', 'onionhostname')):
|
".onion" not in BMConfigParser().get('bitmessagesettings', 'onionhostname')):
|
||||||
acceptConnections = False
|
acceptConnections = False
|
||||||
|
|
||||||
|
@ -146,8 +163,8 @@ class BMConnectionPool(object):
|
||||||
self.bootstrapped = True
|
self.bootstrapped = True
|
||||||
Proxy.proxy = (BMConfigParser().safeGet("bitmessagesettings", "sockshostname"),
|
Proxy.proxy = (BMConfigParser().safeGet("bitmessagesettings", "sockshostname"),
|
||||||
BMConfigParser().safeGetInt("bitmessagesettings", "socksport"))
|
BMConfigParser().safeGetInt("bitmessagesettings", "socksport"))
|
||||||
# TODO AUTH
|
# .. todo:: AUTH
|
||||||
# TODO reset based on GUI settings changes
|
# .. todo:: reset based on GUI settings changes
|
||||||
try:
|
try:
|
||||||
if not BMConfigParser().get("network", "onionsocksproxytype").startswith("SOCKS"):
|
if not BMConfigParser().get("network", "onionsocksproxytype").startswith("SOCKS"):
|
||||||
raise NoOptionError
|
raise NoOptionError
|
||||||
|
@ -171,12 +188,6 @@ class BMConnectionPool(object):
|
||||||
if chosen in state.ownAddresses:
|
if chosen in state.ownAddresses:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
#for c in self.outboundConnections:
|
|
||||||
# if chosen == c.destination:
|
|
||||||
# continue
|
|
||||||
#for c in self.inboundConnections:
|
|
||||||
# if chosen.host == c.destination.host:
|
|
||||||
# continue
|
|
||||||
try:
|
try:
|
||||||
if chosen.host.endswith(".onion") and Proxy.onionproxy is not None:
|
if chosen.host.endswith(".onion") and Proxy.onionproxy is not None:
|
||||||
if BMConfigParser().get("network", "onionsocksproxytype") == "SOCKS5":
|
if BMConfigParser().get("network", "onionsocksproxytype") == "SOCKS5":
|
||||||
|
@ -203,7 +214,7 @@ class BMConnectionPool(object):
|
||||||
self.outboundConnections.values()
|
self.outboundConnections.values()
|
||||||
):
|
):
|
||||||
i.set_state("close")
|
i.set_state("close")
|
||||||
# FIXME: rating will be increased after next connection
|
# .. todo:: FIXME: rating will be increased after next connection
|
||||||
i.handle_close()
|
i.handle_close()
|
||||||
|
|
||||||
if acceptConnections:
|
if acceptConnections:
|
||||||
|
@ -211,14 +222,14 @@ class BMConnectionPool(object):
|
||||||
if BMConfigParser().safeGet("network", "bind") == '':
|
if BMConfigParser().safeGet("network", "bind") == '':
|
||||||
self.startListening()
|
self.startListening()
|
||||||
else:
|
else:
|
||||||
for bind in re.sub("[^\w.]+", " ", BMConfigParser().safeGet("network", "bind")).split():
|
for bind in re.sub(r"[^\w.]+", " ", BMConfigParser().safeGet("network", "bind")).split():
|
||||||
self.startListening(bind)
|
self.startListening(bind)
|
||||||
logger.info('Listening for incoming connections.')
|
logger.info('Listening for incoming connections.')
|
||||||
if not self.udpSockets:
|
if not self.udpSockets:
|
||||||
if BMConfigParser().safeGet("network", "bind") == '':
|
if BMConfigParser().safeGet("network", "bind") == '':
|
||||||
self.startUDPSocket()
|
self.startUDPSocket()
|
||||||
else:
|
else:
|
||||||
for bind in re.sub("[^\w.]+", " ", BMConfigParser().safeGet("network", "bind")).split():
|
for bind in re.sub(r"[^\w.]+", " ", BMConfigParser().safeGet("network", "bind")).split():
|
||||||
self.startUDPSocket(bind)
|
self.startUDPSocket(bind)
|
||||||
self.startUDPSocket(False)
|
self.startUDPSocket(False)
|
||||||
logger.info('Starting UDP socket(s).')
|
logger.info('Starting UDP socket(s).')
|
||||||
|
@ -239,7 +250,6 @@ class BMConnectionPool(object):
|
||||||
loopTime = 2.0
|
loopTime = 2.0
|
||||||
asyncore.loop(timeout=loopTime, count=1000)
|
asyncore.loop(timeout=loopTime, count=1000)
|
||||||
|
|
||||||
reaper = []
|
|
||||||
for i in self.inboundConnections.values() + self.outboundConnections.values():
|
for i in self.inboundConnections.values() + self.outboundConnections.values():
|
||||||
minTx = time.time() - 20
|
minTx = time.time() - 20
|
||||||
if i.fullyEstablished:
|
if i.fullyEstablished:
|
||||||
|
@ -250,14 +260,18 @@ class BMConnectionPool(object):
|
||||||
else:
|
else:
|
||||||
i.close_reason = "Timeout (%is)" % (time.time() - i.lastTx)
|
i.close_reason = "Timeout (%is)" % (time.time() - i.lastTx)
|
||||||
i.set_state("close")
|
i.set_state("close")
|
||||||
for i in self.inboundConnections.values() + self.outboundConnections.values() + self.listeningSockets.values() + self.udpSockets.values():
|
|
||||||
|
all_connections = list()
|
||||||
|
all_connections.extend(self.inboundConnections.values())
|
||||||
|
all_connections.extend(self.outboundConnections.values())
|
||||||
|
all_connections.extend(self.listeningSockets.values())
|
||||||
|
all_connections.extend(self.udpSockets.values())
|
||||||
|
for i in all_connections:
|
||||||
if not (i.accepting or i.connecting or i.connected):
|
if not (i.accepting or i.connecting or i.connected):
|
||||||
reaper.append(i)
|
self.removeConnection(i)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
if i.state == "close":
|
if i.state == "close":
|
||||||
reaper.append(i)
|
self.removeConnection(i)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
for i in reaper:
|
|
||||||
self.removeConnection(i)
|
|
||||||
|
|
|
@ -1,14 +1,22 @@
|
||||||
|
"""
|
||||||
|
src/network/proxy.py
|
||||||
|
====================
|
||||||
|
|
||||||
|
"""
|
||||||
|
# pylint: disable=protected-access
|
||||||
|
|
||||||
import socket
|
import socket
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from advanceddispatcher import AdvancedDispatcher
|
|
||||||
import asyncore_pollchoose as asyncore
|
import asyncore_pollchoose as asyncore
|
||||||
|
import state
|
||||||
|
from advanceddispatcher import AdvancedDispatcher
|
||||||
from bmconfigparser import BMConfigParser
|
from bmconfigparser import BMConfigParser
|
||||||
from debug import logger
|
from debug import logger
|
||||||
import network.connectionpool
|
|
||||||
import state
|
|
||||||
|
|
||||||
class ProxyError(Exception):
|
class ProxyError(Exception):
|
||||||
|
"""Base proxy exception class"""
|
||||||
errorCodes = ("UnknownError")
|
errorCodes = ("UnknownError")
|
||||||
|
|
||||||
def __init__(self, code=-1):
|
def __init__(self, code=-1):
|
||||||
|
@ -21,6 +29,7 @@ class ProxyError(Exception):
|
||||||
|
|
||||||
|
|
||||||
class GeneralProxyError(ProxyError):
|
class GeneralProxyError(ProxyError):
|
||||||
|
"""General proxy error class (not specfic to an implementation)"""
|
||||||
errorCodes = ("Success",
|
errorCodes = ("Success",
|
||||||
"Invalid data",
|
"Invalid data",
|
||||||
"Not connected",
|
"Not connected",
|
||||||
|
@ -34,6 +43,7 @@ class GeneralProxyError(ProxyError):
|
||||||
|
|
||||||
|
|
||||||
class Proxy(AdvancedDispatcher):
|
class Proxy(AdvancedDispatcher):
|
||||||
|
"""Base proxy class for AdvancedDispatcher"""
|
||||||
# these are global, and if you change config during runtime, all active/new
|
# these are global, and if you change config during runtime, all active/new
|
||||||
# instances should change too
|
# instances should change too
|
||||||
_proxy = ("127.0.0.1", 9050)
|
_proxy = ("127.0.0.1", 9050)
|
||||||
|
@ -44,10 +54,12 @@ class Proxy(AdvancedDispatcher):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def proxy(self):
|
def proxy(self):
|
||||||
|
"""Return proxy IP and port"""
|
||||||
return self.__class__._proxy
|
return self.__class__._proxy
|
||||||
|
|
||||||
@proxy.setter
|
@proxy.setter
|
||||||
def proxy(self, address):
|
def proxy(self, address):
|
||||||
|
"""Set proxy IP and port"""
|
||||||
if not isinstance(address, tuple) or (len(address) < 2) or \
|
if not isinstance(address, tuple) or (len(address) < 2) or \
|
||||||
(not isinstance(address[0], str) or not isinstance(address[1], int)):
|
(not isinstance(address[0], str) or not isinstance(address[1], int)):
|
||||||
raise ValueError
|
raise ValueError
|
||||||
|
@ -55,29 +67,35 @@ class Proxy(AdvancedDispatcher):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def auth(self):
|
def auth(self):
|
||||||
|
"""Return proxy authentication settings"""
|
||||||
return self.__class__._auth
|
return self.__class__._auth
|
||||||
|
|
||||||
@auth.setter
|
@auth.setter
|
||||||
def auth(self, authTuple):
|
def auth(self, authTuple):
|
||||||
|
"""Set proxy authentication (username and password)"""
|
||||||
self.__class__._auth = authTuple
|
self.__class__._auth = authTuple
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def onion_proxy(self):
|
def onion_proxy(self):
|
||||||
|
"""Return separate proxy IP and port for use only with onion addresses. Untested."""
|
||||||
return self.__class__._onion_proxy
|
return self.__class__._onion_proxy
|
||||||
|
|
||||||
@onion_proxy.setter
|
@onion_proxy.setter
|
||||||
def onion_proxy(self, address):
|
def onion_proxy(self, address):
|
||||||
if address is not None and (not isinstance(address, tuple) or (len(address) < 2) or \
|
"""Set onion proxy address"""
|
||||||
|
if address is not None and (not isinstance(address, tuple) or (len(address) < 2) or
|
||||||
(not isinstance(address[0], str) or not isinstance(address[1], int))):
|
(not isinstance(address[0], str) or not isinstance(address[1], int))):
|
||||||
raise ValueError
|
raise ValueError
|
||||||
self.__class__._onion_proxy = address
|
self.__class__._onion_proxy = address
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def onion_auth(self):
|
def onion_auth(self):
|
||||||
|
"""Set proxy authentication for onion hosts only. Untested"""
|
||||||
return self.__class__._onion_auth
|
return self.__class__._onion_auth
|
||||||
|
|
||||||
@onion_auth.setter
|
@onion_auth.setter
|
||||||
def onion_auth(self, authTuple):
|
def onion_auth(self, authTuple):
|
||||||
|
"""Set proxy authentication for onion hosts only. Untested"""
|
||||||
self.__class__._onion_auth = authTuple
|
self.__class__._onion_auth = authTuple
|
||||||
|
|
||||||
def __init__(self, address):
|
def __init__(self, address):
|
||||||
|
@ -99,6 +117,7 @@ class Proxy(AdvancedDispatcher):
|
||||||
self.connect(self.proxy)
|
self.connect(self.proxy)
|
||||||
|
|
||||||
def handle_connect(self):
|
def handle_connect(self):
|
||||||
|
"""Handle connection event (to the proxy)"""
|
||||||
self.set_state("init")
|
self.set_state("init")
|
||||||
try:
|
try:
|
||||||
AdvancedDispatcher.handle_connect(self)
|
AdvancedDispatcher.handle_connect(self)
|
||||||
|
@ -109,5 +128,7 @@ class Proxy(AdvancedDispatcher):
|
||||||
self.state_init()
|
self.state_init()
|
||||||
|
|
||||||
def state_proxy_handshake_done(self):
|
def state_proxy_handshake_done(self):
|
||||||
|
"""Handshake is complete at this point"""
|
||||||
|
# pylint: disable=attribute-defined-outside-init
|
||||||
self.connectedAt = time.time()
|
self.connectedAt = time.time()
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -1,21 +1,29 @@
|
||||||
|
"""
|
||||||
|
src/storage/filesystem.py
|
||||||
|
=========================
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import string
|
||||||
|
import time
|
||||||
from binascii import hexlify, unhexlify
|
from binascii import hexlify, unhexlify
|
||||||
from os import listdir, makedirs, path, remove, rmdir
|
from os import listdir, makedirs, path, remove, rmdir
|
||||||
import string
|
|
||||||
from threading import RLock
|
from threading import RLock
|
||||||
import time
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
from paths import lookupAppdataFolder
|
from paths import lookupAppdataFolder
|
||||||
from storage import InventoryStorage, InventoryItem
|
from storage import InventoryItem, InventoryStorage
|
||||||
|
|
||||||
class FilesystemInventory(InventoryStorage):
|
|
||||||
|
class FilesystemInventory(InventoryStorage): # pylint: disable=too-many-ancestors
|
||||||
|
"""Module for using filesystem (directory with files) for inventory storage"""
|
||||||
|
# pylint: disable=abstract-method
|
||||||
topDir = "inventory"
|
topDir = "inventory"
|
||||||
objectDir = "objects"
|
objectDir = "objects"
|
||||||
metadataFilename = "metadata"
|
metadataFilename = "metadata"
|
||||||
dataFilename = "data"
|
dataFilename = "data"
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(self.__class__, self).__init__()
|
super(FilesystemInventory, self).__init__()
|
||||||
self.baseDir = path.join(lookupAppdataFolder(), FilesystemInventory.topDir)
|
self.baseDir = path.join(lookupAppdataFolder(), FilesystemInventory.topDir)
|
||||||
for createDir in [self.baseDir, path.join(self.baseDir, "objects")]:
|
for createDir in [self.baseDir, path.join(self.baseDir, "objects")]:
|
||||||
if path.exists(createDir):
|
if path.exists(createDir):
|
||||||
|
@ -23,72 +31,85 @@ class FilesystemInventory(InventoryStorage):
|
||||||
raise IOError("%s exists but it's not a directory" % (createDir))
|
raise IOError("%s exists but it's not a directory" % (createDir))
|
||||||
else:
|
else:
|
||||||
makedirs(createDir)
|
makedirs(createDir)
|
||||||
self.lock = RLock() # Guarantees that two receiveDataThreads don't receive and process the same message concurrently (probably sent by a malicious individual)
|
self.lock = RLock()
|
||||||
|
# Guarantees that two receiveDataThreads don't receive and process the
|
||||||
|
# same message concurrently (probably sent by a malicious individual)
|
||||||
self._inventory = {}
|
self._inventory = {}
|
||||||
self._load()
|
self._load()
|
||||||
|
|
||||||
def __contains__(self, hash):
|
def __contains__(self, hash_):
|
||||||
retval = False
|
|
||||||
for streamDict in self._inventory.values():
|
for streamDict in self._inventory.values():
|
||||||
if hash in streamDict:
|
if hash_ in streamDict:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def __getitem__(self, hash):
|
def __getitem__(self, hash_):
|
||||||
for streamDict in self._inventory.values():
|
for streamDict in self._inventory.values():
|
||||||
try:
|
try:
|
||||||
retval = streamDict[hash]
|
retval = streamDict[hash_]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
continue
|
continue
|
||||||
if retval.payload is None:
|
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(hash_), retval.expires, retval.tag)
|
||||||
return retval
|
return retval
|
||||||
raise KeyError(hash)
|
raise KeyError(hash_)
|
||||||
|
|
||||||
def __setitem__(self, hash, value):
|
def __setitem__(self, hash_, value):
|
||||||
with self.lock:
|
with self.lock:
|
||||||
value = InventoryItem(*value)
|
value = InventoryItem(*value)
|
||||||
try:
|
try:
|
||||||
makedirs(path.join(self.baseDir, FilesystemInventory.objectDir, hexlify(hash)))
|
makedirs(path.join(self.baseDir, FilesystemInventory.objectDir, hexlify(hash_)))
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
with open(path.join(self.baseDir, FilesystemInventory.objectDir, hexlify(hash), FilesystemInventory.metadataFilename), 'w') as f:
|
inv_path = path.join(self.baseDir, FilesystemInventory.objectDir, hexlify(hash_))
|
||||||
|
with open(path.join(inv_path, FilesystemInventory.metadataFilename), 'w') as f:
|
||||||
f.write("%s,%s,%s,%s," % (value.type, value.stream, value.expires, hexlify(value.tag)))
|
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(inv_path, FilesystemInventory.dataFilename), 'w') as f:
|
||||||
f.write(value.payload)
|
f.write(value.payload)
|
||||||
except IOError:
|
except IOError:
|
||||||
raise KeyError
|
raise KeyError
|
||||||
try:
|
try:
|
||||||
self._inventory[value.stream][hash] = value
|
self._inventory[value.stream][hash_] = value
|
||||||
except KeyError:
|
except KeyError:
|
||||||
self._inventory[value.stream] = {}
|
self._inventory[value.stream] = {}
|
||||||
self._inventory[value.stream][hash] = value
|
self._inventory[value.stream][hash_] = value
|
||||||
|
|
||||||
def delHashId(self, hash):
|
def delHashId(self, hash_):
|
||||||
for stream in self._inventory.keys():
|
"""Remove object from inventory"""
|
||||||
|
for stream in self._inventory:
|
||||||
try:
|
try:
|
||||||
del self._inventory[stream][hash]
|
del self._inventory[stream][hash_]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
with self.lock:
|
with self.lock:
|
||||||
try:
|
try:
|
||||||
remove(path.join(self.baseDir, FilesystemInventory.objectDir, hexlify(hash), FilesystemInventory.metadataFilename))
|
remove(
|
||||||
|
path.join(
|
||||||
|
self.baseDir,
|
||||||
|
FilesystemInventory.objectDir,
|
||||||
|
hexlify(hash_),
|
||||||
|
FilesystemInventory.metadataFilename))
|
||||||
except IOError:
|
except IOError:
|
||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
remove(path.join(self.baseDir, FilesystemInventory.objectDir, hexlify(hash), FilesystemInventory.dataFilename))
|
remove(
|
||||||
|
path.join(
|
||||||
|
self.baseDir,
|
||||||
|
FilesystemInventory.objectDir,
|
||||||
|
hexlify(hash_),
|
||||||
|
FilesystemInventory.dataFilename))
|
||||||
except IOError:
|
except IOError:
|
||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
rmdir(path.join(self.baseDir, FilesystemInventory.objectDir, hexlify(hash)))
|
rmdir(path.join(self.baseDir, FilesystemInventory.objectDir, hexlify(hash_)))
|
||||||
except IOError:
|
except IOError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
elems = []
|
elems = []
|
||||||
for streamDict in self._inventory.values():
|
for streamDict in self._inventory.values():
|
||||||
elems.extend (streamDict.keys())
|
elems.extend(streamDict.keys())
|
||||||
return elems.__iter__()
|
return elems.__iter__()
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
|
@ -103,39 +124,47 @@ class FilesystemInventory(InventoryStorage):
|
||||||
try:
|
try:
|
||||||
objectType, streamNumber, expiresTime, tag = self.getMetadata(hashId)
|
objectType, streamNumber, expiresTime, tag = self.getMetadata(hashId)
|
||||||
try:
|
try:
|
||||||
newInventory[streamNumber][hashId] = InventoryItem(objectType, streamNumber, None, expiresTime, tag)
|
newInventory[streamNumber][hashId] = InventoryItem(
|
||||||
|
objectType, streamNumber, None, expiresTime, tag)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
newInventory[streamNumber] = {}
|
newInventory[streamNumber] = {}
|
||||||
newInventory[streamNumber][hashId] = InventoryItem(objectType, streamNumber, None, expiresTime, tag)
|
newInventory[streamNumber][hashId] = InventoryItem(
|
||||||
|
objectType, streamNumber, None, expiresTime, tag)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
print "error loading %s" % (hexlify(hashId))
|
print "error loading %s" % (hexlify(hashId))
|
||||||
pass
|
|
||||||
self._inventory = newInventory
|
self._inventory = newInventory
|
||||||
# for i, v in self._inventory.items():
|
|
||||||
# print "loaded stream: %s, %i items" % (i, len(v))
|
|
||||||
|
|
||||||
def stream_list(self):
|
def stream_list(self):
|
||||||
|
"""Return list of streams"""
|
||||||
return self._inventory.keys()
|
return self._inventory.keys()
|
||||||
|
|
||||||
def object_list(self):
|
def object_list(self):
|
||||||
|
"""Return inventory vectors (hashes) from a directory"""
|
||||||
return [unhexlify(x) for x in listdir(path.join(self.baseDir, FilesystemInventory.objectDir))]
|
return [unhexlify(x) for x in listdir(path.join(self.baseDir, FilesystemInventory.objectDir))]
|
||||||
|
|
||||||
def getData(self, hashId):
|
def getData(self, hashId):
|
||||||
|
"""Get object data"""
|
||||||
try:
|
try:
|
||||||
with open(path.join(self.baseDir, FilesystemInventory.objectDir, hexlify(hashId), FilesystemInventory.dataFilename), 'r') as f:
|
inv_path = path.join(self.baseDir, FilesystemInventory.objectDir, hexlify(hashId))
|
||||||
|
with open(path.join(inv_path, FilesystemInventory.dataFilename), 'r') as f:
|
||||||
return f.read()
|
return f.read()
|
||||||
except IOError:
|
except IOError:
|
||||||
raise AttributeError
|
raise AttributeError
|
||||||
|
|
||||||
def getMetadata(self, hashId):
|
def getMetadata(self, hashId):
|
||||||
|
"""Get object metadata"""
|
||||||
|
# pylint: disable=unused-variable
|
||||||
try:
|
try:
|
||||||
with open(path.join(self.baseDir, FilesystemInventory.objectDir, hexlify(hashId), FilesystemInventory.metadataFilename), 'r') as f:
|
inv_path = path.join(self.baseDir, FilesystemInventory.objectDir, hexlify(hashId))
|
||||||
|
with open(path.join(inv_path, FilesystemInventory.metadataFilename), 'r') as f:
|
||||||
objectType, streamNumber, expiresTime, tag, undef = string.split(f.read(), ",", 4)
|
objectType, streamNumber, expiresTime, tag, undef = string.split(f.read(), ",", 4)
|
||||||
return [int(objectType), int(streamNumber), int(expiresTime), unhexlify(tag)]
|
return [int(objectType), int(streamNumber), int(expiresTime), unhexlify(tag)]
|
||||||
except IOError:
|
except IOError:
|
||||||
raise KeyError
|
raise KeyError
|
||||||
|
|
||||||
def by_type_and_tag(self, objectType, tag):
|
def by_type_and_tag(self, objectType, tag):
|
||||||
|
"""Get object type and tag"""
|
||||||
|
# pylint: disable=unused-variable
|
||||||
retval = []
|
retval = []
|
||||||
for stream, streamDict in self._inventory:
|
for stream, streamDict in self._inventory:
|
||||||
for hashId, item in streamDict:
|
for hashId, item in streamDict:
|
||||||
|
@ -149,12 +178,14 @@ class FilesystemInventory(InventoryStorage):
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
def hashes_by_stream(self, stream):
|
def hashes_by_stream(self, stream):
|
||||||
|
"""Return inventory vectors (hashes) for a stream"""
|
||||||
try:
|
try:
|
||||||
return self._inventory[stream].keys()
|
return self._inventory[stream].keys()
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def unexpired_hashes_by_stream(self, stream):
|
def unexpired_hashes_by_stream(self, stream):
|
||||||
|
"""Return unexpired hashes in the inventory for a particular stream"""
|
||||||
t = int(time.time())
|
t = int(time.time())
|
||||||
try:
|
try:
|
||||||
return [x for x, value in self._inventory[stream].items() if value.expires > t]
|
return [x for x, value in self._inventory[stream].items() if value.expires > t]
|
||||||
|
@ -162,12 +193,14 @@ class FilesystemInventory(InventoryStorage):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def flush(self):
|
def flush(self):
|
||||||
|
"""Flush the inventory and create a new, empty one"""
|
||||||
self._load()
|
self._load()
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
|
"""Clean out old items from the inventory"""
|
||||||
minTime = int(time.time()) - (60 * 60 * 30)
|
minTime = int(time.time()) - (60 * 60 * 30)
|
||||||
deletes = []
|
deletes = []
|
||||||
for stream, streamDict in self._inventory.items():
|
for _, streamDict in self._inventory.items():
|
||||||
for hashId, item in streamDict.items():
|
for hashId, item in streamDict.items():
|
||||||
if item.expires < minTime:
|
if item.expires < minTime:
|
||||||
deletes.append(hashId)
|
deletes.append(hashId)
|
||||||
|
|
Reference in New Issue
Block a user