<commit_before>"""Utilities that can be imported by tests.  You need to make
sure your PYTHONPATH includes the directory containing this
file in order for the tests that use it to work.  The test
harness does that automatically for you."""

import dbus
bus = dbus.SessionBus()
dbusOrca = bus.get_object('org.gnome.Orca', '/')
dbusOrcaLogging = dbus.Interface(dbusOrca, 'org.gnome.Orca.Logging')

# Where to find Dojo tests.
#
DojoNightlyURLPrefix="http://archive.dojotoolkit.org/nightly/dojotoolkit/dijit/tests/"
#DojoURLPrefix="http://localhost/apache2-default/dojo-release-1.1.0b2/dijit/tests/"
#DojoURLPrefix="http://bashautomation.com/dojo-release-1.1.0b2/dijit/tests/"
DojoJuly09URLPrefix="http://archive.dojotoolkit.org/dojo-2009-08-05/dojotoolkit/dijit/tests/"
DojoURLPrefix=DojoJuly09URLPrefix

# Where to find our local HTML tests.
#
import sys, os, re
wd = os.path.dirname(sys.argv[0])
fullPath = os.path.abspath(wd)
htmlDir = os.path.abspath(fullPath + "/../../html")
htmlURLPrefix = "file://" + htmlDir + "/"

# Various Firefox names as a regex.  These are needed because they like
# to do various things with the app and frame names from release to release.
# These regex's attempt to provide a way to manage those differences.
#
firefoxAppNames = "(Firefox|Minefield)"
firefoxFrameNames = "(Mozilla Firefox|Mozilla Firefox 3.1 Beta 2|Mozilla Firefox 3.1 Beta 3|Minefield|Mozilla Firefox 3.5 Beta 4)"
firefoxLocationBarNames = "(Location|Search Bookmarks and History)"

# Various OpenOffice names as a regex.  These are needed because OOo likes
# to do various things with its window titles from release to release.
# These regex's attempt to provide a way to manage those differences.
#
OOoWriterNames = "(StarOffice Writer|OpenOffice.org Writer|OOo-dev Writer Beta)"

#OOO_VERSION="OpenOffice 2.4"
#OOO_VERSION="StarOffice 8"
#OOO_VERSION="DEV300_m25"
#OOO_VERSION="DEV300_m29"
OOO_VERSION="OpenOffice 3.0"

def getOOoName(app):
    if OOO_VERSION in ["OpenOffice 2.4", "OpenOffice 3.0"]:
        return "OpenOffice.org %s" % app
    elif OOO_VERSION == "StarOffice 8":
        return "StarOffice %s" % app
    elif OOO_VERSION == "DEV300_m25":
        return "OOo-dev %s Beta" % app
    elif OOO_VERSION == "DEV300_m29":
        return "OOo-dev %s" % app

def getOOoBrailleLine(app, title, remainder):
    if OOO_VERSION in ["OpenOffice 2.4", "OpenOffice 3.0"]:
        return "soffice Application " + title + " - OpenOffice.org " + app + " Frame " +  title + " - OpenOffice.org " + app + " RootPane ScrollPane Document view " + remainder
    elif OOO_VERSION == "StarOffice 8":
        return "soffice Application " + title + " - StarOffice " + app + " Frame " +  title + " - StarOffice " + app + " RootPane ScrollPane Document view " + remainder
    elif OOO_VERSION == "DEV300_m25":
        return "soffice Application " + title + " - OOo-dev " + app + " Beta Frame " +  title + " - OOo-dev " + app + " Beta RootPane ScrollPane Document view " + remainder
    elif OOO_VERSION == "DEV300_m29":
        return "soffice Application " + title + " - OOo-dev " + app + " Frame " +  title + " - OOo-dev " + app + " RootPane ScrollPane Document view " + remainder

createDiffs = True
try:
    # If the difflib module is not found, fall back to generating the
    # old-style EXPECTED/ACTUAL output.
    #
    import difflib
except:
    createDiffs = False

from macaroon.playback import *

enable_assert = \
    environ.get('HARNESS_ASSERT', 'yes') in ('yes', 'true', 'y', '1', 1)
errFilename = environ.get('HARNESS_ERR', None)
outFilename = environ.get('HARNESS_OUT', None)

if errFilename and len(errFilename):
    myErr = open(errFilename, 'a', 0)
else:
    myErr = sys.stderr

if outFilename and len(outFilename):
    if outFilename == errFilename:
        myOut = myErr
    else:
        myOut = open(outFilename, 'a', 0)
else:
    myOut = sys.stdout

class StartRecordingAction(AtomicAction):
    '''Tells Orca to log speech and braille output to a string which we
    can later obtain and use in an assertion (see AssertPresentationAction)'''

    def __init__(self):
        if enable_assert:
            AtomicAction.__init__(self, 1000, self._startRecording)
        else:
            AtomicAction.__init__(self, 0, lambda: None)

    def _startRecording(self):
        dbusOrcaLogging.startRecording()

    def __str__(self):
        return 'Start Recording Action'

def assertListEquality(rawOrcaResults, expectedList):
    '''Convert raw speech and braille output obtained from Orca into a
    list by splitting it at newline boundaries.  Compare it to the
    list passed in and return the actual results if they differ.
    Otherwise, return None to indicate an equality.'''

    results = rawOrcaResults.strip().split("\n")

    # Shoot for a string comparison first.
    #
    if results == expectedList:
        return None
    elif len(results) != len(expectedList):
        return results

    # If the string comparison failed, do a regex match item by item
    #
    for i in range(0, len(expectedList)):
	if results[i] == expectedList[i]:
            continue
        else:
            expectedResultRE = re.compile(expectedList[i].decode("UTF-8",
                                                                 "replace"))
            if expectedResultRE.match(results[i].decode("UTF-8",
                                                        "replace")):
                continue
            else:
                return results

    return None

class AssertPresentationAction(AtomicAction):
    '''Ask Orca for the speech and braille logged since the last use
    of StartRecordingAction and apply an assertion predicate.'''

    totalCount = 0
    totalSucceed = 0
    totalFail = 0
    totalKnownIssues = 0

    def __init__(self, name, expectedResults,
                 assertionPredicate=assertListEquality):
        '''name:               the name of the test
           expectedResults:    the results we want (typically a list of strings
                               that can be treated as regular expressions)
           assertionPredicate: method to compare actual results to expected
                               results
        '''
        # [[[WDW: the pause is to wait for Orca to process an event.
        # Probably should think of a better way to do this.]]]
        #
        if enable_assert:
            AtomicAction.__init__(self, 1000, self._stopRecording)
            self._name = sys.argv[0] + ":" + name
            self._expectedResults = expectedResults
            self._assertionPredicate = assertionPredicate
            AssertPresentationAction.totalCount += 1
            self._num = AssertPresentationAction.totalCount
        else:
            AtomicAction.__init__(self, 0, lambda: None)

    def printDiffs(self, results):
        """Compare the expected results with the actual results and print
        out a set of diffs.

        Arguments:
        - results: the actual results.

        Returns an indication of whether this test was expected to fail.
        """

        knownIssue = False
        print >> myErr, "DIFFERENCES FOUND:"
        if isinstance(self._expectedResults, [].__class__):
            for result in self._expectedResults:
                if result.startswith("KNOWN ISSUE") \
                   or result.startswith("BUG?"):
                    knownIssue = True
        else:
            if self._expectedResults.startswith("KNOWN ISSUE") \
               or self._expectedResults.startswith("BUG?"):
                knownIssue = True

        d = difflib.Differ()
        try:
            # This can stack trace for some odd reason (UTF-8 characters?),
            # so we need to capture it.  Otherwise, it can hang the tests.
            #
            diffs = list(d.compare(self._expectedResults, results))
            print >> myErr, '\n'.join(list(diffs))
        except:
            print >> myErr, "(ERROR COMPUTING DIFFERENCES!!!)"
            for i in range(0, max(len(results), len(self._expectedResults))):
                try:
                    print >> myErr, "  EXPECTED: %s" \
                          % self._expectedResults[i].decode("UTF-8", "replace")
                except:
                    pass
                try:
                    print >> myErr, "  ACTUAL:   %s" \
                          % results[i].decode("UTF-8", "replace")
                except:
                    pass

        return knownIssue

    def printResults(self, results):
        """Print out the expected results and the actual results.

        Arguments:
        - results: the actual results.

        Returns an indication of whether this test was expected to fail.
        """

        knownIssue = False
        print >> myErr, "EXPECTED:"
        if isinstance(self._expectedResults, [].__class__):
            for result in self._expectedResults:
                if result.startswith("KNOWN ISSUE") \
                        or result.startswith("BUG?"):
                    knownIssue = True
                print >> myErr, '     "%s",' % result
        else:
            if self._expectedResults.startswith("KNOWN ISSUE") \
               or self._expectedResults.startswith("BUG?"):
                knownIssue = True
            print >> myErr, '     "%s"' % self._expectedResults
        print >> myErr, "ACTUAL:"
        if isinstance(results, [].__class__):
            for result in results:
                print >> myErr, '     "%s",' % result
        else:
            print >> myErr, '     "%s"' % results
        return knownIssue

    def _stopRecording(self):
        result = dbusOrcaLogging.stopRecording()
        results = self._assertionPredicate(result, self._expectedResults)
        if not results:
            AssertPresentationAction.totalSucceed += 1
            print >> myOut, "Test %d of %d SUCCEEDED: %s" \
                            % (self._num,
                               AssertPresentationAction.totalCount,
                               self._name)
        else:
            AssertPresentationAction.totalFail += 1
            print >> myErr, "Test %d of %d FAILED: %s" \
                            % (self._num,
                               AssertPresentationAction.totalCount,
                               self._name)
            if createDiffs:
                knownIssue = self.printDiffs(results)
            else:
                knownIssue = self.printResults(results)

            if knownIssue:
                AssertPresentationAction.totalKnownIssues += 1
                print >> myErr, '[FAILURE WAS EXPECTED - ' \
                                'LOOK FOR KNOWN ISSUE OR BUG? ' \
                                'IN EXPECTED RESULTS]'
            else:
                print >> myErr, '[FAILURE WAS UNEXPECTED]'

    def __str__(self):
        return 'Assert Presentation Action: %s' % self._name

