--- /dev/null
+#!/usr/bin/env python
+
+# Load apt-dht application
+#
+# There are two ways apt-dht can be started:
+# 1. twistd -y apt-dht
+# - twistd will load this file and execute the app
+# in 'application' variable
+# 2. from command line
+# - __name__ will be '__main__'
+
+import pwd,sys
+
+from twisted.application import service, internet, app, strports
+from twisted.internet import reactor
+from twisted.python import usage, log
+from twisted.web2 import channel
+
+from apt_dht import AptDHT
+from apt_dht_conf import config
+
+config_file = []
+
+if __name__ == '__main__':
+ # Parse command line parameters when started on command line
+ class AptDHTOptions(usage.Options):
+ optFlags = [
+ ['help', 'h'],
+ ]
+ optParameters = [
+ ['config-file', 'c', None, "Configuration file"],
+ ]
+ longdesc="apt-dht is a peer-to-peer downloader for apt users"
+ def opt_version(self):
+ print "apt-dht 1.9.x"
+ sys.exit(0)
+
+ opts = AptDHTOptions()
+ try:
+ opts.parseOptions()
+ except usage.UsageError, ue:
+ print '%s: %s' % (sys.argv[0], ue)
+ sys.exit(1)
+
+ config_file = opts.opts['config-file']
+
+config.read(config_file)
+if config.defaults()['username']:
+ uid,gid = pwd.getpwnam(config.defaults()['username'])[2:4]
+else:
+ uid,gid = None,None
+
+application = service.Application("apt-dht", uid, gid)
+print service.IProcess(application).processName
+service.IProcess(application).processName = 'apt-dht'
+
+myapp = AptDHT()
+site = myapp.getSite()
+s = strports.service('tcp:'+config.defaults()['port'], channel.HTTPFactory(site))
+s.setServiceParent(application)
+
+if __name__ == '__main__':
+ # Run on command line
+ log.startLogging(sys.stdout, setStdout=0)
+ service.IServiceCollection(application).privilegedStartService()
+ service.IServiceCollection(application).startService()
+ reactor.run()
--- /dev/null
+
+from twisted.web2 import server, http, http_headers
+
+from apt_dht_conf import config
+from HTTPServer import TopLevel
+
+class AptDHT:
+ def __init__(self):
+ self.http_server = TopLevel(config.defaults()['cache_dir'], self)
+ self.http_site = server.Site(self.http_server)
+
+ def getSite(self):
+ return self.http_site
+
+ def check_freshness(self, path, modtime, resp):
+ return resp
+
+ def get_resp(self, path):
+ return http.Response(
+ 200,
+ {'content-type': http_headers.MimeType('text', 'html')},
+ """<html><body>
+ <h2>Statistics</h2>
+ <p>TODO: eventually this will cause a P2P lookup.</body></html>""")
--- /dev/null
+
+import os, sys
+from ConfigParser import SafeConfigParser
+
+from twisted.python import log, versions
+
+class ConfigError(Exception):
+ def __init__(self, message):
+ self.message = message
+ def __str__(self):
+ return repr(self.message)
+
+version = versions.Version('apt-dht', 0, 0, 0)
+home = os.path.expandvars('${HOME}')
+if home == '${HOME}' or not os.path.isdir(home):
+ home = os.path.expanduser('~')
+ if not os.path.isdir(home):
+ home = os.path.abspath(os.path.dirname(sys.argv[0]))
+
+DEFAULTS = {
+
+ # Port to listen on for all requests (TCP and UDP)
+ 'port': '9977',
+
+ # Directory to store the downloaded files in
+ 'cache_dir': home + '/.apt-dht/cache',
+
+ # User name to try and run as
+ 'username': '',
+
+ # Which DHT implementation to use
+ 'DHT': 'Khashmir',
+}
+
+DHT_DEFAULTS = {
+ # magic id to use before we know a peer's id
+ 'NULL_ID': 20 * '\0',
+
+ # Kademlia "K" constant, this should be an even number
+ 'K': '8',
+
+ # SHA1 is 160 bits long
+ 'HASH_LENGTH': '160',
+
+ # checkpoint every this many seconds
+ 'CHECKPOINT_INTERVAL': '15m', # fifteen minutes
+
+ ### SEARCHING/STORING
+ # concurrent xmlrpc calls per find node/value request!
+ 'CONCURRENT_REQS': '4',
+
+ # how many hosts to post to
+ 'STORE_REDUNDANCY': '3',
+
+ ### ROUTING TABLE STUFF
+ # how many times in a row a node can fail to respond before it's booted from the routing table
+ 'MAX_FAILURES': '3',
+
+ # never ping a node more often than this
+ 'MIN_PING_INTERVAL': '15m', # fifteen minutes
+
+ # refresh buckets that haven't been touched in this long
+ 'BUCKET_STALENESS': '1h', # one hour
+
+ ### KEY EXPIRER
+ # time before expirer starts running
+ 'KEINITIAL_DELAY': '15s', # 15 seconds - to clean out old stuff in persistent db
+
+ # time between expirer runs
+ 'KE_DELAY': '20m', # 20 minutes
+
+ # expire entries older than this
+ 'KE_AGE': '1h', # 60 minutes
+}
+
+class AptDHTConfigParser(SafeConfigParser):
+ """
+ Adds 'gettime' to ConfigParser to interpret the suffixes.
+ """
+ time_multipliers={
+ 's': 1, #seconds
+ 'm': 60, #minutes
+ 'h': 3600, #hours
+ 'd': 86400,#days
+ }
+
+ def gettime(self, section, option):
+ mult = 1
+ value = self.get(section, option)
+ if len(value) == 0:
+ raise ConfigError("Configuration parse error: [%s] %s" % (section, option))
+ suffix = value[-1].lower()
+ if suffix in self.time_multipliers.keys():
+ mult = self.time_multipliers[suffix]
+ value = value[:-1]
+ return int(value)*mult
+ def getstring(self, section, option):
+ return self.get(section,option)
+ def getstringlist(self, section, option):
+ return self.get(section,option).split()
+
+config = AptDHTConfigParser(DEFAULTS)
+config.add_section(config.get('DEFAULT', 'DHT'))
+for k in DHT_DEFAULTS:
+ config.set(config.get('DEFAULT', 'DHT'), k, DHT_DEFAULTS[k])