2 """Loading of configuration files and parameters.
4 @type version: L{twisted.python.versions.Version}
5 @var version: the version of this program
6 @type DEFAULT_CONFIG_FILES: C{list} of C{string}
7 @var DEFAULT_CONFIG_FILES: the default config files to load (in order)
8 @var DEFAULTS: the default config parameter values for the main program
9 @var DHT_DEFAULTS: the default config parameter values for the default DHT
14 from ConfigParser import SafeConfigParser
16 from twisted.python import log, versions
17 from twisted.trial import unittest
19 class ConfigError(Exception):
20 """Errors that occur in the loading of configuration variables."""
21 def __init__(self, message):
22 self.message = message
24 return repr(self.message)
26 version = versions.Version('apt-p2p', 0, 0, 0)
28 # Set the home parameter
29 home = os.path.expandvars('${HOME}')
30 if home == '${HOME}' or not os.path.isdir(home):
31 home = os.path.expanduser('~')
32 if not os.path.isdir(home):
33 home = os.path.abspath(os.path.dirname(sys.argv[0]))
35 DEFAULT_CONFIG_FILES=['/etc/apt-p2p/apt-p2p.conf',
36 home + '/.apt-p2p/apt-p2p.conf']
40 # Port to listen on for all requests (TCP and UDP)
43 # The rate to limit sending data to peers to, in KBytes/sec.
44 # Set this to 0 to not limit the upload bandwidth.
47 # The minimum number of peers before the mirror is not used.
48 # If there are fewer peers than this for a file, the mirror will also be
49 # used to speed up the download. Set to 0 to never use the mirror if
51 'MIN_DOWNLOAD_PEERS': '3',
53 # Directory to store the downloaded files in
54 'CACHE_DIR': home + '/.apt-p2p/cache',
56 # Other directories containing packages to share with others
57 # WARNING: all files in these directories will be hashed and available
58 # for everybody to download
61 # Whether it's OK to use an IP address from a known local/private range
64 # Whether a remote peer can access the statistics page
65 'REMOTE_STATS': 'yes',
67 # Unload the packages cache after an interval of inactivity this long.
68 # The packages cache uses a lot of memory, and only takes a few seconds
69 # to reload when a new request arrives.
70 'UNLOAD_PACKAGES_CACHE': '5m',
72 # Refresh the DHT keys after this much time has passed.
73 # This should be a time slightly less than the DHT's KEY_EXPIRE value.
74 'KEY_REFRESH': '2.5h',
76 # The user name to try and run as (leave blank to run as current user)
77 'USERNAME': 'apt-p2p',
79 # Which DHT implementation to use.
80 # It must be possible to do "from <DHT>.DHT import DHT" to get a class that
81 # implements the IDHT interface.
82 'DHT': 'apt_p2p_Khashmir',
84 # Whether to only run the DHT (for providing only a bootstrap node)
89 # bootstrap nodes to contact to join the DHT
90 'BOOTSTRAP': """www.camrdale.org:9977""",
92 # whether this node is a bootstrap node
93 'BOOTSTRAP_NODE': "no",
95 # checkpoint every this many seconds
96 'CHECKPOINT_INTERVAL': '5m', # five minutes
99 # concurrent xmlrpc calls per find node/value request!
100 'CONCURRENT_REQS': '4',
102 # how many hosts to post to
103 'STORE_REDUNDANCY': '3',
105 # How many values to attempt to retrieve from the DHT.
106 # Setting this to 0 will try and get all values (which could take a while if
107 # a lot of nodes have values). Setting it negative will try to get that
108 # number of results from only the closest STORE_REDUNDANCY nodes to the hash.
109 # The default is a large negative number so all values from the closest
110 # STORE_REDUNDANCY nodes will be retrieved.
111 'RETRIEVE_VALUES': '-10000',
113 ### ROUTING TABLE STUFF
114 # how many times in a row a node can fail to respond before it's booted from the routing table
117 # never ping a node more often than this
118 'MIN_PING_INTERVAL': '15m', # fifteen minutes
120 # refresh buckets that haven't been touched in this long
121 'BUCKET_STALENESS': '1h', # one hour
123 # expire entries older than this
124 'KEY_EXPIRE': '3h', # 3 hours
126 # Timeout KRPC requests to nodes after this time.
127 'KRPC_TIMEOUT': '14s',
129 # KRPC requests are resent using exponential backoff starting with this delay.
130 # The request will first be resent after the delay set here.
131 # The request will be resent again after twice the delay set here. etc.
132 # e.g. if TIMEOUT is 14 sec., and INITIAL_DELAY is 2 sec., then requests will
133 # be resent at times 0, 2 (2 sec. later), and 6 (4 sec. later), and then will
135 'KRPC_INITIAL_DELAY': '2s',
137 # whether to spew info about the requests/responses in the protocol
141 class AptP2PConfigParser(SafeConfigParser):
142 """Adds 'gettime' and 'getstringlist' to ConfigParser objects.
144 @ivar time_multipliers: the 'gettime' suffixes and the multipliers needed
145 to convert them to seconds
155 def gettime(self, section, option):
156 """Read the config parameter as a time value."""
158 value = self.get(section, option)
160 raise ConfigError("Configuration parse error: [%s] %s" % (section, option))
161 suffix = value[-1].lower()
162 if suffix in self.time_multipliers.keys():
163 mult = self.time_multipliers[suffix]
165 return int(float(value)*mult)
167 def getstring(self, section, option):
168 """Read the config parameter as a string."""
169 return self.get(section,option)
171 def getstringlist(self, section, option):
172 """Read the multi-line config parameter as a list of strings."""
173 return self.get(section,option).split()
175 def optionxform(self, option):
176 """Use all uppercase in the config parameters names."""
177 return option.upper()
179 # Initialize the default config parameters
180 config = AptP2PConfigParser(DEFAULTS)
181 config.add_section(config.get('DEFAULT', 'DHT'))
182 for k in DHT_DEFAULTS:
183 config.set(config.get('DEFAULT', 'DHT'), k, DHT_DEFAULTS[k])
185 class TestConfigParser(unittest.TestCase):
186 """Unit tests for the config parser."""
188 def test_uppercase(self):
189 config.set('DEFAULT', 'case_tester', 'foo')
190 self.failUnless(config.get('DEFAULT', 'CASE_TESTER') == 'foo')
191 self.failUnless(config.get('DEFAULT', 'case_tester') == 'foo')
192 config.set('DEFAULT', 'TEST_CASE', 'bar')
193 self.failUnless(config.get('DEFAULT', 'TEST_CASE') == 'bar')
194 self.failUnless(config.get('DEFAULT', 'test_case') == 'bar')
195 config.set('DEFAULT', 'FINAL_test_CASE', 'foobar')
196 self.failUnless(config.get('DEFAULT', 'FINAL_TEST_CASE') == 'foobar')
197 self.failUnless(config.get('DEFAULT', 'final_test_case') == 'foobar')
198 self.failUnless(config.get('DEFAULT', 'FINAL_test_CASE') == 'foobar')
199 self.failUnless(config.get('DEFAULT', 'final_TEST_case') == 'foobar')
202 config.set('DEFAULT', 'time_tester_1', '2d')
203 self.failUnless(config.gettime('DEFAULT', 'time_tester_1') == 2*86400)
204 config.set('DEFAULT', 'time_tester_2', '3h')
205 self.failUnless(config.gettime('DEFAULT', 'time_tester_2') == 3*3600)
206 config.set('DEFAULT', 'time_tester_3', '4m')
207 self.failUnless(config.gettime('DEFAULT', 'time_tester_3') == 4*60)
208 config.set('DEFAULT', 'time_tester_4', '37s')
209 self.failUnless(config.gettime('DEFAULT', 'time_tester_4') == 37)
211 def test_floating_time(self):
212 config.set('DEFAULT', 'time_float_tester_1', '2.5d')
213 self.failUnless(config.gettime('DEFAULT', 'time_float_tester_1') == int(2.5*86400))
214 config.set('DEFAULT', 'time_float_tester_2', '0.5h')
215 self.failUnless(config.gettime('DEFAULT', 'time_float_tester_2') == int(0.5*3600))
216 config.set('DEFAULT', 'time_float_tester_3', '4.3333m')
217 self.failUnless(config.gettime('DEFAULT', 'time_float_tester_3') == int(4.3333*60))
219 def test_string(self):
220 config.set('DEFAULT', 'string_test', 'foobar')
221 self.failUnless(type(config.getstring('DEFAULT', 'string_test')) == str)
222 self.failUnless(config.getstring('DEFAULT', 'string_test') == 'foobar')
224 def test_stringlist(self):
225 config.set('DEFAULT', 'stringlist_test', """foo
228 l = config.getstringlist('DEFAULT', 'stringlist_test')
229 self.failUnless(type(l) == list)
230 self.failUnless(len(l) == 3)
231 self.failUnless(l[0] == 'foo')
232 self.failUnless(l[1] == 'bar')
233 self.failUnless(l[2] == 'foobar')