class AssertionSummaryAction(AtomicAction):
    '''Output the summary of successes and failures of
    AssertPresentationAction assertions.'''

    def __init__(self):
        AtomicAction.__init__(self, 0, self._printSummary)

    def _printSummary(self):
        print >> myOut, \
            "SUMMARY: %d SUCCEEDED and %d FAILED (%d UNEXPECTED) of %d for %s"\
            % (AssertPresentationAction.totalSucceed,
               AssertPresentationAction.totalFail,
               (AssertPresentationAction.totalFail \
               - AssertPresentationAction.totalKnownIssues),
               AssertPresentationAction.totalCount,
               sys.argv[0])

    def __str__(self):
        return 'Start Recording Action'

<commit_msg>Updating the test harness to look for OOo-3.2 dev.<commit_after>"""Utilities that can be imported by tests.  You need to make
sure your PYTHONPATH includes the directory containing this
file in order for the tests that use it to work.  The test
harness does that automatically for you."""

import dbus
bus = dbus.SessionBus()
dbusOrca = bus.get_object('org.gnome.Orca', '/')
dbusOrcaLogging = dbus.Interface(dbusOrca, 'org.gnome.Orca.Logging')

# Where to find Dojo tests.
#
DojoNightlyURLPrefix="http://archive.dojotoolkit.org/nightly/dojotoolkit/dijit/tests/"
#DojoURLPrefix="http://localhost/apache2-default/dojo-release-1.1.0b2/dijit/tests/"
#DojoURLPrefix="http://bashautomation.com/dojo-release-1.1.0b2/dijit/tests/"
DojoJuly09URLPrefix="http://archive.dojotoolkit.org/dojo-2009-08-05/dojotoolkit/dijit/tests/"
DojoURLPrefix=DojoJuly09URLPrefix

# Where to find our local HTML tests.
#
import sys, os, re
wd = os.path.dirname(sys.argv[0])
fullPath = os.path.abspath(wd)
htmlDir = os.path.abspath(fullPath + "/../../html")
htmlURLPrefix = "file://" + htmlDir + "/"

# Various Firefox names as a regex.  These are needed because they like
# to do various things with the app and frame names from release to release.
# These regex's attempt to provide a way to manage those differences.
#
firefoxAppNames = "(Firefox|Minefield)"
firefoxFrameNames = "(Mozilla Firefox|Mozilla Firefox 3.1 Beta 2|Mozilla Firefox 3.1 Beta 3|Minefield|Mozilla Firefox 3.5 Beta 4)"
firefoxLocationBarNames = "(Location|Search Bookmarks and History)"

# Various OpenOffice names as a regex.  These are needed because OOo likes
# to do various things with its window titles from release to release.
# These regex's attempt to provide a way to manage those differences.
#
OOoWriterNames = "(StarOffice Writer|OpenOffice.org Writer|OOo-dev Writer Beta)"

#OOO_VERSION="OpenOffice 2.4"
#OOO_VERSION="StarOffice 8"
#OOO_VERSION="DEV300_m25"
#OOO_VERSION="OpenOffice 3.0"
OOO_VERSION="DEV300_m54" # This is the current dev build of 3.2

def getOOoName(app):
    if OOO_VERSION in ["OpenOffice 2.4", "OpenOffice 3.0"]:
        return "OpenOffice.org %s" % app
    elif OOO_VERSION == "StarOffice 8":
        return "StarOffice %s" % app
    elif OOO_VERSION == "DEV300_m25":
        return "OOo-dev %s Beta" % app
    elif OOO_VERSION == "DEV300_m54":
        return "OOo-dev %s" % app

def getOOoBrailleLine(app, title, remainder):
    if OOO_VERSION in ["OpenOffice 2.4", "OpenOffice 3.0"]:
        return "soffice Application " + title + " - OpenOffice.org " + app + " Frame " +  title + " - OpenOffice.org " + app + " RootPane ScrollPane Document view " + remainder
    elif OOO_VERSION == "StarOffice 8":
        return "soffice Application " + title + " - StarOffice " + app + " Frame " +  title + " - StarOffice " + app + " RootPane ScrollPane Document view " + remainder
    elif OOO_VERSION == "DEV300_m25":
        return "soffice Application " + title + " - OOo-dev " + app + " Beta Frame " +  title + " - OOo-dev " + app + " Beta RootPane ScrollPane Document view " + remainder
    elif OOO_VERSION == "DEV300_m54":
        return "soffice Application " + title + " - OOo-dev " + app + " Frame " +  title + " - OOo-dev " + app + " RootPane ScrollPane Document view " + remainder

createDiffs = True
try:
    # If the difflib module is not found, fall back to generating the
    # old-style EXPECTED/ACTUAL output.
    #
    import difflib
except:
    createDiffs = False

from macaroon.playback import *

enable_assert = \
    environ.get('HARNESS_ASSERT', 'yes') in ('yes', 'true', 'y', '1', 1)
errFilename = environ.get('HARNESS_ERR', None)
outFilename = environ.get('HARNESS_OUT', None)

if errFilename and len(errFilename):
    myErr = open(errFilename, 'a', 0)
else:
    myErr = sys.stderr

if outFilename and len(outFilename):
    if outFilename == errFilename:
        myOut = myErr
    else:
        myOut = open(outFilename, 'a', 0)
else:
    myOut = sys.stdout

class StartRecordingAction(AtomicAction):
    '''Tells Orca to log speech and braille output to a string which we
    can later obtain and use in an assertion (see AssertPresentationAction)'''

    def __init__(self):
        if enable_assert:
            AtomicAction.__init__(self, 1000, self._startRecording)
        else:
            AtomicAction.__init__(self, 0, lambda: None)

    def _startRecording(self):
        dbusOrcaLogging.startRecording()

    def __str__(self):
        return 'Start Recording Action'

def assertListEquality(rawOrcaResults, expectedList):
    '''Convert raw speech and braille output obtained from Orca into a
    list by splitting it at newline boundaries.  Compare it to the
    list passed in and return the actual results if they differ.
    Otherwise, return None to indicate an equality.'''

    results = rawOrcaResults.strip().split("\n")

    # Shoot for a string comparison first.
    #
    if results == expectedList:
        return None
    elif len(results) != len(expectedList):
        return results

    # If the string comparison failed, do a regex match item by item
    #
    for i in range(0, len(expectedList)):
	if results[i] == expectedList[i]:
            continue
        else:
            expectedResultRE = re.compile(expectedList[i].decode("UTF-8",
                                                                 "replace"))
            if expectedResultRE.match(results[i].decode("UTF-8",
                                                        "replace")):
                continue
            else:
                return results

    return None

class AssertPresentationAction(AtomicAction):
    '''Ask Orca for the speech and braille logged since the last use
    of StartRecordingAction and apply an assertion predicate.'''

    totalCount = 0
    totalSucceed = 0
    totalFail = 0
    totalKnownIssues = 0

    def __init__(self, name, expectedResults,
                 assertionPredicate=assertListEquality):
        '''name:               the name of the test
           expectedResults:    the results we want (typically a list of strings
                               that can be treated as regular expressions)
           assertionPredicate: method to compare actual results to expected
                               results
        '''
        # [[[WDW: the pause is to wait for Orca to process an event.
        # Probably should think of a better way to do this.]]]
        #
        if enable_assert:
            AtomicAction.__init__(self, 1000, self._stopRecording)
            self._name = sys.argv[0] + ":" + name
            self._expectedResults = expectedResults
            self._assertionPredicate = assertionPredicate
            AssertPresentationAction.totalCount += 1
            self._num = AssertPresentationAction.totalCount
        else:
            AtomicAction.__init__(self, 0, lambda: None)

    def printDiffs(self, results):
        """Compare the expected results with the actual results and print
        out a set of diffs.

        Arguments:
        - results: the actual results.

        Returns an indication of whether this test was expected to fail.
        """

        knownIssue = False
        print >> myErr, "DIFFERENCES FOUND:"
        if isinstance(self._expectedResults, [].__class__):
            for result in self._expectedResults:
                if result.startswith("KNOWN ISSUE") \
                   or result.startswith("BUG?"):
                    knownIssue = True
        else:
            if self._expectedResults.startswith("KNOWN ISSUE") \
               or self._expectedResults.startswith("BUG?"):
                knownIssue = True

        d = difflib.Differ()
        try:
            # This can stack trace for some odd reason (UTF-8 characters?),
            # so we need to capture it.  Otherwise, it can hang the tests.
            #
            diffs = list(d.compare(self._expectedResults, results))
            print >> myErr, '\n'.join(list(diffs))
        except:
            print >> myErr, "(ERROR COMPUTING DIFFERENCES!!!)"
            for i in range(0, max(len(results), len(self._expectedResults))):
                try:
                    print >> myErr, "  EXPECTED: %s" \
                          % self._expectedResults[i].decode("UTF-8", "replace")
                except:
                    pass
                try:
                    print >> myErr, "  ACTUAL:   %s" \
                          % results[i].decode("UTF-8", "replace")
                except:
                    pass

        return knownIssue

    def printResults(self, results):
        """Print out the expected results and the actual results.

        Arguments:
        - results: the actual results.

        Returns an indication of whether this test was expected to fail.
        """

        knownIssue = False
        print >> myErr, "EXPECTED:"
        if isinstance(self._expectedResults, [].__class__):
            for result in self._expectedResults:
                if result.startswith("KNOWN ISSUE") \
                        or result.startswith("BUG?"):
                    knownIssue = True
                print >> myErr, '     "%s",' % result
        else:
            if self._expectedResults.startswith("KNOWN ISSUE") \
               or self._expectedResults.startswith("BUG?"):
                knownIssue = True
            print >> myErr, '     "%s"' % self._expectedResults
        print >> myErr, "ACTUAL:"
        if isinstance(results, [].__class__):
            for result in results:
                print >> myErr, '     "%s",' % result
        else:
            print >> myErr, '     "%s"' % results
        return knownIssue

    def _stopRecording(self):
        result = dbusOrcaLogging.stopRecording()
        results = self._assertionPredicate(result, self._expectedResults)
        if not results:
            AssertPresentationAction.totalSucceed += 1
            print >> myOut, "Test %d of %d SUCCEEDED: %s" \
                            % (self._num,
                               AssertPresentationAction.totalCount,
                               self._name)
        else:
            AssertPresentationAction.totalFail += 1
            print >> myErr, "Test %d of %d FAILED: %s" \
                            % (self._num,
                               AssertPresentationAction.totalCount,
                               self._name)
            if createDiffs:
                knownIssue = self.printDiffs(results)
            else:
                knownIssue = self.printResults(results)

            if knownIssue:
                AssertPresentationAction.totalKnownIssues += 1
                print >> myErr, '[FAILURE WAS EXPECTED - ' \
                                'LOOK FOR KNOWN ISSUE OR BUG? ' \
                                'IN EXPECTED RESULTS]'
            else:
                print >> myErr, '[FAILURE WAS UNEXPECTED]'

    def __str__(self):
        return 'Assert Presentation Action: %s' % self._name

