3f17473c9dbe46ce9c9c78ccf744fc9da111faad
[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     # Whether a remote peer can access the statistics page
65     'REMOTE_STATS': 'yes',
66
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',
71
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': '57m',
75
76     # The user name to try and run as (leave blank to run as current user)
77     'USERNAME': 'apt-p2p',
78     
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',
83
84     # Whether to only run the DHT (for providing only a bootstrap node)
85     'DHT-ONLY': 'no',
86 }
87
88 DHT_DEFAULTS = {
89     # bootstrap nodes to contact to join the DHT
90     'BOOTSTRAP': """www.camrdale.org:9977""",
91     
92     # whether this node is a bootstrap node
93     'BOOTSTRAP_NODE': "no",
94     
95     # checkpoint every this many seconds
96     'CHECKPOINT_INTERVAL': '5m', # five minutes
97     
98     ### SEARCHING/STORING
99     # concurrent xmlrpc calls per find node/value request!
100     'CONCURRENT_REQS': '4',
101     
102     # how many hosts to post to
103     'STORE_REDUNDANCY': '3',
104     
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',
112
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
115     'MAX_FAILURES': '3',
116     
117     # never ping a node more often than this
118     'MIN_PING_INTERVAL': '15m', # fifteen minutes
119     
120     # refresh buckets that haven't been touched in this long
121     'BUCKET_STALENESS': '1h', # one hour
122     
123     # expire entries older than this
124     'KEY_EXPIRE': '1h', # 60 minutes
125     
126     # whether to spew info about the requests/responses in the protocol
127     'SPEW': 'no',
128 }
129
130 class AptP2PConfigParser(SafeConfigParser):
131     """Adds 'gettime' and 'getstringlist' to ConfigParser objects.
132     
133     @ivar time_multipliers: the 'gettime' suffixes and the multipliers needed
134         to convert them to seconds
135     """
136     
137     time_multipliers={
138         's': 1,    #seconds
139         'm': 60,   #minutes
140         'h': 3600, #hours
141         'd': 86400,#days
142         }
143
144     def gettime(self, section, option):
145         """Read the config parameter as a time value."""
146         mult = 1
147         value = self.get(section, option)
148         if len(value) == 0:
149             raise ConfigError("Configuration parse error: [%s] %s" % (section, option))
150         suffix = value[-1].lower()
151         if suffix in self.time_multipliers.keys():
152             mult = self.time_multipliers[suffix]
153             value = value[:-1]
154         return int(value)*mult
155     
156     def getstring(self, section, option):
157         """Read the config parameter as a string."""
158         return self.get(section,option)
159     
160     def getstringlist(self, section, option):
161         """Read the multi-line config parameter as a list of strings."""
162         return self.get(section,option).split()
163
164     def optionxform(self, option):
165         """Use all uppercase in the config parameters names."""
166         return option.upper()
167
168 # Initialize the default config parameters
169 config = AptP2PConfigParser(DEFAULTS)
170 config.add_section(config.get('DEFAULT', 'DHT'))
171 for k in DHT_DEFAULTS:
172     config.set(config.get('DEFAULT', 'DHT'), k, DHT_DEFAULTS[k])
173
174 class TestConfigParser(unittest.TestCase):
175     """Unit tests for the config parser."""
176
177     def test_uppercase(self):
178         config.set('DEFAULT', 'case_tester', 'foo')
179         self.failUnless(config.get('DEFAULT', 'CASE_TESTER') == 'foo')
180         self.failUnless(config.get('DEFAULT', 'case_tester') == 'foo')
181         config.set('DEFAULT', 'TEST_CASE', 'bar')
182         self.failUnless(config.get('DEFAULT', 'TEST_CASE') == 'bar')
183         self.failUnless(config.get('DEFAULT', 'test_case') == 'bar')
184         config.set('DEFAULT', 'FINAL_test_CASE', 'foobar')
185         self.failUnless(config.get('DEFAULT', 'FINAL_TEST_CASE') == 'foobar')
186         self.failUnless(config.get('DEFAULT', 'final_test_case') == 'foobar')
187         self.failUnless(config.get('DEFAULT', 'FINAL_test_CASE') == 'foobar')
188         self.failUnless(config.get('DEFAULT', 'final_TEST_case') == 'foobar')
189     
190     def test_time(self):
191         config.set('DEFAULT', 'time_tester_1', '2d')
192         self.failUnless(config.gettime('DEFAULT', 'time_tester_1') == 2*86400)
193         config.set('DEFAULT', 'time_tester_2', '3h')
194         self.failUnless(config.gettime('DEFAULT', 'time_tester_2') == 3*3600)
195         config.set('DEFAULT', 'time_tester_3', '4m')
196         self.failUnless(config.gettime('DEFAULT', 'time_tester_3') == 4*60)
197         config.set('DEFAULT', 'time_tester_4', '37s')
198         self.failUnless(config.gettime('DEFAULT', 'time_tester_4') == 37)
199         
200     def test_string(self):
201         config.set('DEFAULT', 'string_test', 'foobar')
202         self.failUnless(type(config.getstring('DEFAULT', 'string_test')) == str)
203         self.failUnless(config.getstring('DEFAULT', 'string_test') == 'foobar')
204
205     def test_stringlist(self):
206         config.set('DEFAULT', 'stringlist_test', """foo
207         bar   
208         foobar  """)
209         l = config.getstringlist('DEFAULT', 'stringlist_test')
210         self.failUnless(type(l) == list)
211         self.failUnless(len(l) == 3)
212         self.failUnless(l[0] == 'foo')
213         self.failUnless(l[1] == 'bar')
214         self.failUnless(l[2] == 'foobar')
215