Add an optimization to the HTTP client protocol so that pieplining begins sooner.
[quix0rs-apt-p2p.git] / apt_p2p_Khashmir / node.py
1 ## Copyright 2002-2003 Andrew Loewenstern, All Rights Reserved
2 # see LICENSE.txt for license information
3
4 """Represents a node in the DHT.
5
6 @type NULL_ID: C{string}
7 @var NULL_ID: the node ID to use until one is known
8 """
9
10 from datetime import datetime, MINYEAR
11 from types import InstanceType
12
13 from twisted.trial import unittest
14
15 import khash
16 from util import compact
17
18 # magic id to use before we know a peer's id
19 NULL_ID = khash.HASH_LENGTH * '\0'
20
21 class Node:
22     """Encapsulate a node's contact info.
23     
24     @ivar conn: the connection to the remote node (added externally)
25     @ivar table: the routing table (added externally)
26     @type fails: C{int}
27     @ivar fails: number of times this node has failed in a row
28     @type lastSeen: C{datetime.datetime}
29     @ivar lastSeen: the last time a response was received from this node
30     @type id: C{string}
31     @ivar id: the node's ID in the DHT
32     @type num: C{long}
33     @ivar num: the node's ID in number form
34     @type host: C{string}
35     @ivar host: the IP address of the node
36     @type port: C{int}
37     @ivar port: the port of the node
38     @type token: C{string}
39     @ivar token: the last received token from the node
40     @type num_values: C{int}
41     @ivar num_values: the number of values the node has for the key in the
42         currently executing action
43     """
44     
45     def __init__(self, id, host = None, port = None):
46         """Initialize the node.
47         
48         @type id: C{string} or C{dictionary}
49         @param id: the node's ID in the DHT, or a dictionary containing the
50             node's id, host and port
51         @type host: C{string}
52         @param host: the IP address of the node
53             (optional, but must be specified if id is not a dictionary)
54         @type port: C{int}
55         @param port: the port of the node
56             (optional, but must be specified if id is not a dictionary)
57         """
58         self.fails = 0
59         self.lastSeen = datetime(MINYEAR, 1, 1)
60
61         # Alternate method, init Node from dictionary
62         if isinstance(id, dict):
63             host = id['host']
64             port = id['port']
65             id = id['id']
66
67         assert isinstance(id, str)
68         assert isinstance(host, str)
69         self.id = id
70         self.num = khash.intify(id)
71         self.host = host
72         self.port = int(port)
73         self.token = ''
74         self.num_values = 0
75         self._contactInfo = None
76     
77     def updateLastSeen(self):
78         """Updates the last contact time of the node and resets the number of failures."""
79         self.lastSeen = datetime.now()
80         self.fails = 0
81         
82     def updateToken(self, token):
83         """Update the token for the node."""
84         self.token = token
85     
86     def updateNumValues(self, num_values):
87         """Update how many values the node has in the current search for a value."""
88         self.num_values = num_values
89     
90     def msgFailed(self):
91         """Log a failed attempt to contact this node.
92         
93         @rtype: C{int}
94         @return: the number of consecutive failures this node has
95         """
96         self.fails = self.fails + 1
97         return self.fails
98     
99     def contactInfo(self):
100         """Get the compact contact info for the node."""
101         if self._contactInfo is None:
102             self._contactInfo = compact(self.id, self.host, self.port)
103         return self._contactInfo
104     
105     def __repr__(self):
106         return `(self.id, self.host, self.port)`
107     
108     def __copy__(self):
109         """Create a shallow copy of the node, resetting some values."""
110         cp = self.__class__(self.id, self.host, self.port)
111         cp.fails = self.fails
112         cp.lastSeen = self.lastSeen
113         if getattr(self, 'table', None) is not None:
114             cp.table = self.table
115         if getattr(self, 'conn', None) is not None:
116             cp.conn = self.conn
117         return cp
118     
119     #{ Comparators to bisect/index a list of nodes with either a node or a long
120     def __lt__(self, a):
121         if type(a) == InstanceType:
122             a = a.num
123         return self.num < a
124     def __le__(self, a):
125         if type(a) == InstanceType:
126             a = a.num
127         return self.num <= a
128     def __gt__(self, a):
129         if type(a) == InstanceType:
130             a = a.num
131         return self.num > a
132     def __ge__(self, a):
133         if type(a) == InstanceType:
134             a = a.num
135         return self.num >= a
136     def __eq__(self, a):
137         if type(a) == InstanceType:
138             a = a.num
139         return self.num == a
140     def __ne__(self, a):
141         if type(a) == InstanceType:
142             a = a.num
143         return self.num != a
144
145
146 class TestNode(unittest.TestCase):
147     """Unit tests for the node implementation."""
148     def setUp(self):
149         self.node = Node(khash.newID(), '127.0.0.1', 2002)
150     def testUpdateLastSeen(self):
151         t = self.node.lastSeen
152         self.node.updateLastSeen()
153         self.failUnless(t < self.node.lastSeen)
154