class AssertionSummaryAction(AtomicAction):
    '''Output the summary of successes and failures of
    AssertPresentationAction assertions.'''

    def __init__(self):
        AtomicAction.__init__(self, 0, self._printSummary)

    def _printSummary(self):
        print >> myOut, \
            "SUMMARY: %d SUCCEEDED and %d FAILED (%d UNEXPECTED) of %d for %s"\
            % (AssertPresentationAction.totalSucceed,
               AssertPresentationAction.totalFail,
               (AssertPresentationAction.totalFail \
               - AssertPresentationAction.totalKnownIssues),
               AssertPresentationAction.totalCount,
               sys.argv[0])

    def __str__(self):
        return 'Start Recording Action'

<|endoftext|>


<commit_before>import sys, __main__, math
"""
$Id$
"""

BANNER         = """
Welcome to Topographica!

Type help() for interactive help, help(commandname) for info on a
specific command, or topo.about() for info on this release,
including licensing information.
"""

global_constants = {'PI':math.pi, 'pi':math.pi, 'Pi':math.pi}

def start(interactive=True):
    """
    Function that will display a banner, import topo into main,
    and change the prompt display.  Since this is part of the topo
    package, the topo.__init__ will already be evaluated when this
    file is imported.
    """
    sys.ps1        = 'Topographica> '

    for (k,v) in global_constants.items():
        exec '%s = %s' % (k,v) in __main__.__dict__

    if interactive:
        print BANNER
        try:
            import readline
        except ImportError:
            print "Module readline not available.\nHistory and completion support disabled."
        else:
        #set up command completion
            import rlcompleter
            readline.parse_and_bind("tab: complete")

<commit_msg>Minor clarification<commit_after>import sys, __main__, math
"""
$Id$
"""

BANNER         = """
Welcome to Topographica!

Type help() for interactive help with python, help(topo) for general
information about about Topographica, help(commandname) for info on a
specific command, or topo.about() for info on this release, including
licensing information.
"""

global_constants = {'PI':math.pi, 'pi':math.pi, 'Pi':math.pi}

def start(interactive=True):
    """
    Function that will display a banner, import topo into main,
    and change the prompt display.  Since this is part of the topo
    package, the topo.__init__ will already be evaluated when this
    file is imported.
    """
    sys.ps1        = 'Topographica> '

    for (k,v) in global_constants.items():
        exec '%s = %s' % (k,v) in __main__.__dict__

    if interactive:
        print BANNER
        try:
            import readline
        except ImportError:
            print "Module readline not available.\nHistory and completion support disabled."
        else:
        #set up command completion
            import rlcompleter
            readline.parse_and_bind("tab: complete")

<|endoftext|>


<commit_before>import logging
import typing
from typing import List, Iterable, Optional
from binascii import hexlify

from twisted.internet import defer

from torba.basescript import BaseInputScript, BaseOutputScript
from torba.baseaccount import BaseAccount
from torba.constants import COIN, NULL_HASH32
from torba.bcd_data_stream import BCDataStream
from torba.hash import sha256, TXRef, TXRefImmutable
from torba.util import ReadOnlyList

if typing.TYPE_CHECKING:
    from torba import baseledger

log = logging.getLogger()

class TXRefMutable(TXRef):

    __slots__ = ('tx',)

    def __init__(self, tx: 'BaseTransaction') -> None:
        super().__init__()
        self.tx = tx

    @property
    def id(self):
        if self._id is None:
            self._id = hexlify(self.hash[::-1]).decode()
        return self._id

    @property
    def hash(self):
        if self._hash is None:
            self._hash = sha256(sha256(self.tx.raw))
        return self._hash

    def reset(self):
        self._id = None
        self._hash = None

class TXORef:

    __slots__ = 'tx_ref', 'position'

    def __init__(self, tx_ref: TXRef, position: int) -> None:
        self.tx_ref = tx_ref
        self.position = position

    @property
    def id(self):
        return '{}:{}'.format(self.tx_ref.id, self.position)

    @property
    def is_null(self):
        return self.tx_ref.is_null

    @property
    def txo(self) -> Optional['BaseOutput']:
        return None

class TXORefResolvable(TXORef):

    __slots__ = ('_txo',)

    def __init__(self, txo: 'BaseOutput') -> None:
        assert txo.tx_ref is not None
        assert txo.position is not None
        super().__init__(txo.tx_ref, txo.position)
        self._txo = txo

    @property
    def txo(self):
        return self._txo

class InputOutput:

    __slots__ = 'tx_ref', 'position'

    def __init__(self, tx_ref: TXRef = None, position: int = None) -> None:
        self.tx_ref = tx_ref
        self.position = position

    @property
    def size(self) -> int:
        """ Size of this input / output in bytes. """
        stream = BCDataStream()
        self.serialize_to(stream)
        return len(stream.get_bytes())

    def get_fee(self, ledger):
        return self.size * ledger.fee_per_byte

    def serialize_to(self, stream, alternate_script=None):
        raise NotImplementedError

class BaseInput(InputOutput):

    script_class = BaseInputScript

    NULL_SIGNATURE = b'\x00'*72
    NULL_PUBLIC_KEY = b'\x00'*33

    __slots__ = 'txo_ref', 'sequence', 'coinbase', 'script'

    def __init__(self, txo_ref: TXORef, script: BaseInputScript, sequence: int = 0xFFFFFFFF,
                 tx_ref: TXRef = None, position: int = None) -> None:
        super().__init__(tx_ref, position)
        self.txo_ref = txo_ref
        self.sequence = sequence
        self.coinbase = script if txo_ref.is_null else None
        self.script = script if not txo_ref.is_null else None

    @property
    def is_coinbase(self):
        return self.coinbase is not None

    @classmethod
    def spend(cls, txo: 'BaseOutput') -> 'BaseInput':
        """ Create an input to spend the output."""
        assert txo.script.is_pay_pubkey_hash, 'Attempting to spend unsupported output.'
        script = cls.script_class.redeem_pubkey_hash(cls.NULL_SIGNATURE, cls.NULL_PUBLIC_KEY)
        return cls(txo.ref, script)

    @property
    def amount(self) -> int:
        """ Amount this input adds to the transaction. """
        if self.txo_ref.txo is None:
            raise ValueError('Cannot resolve output to get amount.')
        return self.txo_ref.txo.amount

    @classmethod
    def deserialize_from(cls, stream):
        tx_ref = TXRefImmutable.from_hash(stream.read(32))
        position = stream.read_uint32()
        script = stream.read_string()
        sequence = stream.read_uint32()
        return cls(
            TXORef(tx_ref, position),
            cls.script_class(script) if not tx_ref.is_null else script,
            sequence
        )

    def serialize_to(self, stream, alternate_script=None):
        stream.write(self.txo_ref.tx_ref.hash)
        stream.write_uint32(self.txo_ref.position)
        if alternate_script is not None:
            stream.write_string(alternate_script)
        else:
            if self.is_coinbase:
                stream.write_string(self.coinbase)
            else:
                stream.write_string(self.script.source)
        stream.write_uint32(self.sequence)

class BaseOutputEffectiveAmountEstimator:

    __slots__ = 'txo', 'txi', 'fee', 'effective_amount'

    def __init__(self, ledger: 'baseledger.BaseLedger', txo: 'BaseOutput') -> None:
        self.txo = txo
        self.txi = ledger.transaction_class.input_class.spend(txo)
        self.fee: int = self.txi.get_fee(ledger)
        self.effective_amount: int = txo.amount - self.fee

    def __lt__(self, other):
        return self.effective_amount < other.effective_amount

class BaseOutput(InputOutput):

    script_class = BaseOutputScript
    estimator_class = BaseOutputEffectiveAmountEstimator

    __slots__ = 'amount', 'script'

    def __init__(self, amount: int, script: BaseOutputScript,
                 tx_ref: TXRef = None, position: int = None) -> None:
        super().__init__(tx_ref, position)
        self.amount = amount
        self.script = script

    @property
    def ref(self):
        return TXORefResolvable(self)

    @property
    def id(self):
        return self.ref.id

    def get_estimator(self, ledger):
        return self.estimator_class(ledger, self)

    @classmethod
    def pay_pubkey_hash(cls, amount, pubkey_hash):
        return cls(amount, cls.script_class.pay_pubkey_hash(pubkey_hash))

    @classmethod
    def deserialize_from(cls, stream):
        return cls(
            amount=stream.read_uint64(),
            script=cls.script_class(stream.read_string())
        )

    def serialize_to(self, stream, alternate_script=None):
        stream.write_uint64(self.amount)
        stream.write_string(self.script.source)

