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