updated on Fri Jan 20 20:16:25 UTC 2012
[aur-mirror.git] / pkgdistcache / pkgdistcache-client
blobe051d26d18a2ef043a7af0c18f16a6f906504a33
1 #!/usr/bin/python2
2 # coding: utf-8
4 # pkgdistcache client v0.3.1
5 # by Alessio Bianchi <venator85@gmail.com>
8 import sys
9 import os
10 import os.path
11 import subprocess
12 import string
13 import avahi
14 import dbus
15 import gobject
16 import dbus.glib
17 import pickle
19 colors = {'none': '\033[0m',
20         'black': '\033[0;30m',          'bold_black': '\033[1;30m',
21         'red': '\033[0;31m',            'bold_red': '\033[1;31m',
22         'green': '\033[0;32m',          'bold_green': '\033[1;32m',
23         'yellow': '\033[0;33m',         'bold_yellow': '\033[1;33m',
24         'blue': '\033[0;34m',           'bold_blue': '\033[1;34m',
25         'magenta': '\033[0;35m',        'bold_magenta': '\033[1;35m',
26         'cyan': '\033[0;36m',           'bold_cyan': '\033[1;36m',
27         'white': '\033[0;37m',          'bold_white': '\033[1;37m'}
29 def printmsg(msg):
30         print "%s>> %s%s" % (colors['bold_blue'], msg, colors['none'])
32 def printerr(msg):
33         print "%s!! %s%s" % (colors['bold_red'], msg, colors['none'])
35 def printwarn(msg):
36         print "%s!! %s%s" % (colors['bold_yellow'], msg, colors['none'])
38 # Run a command synchronously, redirecting stdout and stderr to strings
39 def runcmd(cmd, cwd=None):
40         pipe = subprocess.Popen(cmd, shell=True, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
41         (stdout, stderr) = pipe.communicate()   # wait for process to terminate and return stdout and stderr
42         return {'stdout': stdout.strip(), 'stderr': stderr.strip(), 'retcode': pipe.returncode}
44 # Run a command synchronously, sending stdout and stderr to shell
45 def runcmd2(cmd, cwd=None):
46         pipe = subprocess.Popen(cmd, shell=True, cwd=cwd, stdout=None, stderr=None)
47         pipe.communicate()      # wait for process to terminate
48         return pipe.returncode
50 class Service(object):
51         def __init__(self, service, host, ip, port):
52                 self.service = service
53                 self.host = host
54                 self.ip = ip
55                 self.port = port
56                 
57         def __str__(self):
58                 return "(%s, %s, %s, %d)" % (self.service, self.host, self.ip, self.port)
59         
60         def __repr__(self):
61                 # il tostring sulle liste รจ repr
62                 return self.__str__()
63         
64         def __hash__(self):
65                 return hash(self.__str__())
66         
67         def __eq__(self, other):
68                 return hash(self) == hash(other)
70 class AvahiBrowser(object):
71         def __init__(self):
72                 # Connect to the system bus...
73                 self.bus = dbus.SystemBus()
74                 # Get a proxy to the object we want to talk to.
75                 avahi_proxy = self.bus.get_object(avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER)
76                 # Set the interface we want to use; server in this case.
77                 self.server = dbus.Interface(avahi_proxy, avahi.DBUS_INTERFACE_SERVER)
78                 self.version_string = self.server.GetVersionString()
79                 self.domain = "local"
80                 self.loop = gobject.MainLoop()
81                 self.services = []
83         def browse(self, stype):
84                 # Ask the server for a path to the browser object for the service we're interested in...
85                 browser_path = self.server.ServiceBrowserNew(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, stype, self.domain, dbus.UInt32(0))
86                 # Get it's proxy object...
87                 browser_proxy = self.bus.get_object(avahi.DBUS_NAME, browser_path)
88                 # And set the interface we want to use          
89                 browser = dbus.Interface(browser_proxy, avahi.DBUS_INTERFACE_SERVICE_BROWSER)
91                 # Now connect the call backs to the relevant signals.
92                 browser.connect_to_signal('ItemNew', self.new_service)
93                 browser.connect_to_signal('AllForNow', self.all_for_now)
94                 self.loop.run()
95                 return self.services
96         
97         def new_service(self, interface, protocol, name, stype, domain, flags):
98                 try:
99                         s = self.server.ResolveService(interface, protocol, name, stype, domain, avahi.PROTO_UNSPEC, dbus.UInt32(0))
100                         service = Service(str(s[3]), str(s[2]), str(s[7]), int(s[8])) # service name, host, ip, port
101                         self.services.append(service)
102                 except TimeoutError:
103                         printwarn("Timeout error during service resolution!")
105         def all_for_now(self):
106                 self.loop.quit()
108 def main(argv):
109         # load configuration file
110         conf_file = '/etc/pkgdistcache.conf'
111         if os.path.isfile(conf_file):
112                 config = eval(open(conf_file).read())
113         else:
114                 printerr("Config file " + conf_file + " not found")
115                 return 2
116         
117         download_cmd_template = string.Template(config['download_cmd'])
118         
119         pkg = os.path.basename(argv[1])         # argv[1] = %u passed by pacman
121         must_download = True
122         if not pkg.endswith('.db.tar.gz'):
123                 # find first /tmp/pkgdistcache.* file modified less than CACHE_FILE_LIFE minutes ago
124                 cmd = 'find /tmp -name "pkgdistcache.*" -mmin -' + str(config['cache_file_life'])
125                 cache_file = runcmd(cmd)['stdout']
126                 if len(cache_file) > 0:
127                         # recent cache file found, use it
128                         with open(cache_file, 'rb') as f:
129                                 pkgdistcache_clients = pickle.load(f)
130                 else:
131                         # recent cache file not found, discover other pkgdistcache capable hosts via avahi and save result to a new cache file
132                         runcmd("rm -f /tmp/pkgdistcache.*")             # remove any old cache file
133                         cache_file = runcmd('mktemp /tmp/pkgdistcache.XXXXX')['stdout']         # create new cache file
134         
135                         hostname = runcmd('hostname')['stdout']
136                         browser = AvahiBrowser()
137                         clients = browser.browse("_pkgdistcache._tcp")
138                         clients = set(clients)  # remove duplicates (eg services offered on more than a network card etc.)
139                         pkgdistcache_clients = []
140                         for client in clients:
141                                 if client.host != hostname:             # exclude current machine from results
142                                         pkgdistcache_clients.append(client)
143                         
144                         with open(cache_file, 'wb') as f:
145                                 pickle.dump(pkgdistcache_clients, f, -1)
146         
147                 for client in pkgdistcache_clients:
148                         url = "http://" + client.ip + ":" + str(client.port) + "/" + pkg
149                         dst = argv[2]
150                         printmsg("Downloading " + pkg + " from host '" + client.host + "'")
151                         download_cmd = download_cmd_template.substitute({'u': url, 'o': dst})
152                         try:
153                                 ret = runcmd2(download_cmd)
154                                 if ret == 0:
155                                         must_download = False
156                                         break
157                                 else:
158                                         printwarn("Host '%s' doesn't have %s in cache" % (client.host, pkg))
159                         except KeyboardInterrupt:
160                                 printerr("Aborted")
161                                 return 1
162         
163         # download package file from mirror if necessary
164         if must_download == True:
165                 try:
166                         download_cmd = download_cmd_template.substitute({'u': argv[1], 'o': argv[2]})
167                         return runcmd2(download_cmd)
168                 except KeyboardInterrupt:
169                         printerr("Aborted")
170                         return 1
171         else:
172                 return 0
173         
174 if __name__ == '__main__':
175         sys.exit(main(sys.argv))