class BaseTransaction:

    input_class = BaseInput
    output_class = BaseOutput

    def __init__(self, raw=None, version=1, locktime=0) -> None:
        self._raw = raw
        self.ref = TXRefMutable(self)
        self.version = version  # type: int
        self.locktime = locktime  # type: int
        self._inputs = []  # type: List[BaseInput]
        self._outputs = []  # type: List[BaseOutput]
        if raw is not None:
            self._deserialize()

    @property
    def id(self):
        return self.ref.id

    @property
    def hash(self):
        return self.ref.hash

    @property
    def raw(self):
        if self._raw is None:
            self._raw = self._serialize()
        return self._raw

    def _reset(self):
        self._raw = None
        self.ref.reset()

    @property
    def inputs(self):  # type: () -> ReadOnlyList[BaseInput]
        return ReadOnlyList(self._inputs)

    @property
    def outputs(self):  # type: () -> ReadOnlyList[BaseOutput]
        return ReadOnlyList(self._outputs)

    def _add(self, new_ios: Iterable[InputOutput], existing_ios: List) -> 'BaseTransaction':
        for txio in new_ios:
            txio.tx_ref = self.ref
            txio.position = len(existing_ios)
            existing_ios.append(txio)
        self._reset()
        return self

    def add_inputs(self, inputs: Iterable[BaseInput]) -> 'BaseTransaction':
        return self._add(inputs, self._inputs)

    def add_outputs(self, outputs: Iterable[BaseOutput]) -> 'BaseTransaction':
        return self._add(outputs, self._outputs)

    @property
    def size(self) -> int:
        """ Size in bytes of the entire transaction. """
        return len(self.raw)

    @property
    def base_size(self) -> int:
        """ Size of transaction without inputs or outputs in bytes. """
        return (
            self.size
            - sum(txi.size for txi in self._inputs)
            - sum(txo.size for txo in self._outputs)
        )

    @property
    def input_sum(self):
        return sum(i.amount for i in self.inputs)

    @property
    def output_sum(self):
        return sum(o.amount for o in self.outputs)

    def get_base_fee(self, ledger):
        """ Fee for base tx excluding inputs and outputs. """
        return self.base_size * ledger.fee_per_byte

    def get_effective_input_sum(self, ledger):
        """ Sum of input values *minus* the cost involved to spend them. """
        return sum(txi.amount - txi.get_fee(ledger) for txi in self._inputs)

    def get_total_output_sum(self, ledger):
        """ Sum of output values *plus* the cost involved to spend them. """
        return sum(txo.amount + txo.get_fee(ledger) for txo in self._outputs)

    def _serialize(self, with_inputs: bool = True) -> bytes:
        stream = BCDataStream()
        stream.write_uint32(self.version)
        if with_inputs:
            stream.write_compact_size(len(self._inputs))
            for txin in self._inputs:
                txin.serialize_to(stream)
        stream.write_compact_size(len(self._outputs))
        for txout in self._outputs:
            txout.serialize_to(stream)
        stream.write_uint32(self.locktime)
        return stream.get_bytes()

    def _serialize_for_signature(self, signing_input: int) -> bytes:
        stream = BCDataStream()
        stream.write_uint32(self.version)
        stream.write_compact_size(len(self._inputs))
        for i, txin in enumerate(self._inputs):
            if signing_input == i:
                assert txin.txo_ref.txo is not None
                txin.serialize_to(stream, txin.txo_ref.txo.script.source)
            else:
                txin.serialize_to(stream, b'')
        stream.write_compact_size(len(self._outputs))
        for txout in self._outputs:
            txout.serialize_to(stream)
        stream.write_uint32(self.locktime)
        stream.write_uint32(self.signature_hash_type(1))  # signature hash type: SIGHASH_ALL
        return stream.get_bytes()

    def _deserialize(self):
        if self._raw is not None:
            stream = BCDataStream(self._raw)
            self.version = stream.read_uint32()
            input_count = stream.read_compact_size()
            self.add_inputs([
                self.input_class.deserialize_from(stream) for _ in range(input_count)
            ])
            output_count = stream.read_compact_size()
            self.add_outputs([
                self.output_class.deserialize_from(stream) for _ in range(output_count)
            ])
            self.locktime = stream.read_uint32()

    @classmethod
    def ensure_all_have_same_ledger(cls, funding_accounts: Iterable[BaseAccount],
                                    change_account: BaseAccount = None) -> 'baseledger.BaseLedger':
        ledger = None
        for account in funding_accounts:
            if ledger is None:
                ledger = account.ledger
            if ledger != account.ledger:
                raise ValueError(
                    'All funding accounts used to create a transaction must be on the same ledger.'
                )
        if change_account is not None and change_account.ledger != ledger:
            raise ValueError('Change account must use same ledger as funding accounts.')
        if ledger is None:
            raise ValueError('No ledger found.')
        return ledger

    @classmethod
    @defer.inlineCallbacks
    def create(cls, inputs: Iterable[BaseInput], outputs: Iterable[BaseOutput],
               funding_accounts: Iterable[BaseAccount], change_account: BaseAccount):
        """ Find optimal set of inputs when only outputs are provided; add change
            outputs if only inputs are provided or if inputs are greater than outputs. """

        tx = cls() \
            .add_inputs(inputs) \
            .add_outputs(outputs)

        ledger = cls.ensure_all_have_same_ledger(funding_accounts, change_account)

        # value of the outputs plus associated fees
        cost = (
            tx.get_base_fee(ledger) +
            tx.get_total_output_sum(ledger)
        )
        # value of the inputs less the cost to spend those inputs
        payment = tx.get_effective_input_sum(ledger)

        try:

            if payment < cost:
                deficit = cost - payment
                spendables = yield ledger.get_spendable_utxos(deficit, funding_accounts)
                if not spendables:
                    raise ValueError('Not enough funds to cover this transaction.')
                payment += sum(s.effective_amount for s in spendables)
                tx.add_inputs(s.txi for s in spendables)

            if payment > cost:
                cost_of_change = (
                    tx.get_base_fee(ledger) +
                    cls.output_class.pay_pubkey_hash(COIN, NULL_HASH32).get_fee(ledger)
                )
                change = payment - cost
                if change > cost_of_change:
                    change_address = yield change_account.change.get_or_create_usable_address()
                    change_hash160 = change_account.ledger.address_to_hash160(change_address)
                    change_amount = change - cost_of_change
                    tx.add_outputs([cls.output_class.pay_pubkey_hash(change_amount, change_hash160)])

            yield tx.sign(funding_accounts)

        except Exception as e:
            log.exception('Failed to synchronize transaction:')
            yield ledger.release_outputs(tx.outputs)
            raise e

        defer.returnValue(tx)

    @staticmethod
    def signature_hash_type(hash_type):
        return hash_type

    @defer.inlineCallbacks
    def sign(self, funding_accounts: Iterable[BaseAccount]) -> defer.Deferred:
        ledger = self.ensure_all_have_same_ledger(funding_accounts)
        for i, txi in enumerate(self._inputs):
            assert txi.script is not None
            assert txi.txo_ref.txo is not None
            txo_script = txi.txo_ref.txo.script
            if txo_script.is_pay_pubkey_hash:
                address = ledger.hash160_to_address(txo_script.values['pubkey_hash'])
                private_key = yield ledger.get_private_key_for_address(address)
                tx = self._serialize_for_signature(i)
                txi.script.values['signature'] = \
                    private_key.sign(tx) + bytes((self.signature_hash_type(1),))
                txi.script.values['pubkey'] = private_key.public_key.pubkey_bytes
                txi.script.generate()
            else:
                raise NotImplementedError("Don't know how to spend this output.")
        self._reset()

    @defer.inlineCallbacks
    def get_my_addresses(self, ledger):
        addresses = set()
        for txo in self.outputs:
            address = ledger.hash160_to_address(txo.script.values['pubkey_hash'])
            record = yield ledger.db.get_address(address)
            if record is not None:
                addresses.add(address)
        defer.returnValue(list(addresses))
<commit_msg>add tx.fee property back<commit_after>import logging
import typing
from typing import List, Iterable, Optional
from binascii import hexlify

from twisted.internet import defer

from torba.basescript import BaseInputScript, BaseOutputScript
from torba.baseaccount import BaseAccount
from torba.constants import COIN, NULL_HASH32
from torba.bcd_data_stream import BCDataStream
from torba.hash import sha256, TXRef, TXRefImmutable
from torba.util import ReadOnlyList

if typing.TYPE_CHECKING:
    from torba import baseledger

log = logging.getLogger()

class TXRefMutable(TXRef):

    __slots__ = ('tx',)

    def __init__(self, tx: 'BaseTransaction') -> None:
        super().__init__()
        self.tx = tx

    @property
    def id(self):
        if self._id is None:
            self._id = hexlify(self.hash[::-1]).decode()
        return self._id

    @property
    def hash(self):
        if self._hash is None:
            self._hash = sha256(sha256(self.tx.raw))
        return self._hash

    def reset(self):
        self._id = None
        self._hash = None

class TXORef:

    __slots__ = 'tx_ref', 'position'

    def __init__(self, tx_ref: TXRef, position: int) -> None:
        self.tx_ref = tx_ref
        self.position = position

    @property
    def id(self):
        return '{}:{}'.format(self.tx_ref.id, self.position)

    @property
    def is_null(self):
        return self.tx_ref.is_null

    @property
    def txo(self) -> Optional['BaseOutput']:
        return None

