20ba4125a281dc02f9233847c4bb355839af9c10
[quix0rs-apt-p2p.git] / apt_p2p / apt_p2p_conf.py
1
2 """Loading of configuration files and parameters.
3
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
10
11 """
12
13 import os, sys
14 from ConfigParser import SafeConfigParser
15
16 from twisted.python import log, versions
17 from twisted.trial import unittest
18
19 class ConfigError(Exception):
20     """Errors that occur in the loading of configuration variables."""
21     def __init__(self, message):
22         self.message = message
23     def __str__(self):
24         return repr(self.message)
25
26 version = versions.Version('apt-p2p', 0, 0, 0)
27
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]))
34
35 DEFAULT_CONFIG_FILES=['/etc/apt-p2p/apt-p2p.conf',
36                       home + '/.apt-p2p/apt-p2p.conf']
37
38 DEFAULTS = {
39
40     # Port to listen on for all requests (TCP and UDP)
41     'PORT': '9977',
42     
43     # The rate to limit sending data to peers to, in KBytes/sec.
44     # Set this to 0 to not limit the upload bandwidth.
45     'UPLOAD_LIMIT': '0',
46
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
50     # there are peers.
51     'MIN_DOWNLOAD_PEERS': '3',
52
53     # Directory to store the downloaded files in
54     'CACHE_DIR': home + '/.apt-p2p/cache',
55     
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
59     'OTHER_DIRS': """""",
60     
61     # Whether it's OK to use an IP address from a known local/private range
62     'LOCAL_OK': 'no',
63
64     # Unload the packages cache after an interval of inactivity this long.
65     # The packages cache uses a lot of memory, and only takes a few seconds
66     # to reload when a new request arrives.
67     'UNLOAD_PACKAGES_CACHE': '5m',
68
69     # Refresh the DHT keys after this much time has passed.
70     # This should be a time slightly less than the DHT's KEY_EXPIRE value.
71     'KEY_REFRESH': '57m',
72
73     # Which DHT implementation to use.
74     # It must be possible to do "from <DHT>.DHT import DHT" to get a class that
75     # implements the IDHT interface.
76     'DHT': 'apt_p2p_Khashmir',
77
78     # Whether to only run the DHT (for providing only a bootstrap node)
79     'DHT-ONLY': 'no',
80 }
81
82 DHT_DEFAULTS = {
83     # bootstrap nodes to contact to join the DHT
84     'BOOTSTRAP': """www.camrdale.org:9977""",
85     
86     # whether this node is a bootstrap node
87     'BOOTSTRAP_NODE': "no",
88     
89     # checkpoint every this many seconds
90     'CHECKPOINT_INTERVAL': '5m', # five minutes
91     
92     ### SEARCHING/STORING
93     # concurrent xmlrpc calls per find node/value request!
94     'CONCURRENT_REQS': '4',
95     
96     # how many hosts to post to
97     'STORE_REDUNDANCY': '3',
98     
99     # How many values to attempt to retrieve from the DHT.
100     # Setting this to 0 will try and get all values (which could take a while if
101     # a lot of nodes have values). Setting it negative will try to get that
102     # number of results from only the closest STORE_REDUNDANCY nodes to the hash.
103     # The default is a large negative number so all values from the closest
104     # STORE_REDUNDANCY nodes will be retrieved.
105     'RETRIEVE_VALUES': '-10000',
106
107     ###  ROUTING TABLE STUFF
108     # how many times in a row a node can fail to respond before it's booted from the routing table
109     'MAX_FAILURES': '3',
110     
111     # never ping a node more often than this
112     'MIN_PING_INTERVAL': '15m', # fifteen minutes
113     
114     # refresh buckets that haven't been touched in this long
115     'BUCKET_STALENESS': '1h', # one hour
116     
117     # expire entries older than this
118     'KEY_EXPIRE': '1h', # 60 minutes
119     
120     # whether to spew info about the requests/responses in the protocol
121     'SPEW': 'no',
122 }
123
124 class AptP2PConfigParser(SafeConfigParser):
125     """Adds 'gettime' and 'getstringlist' to ConfigParser objects.
126     
127     @ivar time_multipliers: the 'gettime' suffixes and the multipliers needed
128         to convert them to seconds
129     """
130     
131     time_multipliers={
132         's': 1,    #seconds
133         'm': 60,   #minutes
134         'h': 3600, #hours
135         'd': 86400,#days
136         }
137
138     def gettime(self, section, option):
139         """Read the config parameter as a time value."""
140         mult = 1
141         value = self.get(section, option)
142         if len(value) == 0:
143             raise ConfigError("Configuration parse error: [%s] %s" % (section, option))
144         suffix = value[-1].lower()
145         if suffix in self.time_multipliers.keys():
146             mult = self.time_multipliers[suffix]
147             value = value[:-1]
148         return int(value)*mult
149     
150     def getstring(self, section, option):
151         """Read the config parameter as a string."""
152         return self.get(section,option)
153     
154     def getstringlist(self, section, option):
155         """Read the multi-line config parameter as a list of strings."""
156         return self.get(section,option).split()
157
158     def optionxform(self, option):
159         """Use all uppercase in the config parameters names."""
160         return option.upper()
161
162 # Initialize the default config parameters
163 config = AptP2PConfigParser(DEFAULTS)
164 config.add_section(config.get('DEFAULT', 'DHT'))
165 for k in DHT_DEFAULTS:
166     config.set(config.get('DEFAULT', 'DHT'), k, DHT_DEFAULTS[k])
167
168 class TestConfigParser(unittest.TestCase):
169     """Unit tests for the config parser."""
170
171     def test_uppercase(self):
172         config.set('DEFAULT', 'case_tester', 'foo')
173         self.failUnless(config.get('DEFAULT', 'CASE_TESTER') == 'foo')
174         self.failUnless(config.get('DEFAULT', 'case_tester') == 'foo')
175         config.set('DEFAULT', 'TEST_CASE', 'bar')
176         self.failUnless(config.get('DEFAULT', 'TEST_CASE') == 'bar')
177         self.failUnless(config.get('DEFAULT', 'test_case') == 'bar')
178         config.set('DEFAULT', 'FINAL_test_CASE', 'foobar')
179         self.failUnless(config.get('DEFAULT', 'FINAL_TEST_CASE') == 'foobar')
180         self.failUnless(config.get('DEFAULT', 'final_test_case') == 'foobar')
181         self.failUnless(config.get('DEFAULT', 'FINAL_test_CASE') == 'foobar')
182         self.failUnless(config.get('DEFAULT', 'final_TEST_case') == 'foobar')
183     
184     def test_time(self):
185         config.set('DEFAULT', 'time_tester_1', '2d')
186         self.failUnless(config.gettime('DEFAULT', 'time_tester_1') == 2*86400)
187         config.set('DEFAULT', 'time_tester_2', '3h')
188         self.failUnless(config.gettime('DEFAULT', 'time_tester_2') == 3*3600)
189         config.set('DEFAULT', 'time_tester_3', '4m')
190         self.failUnless(config.gettime('DEFAULT', 'time_tester_3') == 4*60)
191         config.set('DEFAULT', 'time_tester_4', '37s')
192         self.failUnless(config.gettime('DEFAULT', 'time_tester_4') == 37)
193         
194     def test_string(self):
195         config.set('DEFAULT', 'string_test', 'foobar')
196         self.failUnless(type(config.getstring('DEFAULT', 'string_test')) == str)
197         self.failUnless(config.getstring('DEFAULT', 'string_test') == 'foobar')
198
199     def test_stringlist(self):
200         config.set('DEFAULT', 'stringlist_test', """foo
201         bar   
202         foobar  """)
203         l = config.getstringlist('DEFAULT', 'stringlist_test')
204         self.failUnless(type(l) == list)
205         self.failUnless(len(l) == 3)
206         self.failUnless(l[0] == 'foo')
207         self.failUnless(l[1] == 'bar')
208         self.failUnless(l[2] == 'foobar')
209