class TXORefResolvable(TXORef):

    __slots__ = ('_txo',)

    def __init__(self, txo: 'BaseOutput') -> None:
        assert txo.tx_ref is not None
        assert txo.position is not None
        super().__init__(txo.tx_ref, txo.position)
        self._txo = txo

    @property
    def txo(self):
        return self._txo

class InputOutput:

    __slots__ = 'tx_ref', 'position'

    def __init__(self, tx_ref: TXRef = None, position: int = None) -> None:
        self.tx_ref = tx_ref
        self.position = position

    @property
    def size(self) -> int:
        """ Size of this input / output in bytes. """
        stream = BCDataStream()
        self.serialize_to(stream)
        return len(stream.get_bytes())

    def get_fee(self, ledger):
        return self.size * ledger.fee_per_byte

    def serialize_to(self, stream, alternate_script=None):
        raise NotImplementedError

class BaseInput(InputOutput):

    script_class = BaseInputScript

    NULL_SIGNATURE = b'\x00'*72
    NULL_PUBLIC_KEY = b'\x00'*33

    __slots__ = 'txo_ref', 'sequence', 'coinbase', 'script'

    def __init__(self, txo_ref: TXORef, script: BaseInputScript, sequence: int = 0xFFFFFFFF,
                 tx_ref: TXRef = None, position: int = None) -> None:
        super().__init__(tx_ref, position)
        self.txo_ref = txo_ref
        self.sequence = sequence
        self.coinbase = script if txo_ref.is_null else None
        self.script = script if not txo_ref.is_null else None

    @property
    def is_coinbase(self):
        return self.coinbase is not None

    @classmethod
    def spend(cls, txo: 'BaseOutput') -> 'BaseInput':
        """ Create an input to spend the output."""
        assert txo.script.is_pay_pubkey_hash, 'Attempting to spend unsupported output.'
        script = cls.script_class.redeem_pubkey_hash(cls.NULL_SIGNATURE, cls.NULL_PUBLIC_KEY)
        return cls(txo.ref, script)

    @property
    def amount(self) -> int:
        """ Amount this input adds to the transaction. """
        if self.txo_ref.txo is None:
            raise ValueError('Cannot resolve output to get amount.')
        return self.txo_ref.txo.amount

    @classmethod
    def deserialize_from(cls, stream):
        tx_ref = TXRefImmutable.from_hash(stream.read(32))
        position = stream.read_uint32()
        script = stream.read_string()
        sequence = stream.read_uint32()
        return cls(
            TXORef(tx_ref, position),
            cls.script_class(script) if not tx_ref.is_null else script,
            sequence
        )

    def serialize_to(self, stream, alternate_script=None):
        stream.write(self.txo_ref.tx_ref.hash)
        stream.write_uint32(self.txo_ref.position)
        if alternate_script is not None:
            stream.write_string(alternate_script)
        else:
            if self.is_coinbase:
                stream.write_string(self.coinbase)
            else:
                stream.write_string(self.script.source)
        stream.write_uint32(self.sequence)

class BaseOutputEffectiveAmountEstimator:

    __slots__ = 'txo', 'txi', 'fee', 'effective_amount'

    def __init__(self, ledger: 'baseledger.BaseLedger', txo: 'BaseOutput') -> None:
        self.txo = txo
        self.txi = ledger.transaction_class.input_class.spend(txo)
        self.fee: int = self.txi.get_fee(ledger)
        self.effective_amount: int = txo.amount - self.fee

    def __lt__(self, other):
        return self.effective_amount < other.effective_amount

class BaseOutput(InputOutput):

    script_class = BaseOutputScript
    estimator_class = BaseOutputEffectiveAmountEstimator

    __slots__ = 'amount', 'script'

    def __init__(self, amount: int, script: BaseOutputScript,
                 tx_ref: TXRef = None, position: int = None) -> None:
        super().__init__(tx_ref, position)
        self.amount = amount
        self.script = script

    @property
    def ref(self):
        return TXORefResolvable(self)

    @property
    def id(self):
        return self.ref.id

    def get_estimator(self, ledger):
        return self.estimator_class(ledger, self)

    @classmethod
    def pay_pubkey_hash(cls, amount, pubkey_hash):
        return cls(amount, cls.script_class.pay_pubkey_hash(pubkey_hash))

    @classmethod
    def deserialize_from(cls, stream):
        return cls(
            amount=stream.read_uint64(),
            script=cls.script_class(stream.read_string())
        )

    def serialize_to(self, stream, alternate_script=None):
        stream.write_uint64(self.amount)
        stream.write_string(self.script.source)

class BaseTransaction:

    input_class = BaseInput
    output_class = BaseOutput

    def __init__(self, raw=None, version=1, locktime=0) -> None:
        self._raw = raw
        self.ref = TXRefMutable(self)
        self.version = version  # type: int
        self.locktime = locktime  # type: int
        self._inputs = []  # type: List[BaseInput]
        self._outputs = []  # type: List[BaseOutput]
        if raw is not None:
            self._deserialize()

    @property
    def id(self):
        return self.ref.id

    @property
    def hash(self):
        return self.ref.hash

    @property
    def raw(self):
        if self._raw is None:
            self._raw = self._serialize()
        return self._raw

    def _reset(self):
        self._raw = None
        self.ref.reset()

    @property
    def inputs(self):  # type: () -> ReadOnlyList[BaseInput]
        return ReadOnlyList(self._inputs)

    @property
    def outputs(self):  # type: () -> ReadOnlyList[BaseOutput]
        return ReadOnlyList(self._outputs)

    def _add(self, new_ios: Iterable[InputOutput], existing_ios: List) -> 'BaseTransaction':
        for txio in new_ios:
            txio.tx_ref = self.ref
            txio.position = len(existing_ios)
            existing_ios.append(txio)
        self._reset()
        return self

    def add_inputs(self, inputs: Iterable[BaseInput]) -> 'BaseTransaction':
        return self._add(inputs, self._inputs)

    def add_outputs(self, outputs: Iterable[BaseOutput]) -> 'BaseTransaction':
        return self._add(outputs, self._outputs)

    @property
    def size(self) -> int:
        """ Size in bytes of the entire transaction. """
        return len(self.raw)

    @property
    def base_size(self) -> int:
        """ Size of transaction without inputs or outputs in bytes. """
        return (
            self.size
            - sum(txi.size for txi in self._inputs)
            - sum(txo.size for txo in self._outputs)
        )

    @property
    def input_sum(self):
        return sum(i.amount for i in self.inputs)

    @property
    def output_sum(self):
        return sum(o.amount for o in self.outputs)

    @property
    def fee(self):
        return self.input_sum - self.output_sum

    def get_base_fee(self, ledger):
        """ Fee for base tx excluding inputs and outputs. """
        return self.base_size * ledger.fee_per_byte

    def get_effective_input_sum(self, ledger):
        """ Sum of input values *minus* the cost involved to spend them. """
        return sum(txi.amount - txi.get_fee(ledger) for txi in self._inputs)

    def get_total_output_sum(self, ledger):
        """ Sum of output values *plus* the cost involved to spend them. """
        return sum(txo.amount + txo.get_fee(ledger) for txo in self._outputs)

    def _serialize(self, with_inputs: bool = True) -> bytes:
        stream = BCDataStream()
        stream.write_uint32(self.version)
        if with_inputs:
            stream.write_compact_size(len(self._inputs))
            for txin in self._inputs:
                txin.serialize_to(stream)
        stream.write_compact_size(len(self._outputs))
        for txout in self._outputs:
            txout.serialize_to(stream)
        stream.write_uint32(self.locktime)
        return stream.get_bytes()

    def _serialize_for_signature(self, signing_input: int) -> bytes:
        stream = BCDataStream()
        stream.write_uint32(self.version)
        stream.write_compact_size(len(self._inputs))
        for i, txin in enumerate(self._inputs):
            if signing_input == i:
                assert txin.txo_ref.txo is not None
                txin.serialize_to(stream, txin.txo_ref.txo.script.source)
            else:
                txin.serialize_to(stream, b'')
        stream.write_compact_size(len(self._outputs))
        for txout in self._outputs:
            txout.serialize_to(stream)
        stream.write_uint32(self.locktime)
        stream.write_uint32(self.signature_hash_type(1))  # signature hash type: SIGHASH_ALL
        return stream.get_bytes()

    def _deserialize(self):
        if self._raw is not None:
            stream = BCDataStream(self._raw)
            self.version = stream.read_uint32()
            input_count = stream.read_compact_size()
            self.add_inputs([
                self.input_class.deserialize_from(stream) for _ in range(input_count)
            ])
            output_count = stream.read_compact_size()
            self.add_outputs([
                self.output_class.deserialize_from(stream) for _ in range(output_count)
            ])
            self.locktime = stream.read_uint32()

    @classmethod
    def ensure_all_have_same_ledger(cls, funding_accounts: Iterable[BaseAccount],
                                    change_account: BaseAccount = None) -> 'baseledger.BaseLedger':
        ledger = None
        for account in funding_accounts:
            if ledger is None:
                ledger = account.ledger
            if ledger != account.ledger:
                raise ValueError(
                    'All funding accounts used to create a transaction must be on the same ledger.'
                )
        if change_account is not None and change_account.ledger != ledger:
            raise ValueError('Change account must use same ledger as funding accounts.')
        if ledger is None:
            raise ValueError('No ledger found.')
        return ledger

    @classmethod
    @defer.inlineCallbacks
    def create(cls, inputs: Iterable[BaseInput], outputs: Iterable[BaseOutput],
               funding_accounts: Iterable[BaseAccount], change_account: BaseAccount):
        """ Find optimal set of inputs when only outputs are provided; add change
            outputs if only inputs are provided or if inputs are greater than outputs. """

        tx = cls() \
            .add_inputs(inputs) \
            .add_outputs(outputs)

        ledger = cls.ensure_all_have_same_ledger(funding_accounts, change_account)

        # value of the outputs plus associated fees
        cost = (
            tx.get_base_fee(ledger) +
            tx.get_total_output_sum(ledger)
        )
        # value of the inputs less the cost to spend those inputs
        payment = tx.get_effective_input_sum(ledger)

        try:

            if payment < cost:
                deficit = cost - payment
                spendables = yield ledger.get_spendable_utxos(deficit, funding_accounts)
                if not spendables:
                    raise ValueError('Not enough funds to cover this transaction.')
                payment += sum(s.effective_amount for s in spendables)
                tx.add_inputs(s.txi for s in spendables)

            if payment > cost:
                cost_of_change = (
                    tx.get_base_fee(ledger) +
                    cls.output_class.pay_pubkey_hash(COIN, NULL_HASH32).get_fee(ledger)
                )
                change = payment - cost
                if change > cost_of_change:
                    change_address = yield change_account.change.get_or_create_usable_address()
                    change_hash160 = change_account.ledger.address_to_hash160(change_address)
                    change_amount = change - cost_of_change
                    tx.add_outputs([cls.output_class.pay_pubkey_hash(change_amount, change_hash160)])

            yield tx.sign(funding_accounts)

        except Exception as e:
            log.exception('Failed to synchronize transaction:')
            yield ledger.release_outputs(tx.outputs)
            raise e

        defer.returnValue(tx)

    @staticmethod
    def signature_hash_type(hash_type):
        return hash_type

    @defer.inlineCallbacks
    def sign(self, funding_accounts: Iterable[BaseAccount]) -> defer.Deferred:
        ledger = self.ensure_all_have_same_ledger(funding_accounts)
        for i, txi in enumerate(self._inputs):
            assert txi.script is not None
            assert txi.txo_ref.txo is not None
            txo_script = txi.txo_ref.txo.script
            if txo_script.is_pay_pubkey_hash:
                address = ledger.hash160_to_address(txo_script.values['pubkey_hash'])
                private_key = yield ledger.get_private_key_for_address(address)
                tx = self._serialize_for_signature(i)
                txi.script.values['signature'] = \
                    private_key.sign(tx) + bytes((self.signature_hash_type(1),))
                txi.script.values['pubkey'] = private_key.public_key.pubkey_bytes
                txi.script.generate()
            else:
                raise NotImplementedError("Don't know how to spend this output.")
        self._reset()

    @defer.inlineCallbacks
    def get_my_addresses(self, ledger):
        addresses = set()
        for txo in self.outputs:
            address = ledger.hash160_to_address(txo.script.values['pubkey_hash'])
            record = yield ledger.db.get_address(address)
            if record is not None:
                addresses.add(address)
        defer.returnValue(list(addresses))
<|endoftext|>


<commit_before>#!/usr/bin/env python3

# Author: Volodymyr Shymanskyy
# Usage:
#   ./run-wasi-test.py
#   ./run-wasi-test.py --exec ../custom_build/wasm3 --timeout 120
#   ./run-wasi-test.py --exec "wasmer run"
#

# TODO
# - Implement wasi args passing => reduce test time & cpu usage

import argparse
import sys
import subprocess
import hashlib
import fnmatch

from pprint import pprint

#
# Args handling
#

parser = argparse.ArgumentParser()
parser.add_argument("--exec", metavar="<interpreter>", default="../build/wasm3")
parser.add_argument("--timeout", type=int,             default=30)

args = parser.parse_args()

#
# Helpers
#

class ansi:
    ENDC = '\033[0m'
    HEADER = '\033[94m'
    OKGREEN = '\033[92m'
    WARNING = '\033[93m'
    FAIL = '\033[91m'
    BOLD = '\033[1m'
    UNDERLINE = '\033[4m'

class dotdict(dict):
    def __init__(self, *args, **kwargs):
        super(dotdict, self).__init__(*args, **kwargs)
        for arg in args:
            if isinstance(arg, dict):
                for k, v in arg.items():
                    self[k] = v
        if kwargs:
            for k, v in kwargs.items():
                self[k] = v

    __getattr__ = dict.get
    __setattr__ = dict.__setitem__
    __delattr__ = dict.__delitem__

#
# Actual test
#

stats = dotdict(total_run=0, failed=0, crashed=0, timeout=0)

commands = [
  {
    "name":           "Simple WASI test",
    "wasm":           "./wasi/test.wasm",
    "expect_pattern": "Hello world*Constructor OK*fib(10) = 55*[* ms]*"
  }, {
    "name":           "CoreMark",
    "wasm":           "./benchmark/coremark/coremark-wasi.wasm",
    "expect_pattern": "*Correct operation validated.*CoreMark 1.0 : * / Clang* / HEAP*"
  }, {
    "name":           "C-Ray",
    "stdin":          open("./benchmark/c-ray/scene", "rb"),
    "wasm":           "./benchmark/c-ray/c-ray.wasm",
    "expect_sha1":    "af7baced15a066eb83150aceaea0add05e0c7edf"
  }, {
    "skip":           True,  # TODO
    "name":           "smallpt (explicit light sampling)",
    "wasm":           "./benchmark/smallpt/smallpt-ex.wasm",
    "expect_sha1":    "ad8e505dc15564a86cdbdbffc48682b2a923f37c"
  }, {
    "name":           "STREAM",
    "wasm":           "./benchmark/stream/stream.wasm",
    "expect_pattern": "----*Solution Validates:*on all three arrays*----*"
  }, {
    "skip":           True,  # TODO
    "name":           "Self-hosting",
    "wasm":           "./self-hosting/wasm3-fib.wasm",
    "expect_pattern": "*wasm3 on WASM*Elapsed: * ms*"
  }, {
    "skip":           True,  # TODO
    "name":           "Brotli",
    "stdin":          open("./benchmark/brotli/alice29.txt", "rb"),
    "wasm":           "./benchmark/brotli/brotli.wasm",
    "args":           ["-c"],
    "expect_sha1":    "8eacda4b80fc816cad185330caa7556e19643dff"
  }
]

def fail(msg):
    print(f"{ansi.FAIL}FAIL:{ansi.ENDC} {msg}")
    stats.failed += 1

args_sep = None
if "wasmer" in args.exec or "wasmtime" in args.exec:
    args_sep = "--"

for cmd in commands:
    if "skip" in cmd:
        continue

    command = args.exec.split(' ')
    command.append(cmd['wasm'])
    if "args" in cmd:
        if args_sep:
            command.append(args_sep)
        command.extend(cmd['args'])

    command = list(map(str, command))
    print(f"Running {cmd['name']}")
    stats.total_run += 1
    try:
        if "stdin" in cmd:
            output = subprocess.check_output(command, timeout=args.timeout, stdin=cmd['stdin'])
        else:
            output = subprocess.check_output(command, timeout=args.timeout)
    except subprocess.TimeoutExpired:
        stats.timeout += 1
        fail("Timeout")
        continue
    except subprocess.CalledProcessError:
        stats.crashed += 1
        fail("Crashed")
        continue

    if "expect_sha1" in cmd:
        actual = hashlib.sha1(output).hexdigest()
        if actual != cmd['expect_sha1']:
            fail(f"Actual sha1: {actual}")

    if "expect_pattern" in cmd:
        if not fnmatch.fnmatch(output.decode("utf-8"), cmd['expect_pattern']):
            fail(f"Output does not match pattern")

pprint(stats)

if stats.failed:
    print(f"{ansi.FAIL}=======================")
    print(f" FAILED: {stats.failed}/{stats.total_run}")
    print(f"======================={ansi.ENDC}")
    sys.exit(1)

else:
    print(f"{ansi.OKGREEN}=======================")
    print(f" All {stats.total_run} tests OK")
    print(f"======================={ansi.ENDC}")
<commit_msg>Add TODO<commit_after>#!/usr/bin/env python3

# Author: Volodymyr Shymanskyy
# Usage:
#   ./run-wasi-test.py
#   ./run-wasi-test.py --exec ../custom_build/wasm3 --timeout 120
#   ./run-wasi-test.py --exec "wasmer run"
#

# TODO
# - Implement wasi args passing => reduce test time & cpu usage
# - Fix "Empty stack" output, so it does not affect the SHA1 checksum

import argparse
import sys
import subprocess
import hashlib
import fnmatch

from pprint import pprint

#
# Args handling
#

parser = argparse.ArgumentParser()
parser.add_argument("--exec", metavar="<interpreter>", default="../build/wasm3")
parser.add_argument("--timeout", type=int,             default=30)

args = parser.parse_args()

#
# Helpers
#

class ansi:
    ENDC = '\033[0m'
    HEADER = '\033[94m'
    OKGREEN = '\033[92m'
    WARNING = '\033[93m'
    FAIL = '\033[91m'
    BOLD = '\033[1m'
    UNDERLINE = '\033[4m'

class dotdict(dict):
    def __init__(self, *args, **kwargs):
        super(dotdict, self).__init__(*args, **kwargs)
        for arg in args:
            if isinstance(arg, dict):
                for k, v in arg.items():
                    self[k] = v
        if kwargs:
            for k, v in kwargs.items():
                self[k] = v

    __getattr__ = dict.get
    __setattr__ = dict.__setitem__
    __delattr__ = dict.__delitem__

#
# Actual test
#

stats = dotdict(total_run=0, failed=0, crashed=0, timeout=0)

commands = [
  {
    "name":           "Simple WASI test",
    "wasm":           "./wasi/test.wasm",
    "expect_pattern": "Hello world*Constructor OK*fib(10) = 55*[* ms]*"
  }, {
    "name":           "CoreMark",
    "wasm":           "./benchmark/coremark/coremark-wasi.wasm",
    "expect_pattern": "*Correct operation validated.*CoreMark 1.0 : * / Clang* / HEAP*"
  }, {
    "name":           "C-Ray",
    "stdin":          open("./benchmark/c-ray/scene", "rb"),
    "wasm":           "./benchmark/c-ray/c-ray.wasm",
    "expect_sha1":    "0260a0ee271abd447ffa505aecbf745cb7399e2c" # TODO: should be af7baced15a066eb83150aceaea0add05e0c7edf
  }, {
    "skip":           True,  # TODO
    "name":           "smallpt (explicit light sampling)",
    "wasm":           "./benchmark/smallpt/smallpt-ex.wasm",
    "expect_sha1":    "ad8e505dc15564a86cdbdbffc48682b2a923f37c"
  }, {
    "name":           "STREAM",
    "wasm":           "./benchmark/stream/stream.wasm",
    "expect_pattern": "----*Solution Validates:*on all three arrays*----*"
  }, {
    "skip":           True,  # TODO
    "name":           "Self-hosting",
    "wasm":           "./self-hosting/wasm3-fib.wasm",
    "expect_pattern": "*wasm3 on WASM*Elapsed: * ms*"
  }, {
    "skip":           True,  # TODO
    "name":           "Brotli",
    "stdin":          open("./benchmark/brotli/alice29.txt", "rb"),
    "wasm":           "./benchmark/brotli/brotli.wasm",
    "args":           ["-c"],
    "expect_sha1":    "8eacda4b80fc816cad185330caa7556e19643dff"
  }
]

def fail(msg):
    print(f"{ansi.FAIL}FAIL:{ansi.ENDC} {msg}")
    stats.failed += 1

args_sep = None
if "wasmer" in args.exec or "wasmtime" in args.exec:
    args_sep = "--"

for cmd in commands:
    if "skip" in cmd:
        continue

    command = args.exec.split(' ')
    command.append(cmd['wasm'])
    if "args" in cmd:
        if args_sep:
            command.append(args_sep)
        command.extend(cmd['args'])

    command = list(map(str, command))
    print(f"Running {cmd['name']}")
    stats.total_run += 1
    try:
        if "stdin" in cmd:
            output = subprocess.check_output(command, timeout=args.timeout, stdin=cmd['stdin'])
        else:
            output = subprocess.check_output(command, timeout=args.timeout)
    except subprocess.TimeoutExpired:
        stats.timeout += 1
        fail("Timeout")
        continue
    except subprocess.CalledProcessError:
        stats.crashed += 1
        fail("Crashed")
        continue

    if "expect_sha1" in cmd:
        actual = hashlib.sha1(output).hexdigest()
        if actual != cmd['expect_sha1']:
            fail(f"Actual sha1: {actual}")

    if "expect_pattern" in cmd:
        if not fnmatch.fnmatch(output.decode("utf-8"), cmd['expect_pattern']):
            fail(f"Output does not match pattern")

pprint(stats)

if stats.failed:
    print(f"{ansi.FAIL}=======================")
    print(f" FAILED: {stats.failed}/{stats.total_run}")
    print(f"======================={ansi.ENDC}")
    sys.exit(1)

else:
    print(f"{ansi.OKGREEN}=======================")
    print(f" All {stats.total_run} tests OK")
    print(f"======================={ansi.ENDC}")
<|endoftext|>


<commit_before># -*- coding: iso8859-1 -*-
#
# Copyright (C) 2004 Edgewall Software
# Copyright (C) 2004 Christopher Lenz <cmlenz@gmx.de>
#
# Trac is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version.
#
# Trac is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
# Author: Christopher Lenz <cmlenz@gmx.de>

import re, threading
import auth, core, Environment, Href, Session, Wiki
from util import TracError
from mod_python import apache, util

content_type_re = re.compile(r'^Content-Type$', re.IGNORECASE)

class ModPythonRequest(core.Request):

    def __init__(self, req):
        self.req = req

    def init_request(self):
        core.Request.init_request(self)

        # TODO This will need proxy host name support (see #437 and [581])
        host = self.req.hostname
        port = self.req.connection.local_addr[1]
        path = re.sub('%s$' % re.escape(self.req.path_info), '', self.req.uri)
        if port == 80:
            self.base_url = 'http://%s%s' % (host, path)
        elif port == 443:
            self.base_url = 'https://%s%s' % (host, path)
        else:
            self.base_url = 'http://%s:%d%s' % (host, port, path)

        self.cgi_location = path
        self.remote_addr = self.req.connection.remote_ip
        self.remote_user = self.req.user
        self.command = self.req.method

        if self.req.headers_in.has_key('Cookie'):
            self.incookie.load(self.req.headers_in['Cookie'])

        self.hdf.setValue('HTTP.Host', self.req.hostname)

    def read(self, len):
        return self.req.read(len)

    def write(self, data):
        self.req.write(data)

    def get_header(self, name):
        return self.req.headers_in.get(name)

    def send_response(self, code):
        self.req.status = code

    def send_header(self, name, value):
        if content_type_re.match(name):
            self.req.content_type = value
        else:
            self.req.headers_out.add(name, str(value))

    def end_headers(self):
        pass

class TracFieldStorage(util.FieldStorage):
    """
    FieldStorage class with an added get function.
    """
    def get(self, key, default=''):
        return util.FieldStorage.get(self, key, default)

def init_env(req):

    options = req.req.get_options()
    if not options.has_key('TracEnv'):
        raise EnvironmentError, \
            'Missing PythonOption "TracEnv". Trac requires this option '\
            'to point to a valid Trac Environment.'
    env_path = options['TracEnv']

    env = Environment.Environment(env_path)
    version = env.get_version()
    if version < Environment.db_version:
        raise TracError('The Trac environment needs to be upgraded. '
                        'Run "trac-admin %s upgrade"' % env_path)
    elif version > Environment.db_version:
        raise TracError('Unknown Trac Environment version (%d).' % version)

    env.href = Href.Href(req.cgi_location)
    env.abs_href = Href.Href(req.base_url)

    # Let the wiki module build a dictionary of all page names
    database = env.get_db_cnx()
    Wiki.populate_page_dict(database, env)

    return env

projects = {}
projects_lock = threading.Lock()

def handler(req):
    global projects, projects_lock

    mpr = ModPythonRequest(req)
    mpr.init_request()

    projects_lock.acquire()
    env = projects.get(req.filename, None)
    if not env:
        env = init_env(mpr)
        projects[req.filename] = env
    projects_lock.release()

    args = TracFieldStorage(req)
    core.parse_path_info(args, req.path_info)

    referrer = req.headers_in.get('Referer', None)

    req.content_type = 'text/html'

    try:
        core.dispatch_request(req.path_info, args, mpr, env)
    except Exception, e:
        core.send_pretty_error(e, env, mpr)

    return apache.OK
<commit_msg>Remove unused code<commit_after># -*- coding: iso8859-1 -*-
#
# Copyright (C) 2004 Edgewall Software
# Copyright (C) 2004 Christopher Lenz <cmlenz@gmx.de>
#
# Trac is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version.
#
# Trac is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
# Author: Christopher Lenz <cmlenz@gmx.de>

import re, threading
import auth, core, Environment, Href, Session, Wiki
from util import TracError
from mod_python import apache, util

content_type_re = re.compile(r'^Content-Type$', re.IGNORECASE)

class ModPythonRequest(core.Request):

    def __init__(self, req):
        self.req = req

    def init_request(self):
        core.Request.init_request(self)

        # TODO This will need proxy host name support (see #437 and [581])
        host = self.req.hostname
        port = self.req.connection.local_addr[1]
        path = re.sub('%s$' % re.escape(self.req.path_info), '', self.req.uri)
        if port == 80:
            self.base_url = 'http://%s%s' % (host, path)
        elif port == 443:
            self.base_url = 'https://%s%s' % (host, path)
        else:
            self.base_url = 'http://%s:%d%s' % (host, port, path)

        self.cgi_location = path
        self.remote_addr = self.req.connection.remote_ip
        self.remote_user = self.req.user
        self.command = self.req.method

        if self.req.headers_in.has_key('Cookie'):
            self.incookie.load(self.req.headers_in['Cookie'])

        self.hdf.setValue('HTTP.Host', self.req.hostname)

    def read(self, len):
        return self.req.read(len)

    def write(self, data):
        self.req.write(data)

    def get_header(self, name):
        return self.req.headers_in.get(name)

    def send_response(self, code):
        self.req.status = code

    def send_header(self, name, value):
        if content_type_re.match(name):
            self.req.content_type = value
        else:
            self.req.headers_out.add(name, str(value))

    def end_headers(self):
        pass

class TracFieldStorage(util.FieldStorage):
    """
    FieldStorage class with an added get function.
    """
    def get(self, key, default=''):
        return util.FieldStorage.get(self, key, default)

def init_env(req):

    options = req.req.get_options()
    if not options.has_key('TracEnv'):
        raise EnvironmentError, \
            'Missing PythonOption "TracEnv". Trac requires this option '\
            'to point to a valid Trac Environment.'
    env_path = options['TracEnv']

    env = Environment.Environment(env_path)
    version = env.get_version()
    if version < Environment.db_version:
        raise TracError('The Trac environment needs to be upgraded. '
                        'Run "trac-admin %s upgrade"' % env_path)
    elif version > Environment.db_version:
        raise TracError('Unknown Trac Environment version (%d).' % version)

    env.href = Href.Href(req.cgi_location)
    env.abs_href = Href.Href(req.base_url)

    # Let the wiki module build a dictionary of all page names
    database = env.get_db_cnx()
    Wiki.populate_page_dict(database, env)

    return env

projects = {}
projects_lock = threading.Lock()

def handler(req):
    global projects, projects_lock

    mpr = ModPythonRequest(req)
    mpr.init_request()

    projects_lock.acquire()
    env = projects.get(req.filename, None)
    if not env:
        env = init_env(mpr)
        projects[req.filename] = env
    projects_lock.release()

    args = TracFieldStorage(req)
    core.parse_path_info(args, req.path_info)

    req.content_type = 'text/html'
    try:
        core.dispatch_request(req.path_info, args, mpr, env)
    except Exception, e:
        core.send_pretty_error(e, env, mpr)

    return apache.OK
<|endoftext|>


<commit_before># -*- coding: iso8859-1 -*-
#
# Copyright (C) 2004 Edgewall Software
# Copyright (C) 2004 Christopher Lenz <cmlenz@gmx.de>
#
# Trac is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version.
#
# Trac is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
# Author: Christopher Lenz <cmlenz@gmx.de>

import os
import re, threading
import auth, core, Environment, Href, Session, Wiki
from util import TracError, href_join
from mod_python import apache, util

class ModPythonRequest(core.Request):

    def __init__(self, req):
        self.req = req

    def init_request(self):
        core.Request.init_request(self)
        options = self.req.get_options()

        if options.has_key('TracEnvParentDir') and self.req.path_info:
            # We have to remove one path element from path_info when we're
            # using TracEnvParentDir
            self.path_info = re.sub('/[^/]+', '', self.req.path_info, 1)
        else:
            self.path_info = self.req.path_info

        if len(self.path_info):
            self.cgi_location = self.req.uri[:-len(self.path_info)]
        else:
            self.cgi_location = self.req.uri

        if len(self.req.path_info):
            self.idx_location = self.req.uri[:-len(self.req.path_info)]
        else:
            self.idx_location = self.req.uri

        # TODO This will need proxy host name support (see #437 and [581])
        host = self.req.hostname
        port = self.req.connection.local_addr[1]
        if port == 80:
            self.base_url = 'http://%s%s' % (host, self.cgi_location)
        elif port == 443:
            self.base_url = 'https://%s%s' % (host, self.cgi_location)
        else:
            self.base_url = 'http://%s:%d%s' % (host, port, self.cgi_location)

        self.remote_addr = self.req.connection.remote_ip
        self.remote_user = self.req.user
        self.command = self.req.method

        if self.req.headers_in.has_key('Cookie'):
            self.incookie.load(self.req.headers_in['Cookie'])

        self.hdf.setValue('HTTP.Host', self.req.hostname)

    def read(self, len):
        return self.req.read(len)

    def write(self, data):
        self.req.write(data)

    def get_header(self, name):
        return self.req.headers_in.get(name)

    def send_response(self, code):
        self.req.status = code

    def send_header(self, name, value):
        if name.lower() == 'content-type':
            self.req.content_type = value
        else:
            self.req.headers_out.add(name, str(value))

    def end_headers(self):
        pass

class TracFieldStorage(util.FieldStorage):
    """
    FieldStorage class with an added get function.
    """
    def get(self, key, default=''):
        return util.FieldStorage.get(self, key, default)

def send_project_index(req, mpr, dir):
    req.content_type = 'text/html'
    req.write('<title>Available Projects</title>')
    req.write('
Available Projects

')
    for project in os.listdir(dir):
        req.write('
%s
' % (href_join(mpr.idx_location,
                                                              project),
                                                    project))
    req.write('
')

def open_environment(env_path, mpr):
    env = Environment.Environment(env_path)
    version = env.get_version()
    if version < Environment.db_version:
        raise TracError('The Trac environment needs to be upgraded. '
                        'Run "trac-admin %s upgrade"' % env_path)
    elif version > Environment.db_version:
        raise TracError('Unknown Trac Environment version (%d).' % version)

    env.href = Href.Href(mpr.cgi_location)
    env.abs_href = Href.Href(mpr.base_url)
    # Let the wiki module build a dictionary of all page names
    database = env.get_db_cnx()
    Wiki.populate_page_dict(database, env)
    return env

env_cache = {}
env_cache_lock = threading.Lock()

def get_environment(req, mpr):
    global env_cache, env_cache_lock
    options = req.get_options()

    if not options.has_key('TracEnv') and not options.has_key('TracEnvParentDir'):
        raise EnvironmentError, \
              'Missing PythonOption "TracEnv". Trac requires this option '\
              'to point to a valid Trac Environment.'

    if options.has_key('TracEnv'):
        env_path = options['TracEnv']

    elif options.has_key('TracEnvParentDir'):
        env_parent_dir = options['TracEnvParentDir']
        env_name = mpr.cgi_location.split('/')[-1]
        env_path = os.path.join(env_parent_dir, env_name)
        if len(env_name) == 0 or not os.path.exists(env_path):
            send_project_index(req, mpr, env_parent_dir)
            return None

    env_cache_lock.acquire()
    if not env_path in env_cache:
        env_cache[env_path] = open_environment(env_path, mpr)
    env = env_cache[env_path]
    env_cache_lock.release()
    return env

def handler(req):
    global projects, projects_lock

    mpr = ModPythonRequest(req)
    mpr.init_request()

    env = get_environment(req, mpr)
    if not env:
        return apache.OK

    args = TracFieldStorage(req)
    core.parse_path_info(args, mpr.path_info)

    req.content_type = 'text/html'
    try:
        core.dispatch_request(mpr.path_info, args, mpr, env)
    except Exception, e:
        core.send_pretty_error(e, env, mpr)
    return apache.OK
<commit_msg>Remove unused import<commit_after># -*- coding: iso8859-1 -*-
#
# Copyright (C) 2004 Edgewall Software
# Copyright (C) 2004 Christopher Lenz <cmlenz@gmx.de>
#
# Trac is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version.
#
# Trac is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
# Author: Christopher Lenz <cmlenz@gmx.de>

import os
import re, threading
import auth, core, Environment, Href, Wiki
from util import TracError, href_join
from mod_python import apache, util

class ModPythonRequest(core.Request):

    def __init__(self, req):
        self.req = req

    def init_request(self):
        core.Request.init_request(self)
        options = self.req.get_options()

        if options.has_key('TracEnvParentDir') and self.req.path_info:
            # We have to remove one path element from path_info when we're
            # using TracEnvParentDir
            self.path_info = re.sub('/[^/]+', '', self.req.path_info, 1)
        else:
            self.path_info = self.req.path_info

        if len(self.path_info):
            self.cgi_location = self.req.uri[:-len(self.path_info)]
        else:
            self.cgi_location = self.req.uri

        if len(self.req.path_info):
            self.idx_location = self.req.uri[:-len(self.req.path_info)]
        else:
            self.idx_location = self.req.uri

        # TODO This will need proxy host name support (see #437 and [581])
        host = self.req.hostname
        port = self.req.connection.local_addr[1]
        if port == 80:
            self.base_url = 'http://%s%s' % (host, self.cgi_location)
        elif port == 443:
            self.base_url = 'https://%s%s' % (host, self.cgi_location)
        else:
            self.base_url = 'http://%s:%d%s' % (host, port, self.cgi_location)

        self.remote_addr = self.req.connection.remote_ip
        self.remote_user = self.req.user
        self.command = self.req.method

        if self.req.headers_in.has_key('Cookie'):
            self.incookie.load(self.req.headers_in['Cookie'])

        self.hdf.setValue('HTTP.Host', self.req.hostname)

    def read(self, len):
        return self.req.read(len)

    def write(self, data):
        self.req.write(data)

    def get_header(self, name):
        return self.req.headers_in.get(name)

    def send_response(self, code):
        self.req.status = code

    def send_header(self, name, value):
        if name.lower() == 'content-type':
            self.req.content_type = value
        else:
            self.req.headers_out.add(name, str(value))

    def end_headers(self):
        pass

class TracFieldStorage(util.FieldStorage):
    """
    FieldStorage class with an added get function.
    """
    def get(self, key, default=''):
        return util.FieldStorage.get(self, key, default)

def send_project_index(req, mpr, dir):
    req.content_type = 'text/html'
    req.write('<title>Available Projects</title>')
    req.write('
Available Projects

')
    for project in os.listdir(dir):
        req.write('
%s
' % (href_join(mpr.idx_location,
                                                              project),
                                                    project))
    req.write('
')

def open_environment(env_path, mpr):
    env = Environment.Environment(env_path)
    version = env.get_version()
    if version < Environment.db_version:
        raise TracError('The Trac environment needs to be upgraded. '
                        'Run "trac-admin %s upgrade"' % env_path)
    elif version > Environment.db_version:
        raise TracError('Unknown Trac Environment version (%d).' % version)

    env.href = Href.Href(mpr.cgi_location)
    env.abs_href = Href.Href(mpr.base_url)
    # Let the wiki module build a dictionary of all page names
    database = env.get_db_cnx()
    Wiki.populate_page_dict(database, env)
    return env

env_cache = {}
env_cache_lock = threading.Lock()

def get_environment(req, mpr):
    global env_cache, env_cache_lock
    options = req.get_options()

    if not options.has_key('TracEnv') and not options.has_key('TracEnvParentDir'):
        raise EnvironmentError, \
              'Missing PythonOption "TracEnv" or "TracEnvParentDir". Trac '\
              'requires one of these options to locate the Trac environment(s).'

    if options.has_key('TracEnv'):
        env_path = options['TracEnv']

    elif options.has_key('TracEnvParentDir'):
        env_parent_dir = options['TracEnvParentDir']
        env_name = mpr.cgi_location.split('/')[-1]
        env_path = os.path.join(env_parent_dir, env_name)
        if len(env_name) == 0 or not os.path.exists(env_path):
            send_project_index(req, mpr, env_parent_dir)
            return None

    env_cache_lock.acquire()
    if not env_path in env_cache:
        env_cache[env_path] = open_environment(env_path, mpr)
    env = env_cache[env_path]
    env_cache_lock.release()
    return env

def handler(req):
    global projects, projects_lock

    mpr = ModPythonRequest(req)
    mpr.init_request()

    env = get_environment(req, mpr)
    if not env:
        return apache.OK

    args = TracFieldStorage(req)
    core.parse_path_info(args, mpr.path_info)

    req.content_type = 'text/html'
    try:
        core.dispatch_request(mpr.path_info, args, mpr, env)
    except Exception, e:
        core.send_pretty_error(e, env, mpr)
    return apache.OK
<|endoftext|>