]> git.decadent.org.uk Git - dak.git/blob - dak/mirror_split.py
import dak.lib.foo as foo for library modules.
[dak.git] / dak / mirror_split.py
1 #!/usr/bin/env python
2
3 # Prepare and maintain partial trees by architecture
4 # Copyright (C) 2004, 2006  Daniel Silverstone <dsilvers@digital-scurf.org>
5
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19
20
21 ###############################################################################
22 ## <kinnison> So Martin, do you have a quote for me yet?
23 ## <tbm> Make something damned stupid up and attribute it to me, that's okay
24 ###############################################################################
25
26 import sys
27 import apt_pkg
28
29 from stat import S_ISDIR, S_ISLNK, S_ISREG
30 import os
31 import cPickle
32
33 import dak.lib.utils as utils
34
35 ## Master path is the main repository
36 #MASTER_PATH = "/org/ftp.debian.org/scratch/dsilvers/master"
37
38 MASTER_PATH = "***Configure Mirror-Split::FTPPath Please***"
39 TREE_ROOT = "***Configure Mirror-Split::TreeRootPath Please***"
40 TREE_DB_ROOT = "***Configure Mirror-Split::TreeDatabasePath Please***"
41 trees = []
42
43 Cnf = None
44
45 ###############################################################################
46 # A MirrorSplitTarget is a representation of a target. It is a set of archs, a path
47 # and whether or not the target includes source.
48 ##################
49
50 class MirrorSplitTarget:
51     def __init__(self, name, archs, source):
52         self.name = name
53         self.root = "%s/%s" % (TREE_ROOT,name)
54         self.archs = archs.split(",")
55         self.source = source
56         self.dbpath = "%s/%s.db" % (TREE_DB_ROOT,name)
57         self.db = MirrorSplitDB()
58         if os.path.exists( self.dbpath ):
59             self.db.load_from_file( self.dbpath )
60
61     ## Save the db back to disk
62     def save_db(self):
63         self.db.save_to_file( self.dbpath )
64
65     ## Returns true if it's a poolish match
66     def poolish_match(self, path):
67         for a in self.archs:
68             if path.endswith( "_%s.deb" % (a) ):
69                 return 1
70             if path.endswith( "_%s.udeb" % (a) ):
71                 return 1
72         if self.source:
73             if (path.endswith( ".tar.gz" ) or
74                 path.endswith( ".diff.gz" ) or
75                 path.endswith( ".dsc" )):
76                 return 1
77         return 0
78
79     ## Returns false if it's a badmatch distswise
80     def distish_match(self,path):
81         for a in self.archs:
82             if path.endswith("/Contents-%s.gz" % (a)):
83                 return 1
84             if path.find("/binary-%s/" % (a)) != -1:
85                 return 1
86             if path.find("/installer-%s/" % (a)) != -1:
87                 return 1
88         if path.find("/source/") != -1:
89             if self.source:
90                 return 1
91             else:
92                 return 0
93         if path.find("/Contents-") != -1:
94             return 0
95         if path.find("/binary-") != -1:
96             return 0
97         if path.find("/installer-") != -1:
98             return 0
99         return 1
100     
101 ##############################################################################
102 # The applicable function is basically a predicate. Given a path and a
103 # target object its job is to decide if the path conforms for the
104 # target and thus is wanted.
105 #
106 # 'verbatim' is a list of files which are copied regardless
107 # it should be loaded from a config file eventually
108 ##################
109
110 verbatim = [
111     ]
112
113 verbprefix = [
114     "/tools/",
115     "/README",
116     "/doc/"
117     ]
118
119 def applicable(path, target):
120     if path.startswith("/pool/"):
121         return target.poolish_match(path)
122     if (path.startswith("/dists/") or
123         path.startswith("/project/experimental/")):
124         return target.distish_match(path)
125     if path in verbatim:
126         return 1
127     for prefix in verbprefix:
128         if path.startswith(prefix):
129             return 1
130     return 0
131
132
133 ##############################################################################
134 # A MirrorSplitDir is a representation of a tree.
135 #   It distinguishes files dirs and links
136 # Dirs are dicts of (name, MirrorSplitDir)
137 # Files are dicts of (name, inode)
138 # Links are dicts of (name, target)
139 ##############
140
141 class MirrorSplitDir:
142     def __init__(self):
143         self.dirs = {}
144         self.files = {}
145         self.links = {}
146
147 ##############################################################################
148 # A MirrorSplitDB is a container for a MirrorSplitDir...
149 ##############
150
151 class MirrorSplitDB:
152     ## Initialise a MirrorSplitDB as containing nothing
153     def __init__(self):
154         self.root = MirrorSplitDir()
155
156     def _internal_recurse(self, path):
157         bdir = MirrorSplitDir()
158         dl = os.listdir( path )
159         dl.sort()
160         dirs = []
161         for ln in dl:
162             lnl = os.lstat( "%s/%s" % (path, ln) )
163             if S_ISDIR(lnl[0]):
164                 dirs.append(ln)
165             elif S_ISLNK(lnl[0]):
166                 bdir.links[ln] = os.readlink( "%s/%s" % (path, ln) )
167             elif S_ISREG(lnl[0]):
168                 bdir.files[ln] = lnl[1]
169             else:
170                 utils.fubar( "Confused by %s/%s -- not a dir, link or file" %
171                             ( path, ln ) )
172         for d in dirs:
173             bdir.dirs[d] = self._internal_recurse( "%s/%s" % (path,d) )
174
175         return bdir
176
177     ## Recurse through a given path, setting the sequence accordingly
178     def init_from_dir(self, dirp):
179         self.root = self._internal_recurse( dirp )
180
181     ## Load this MirrorSplitDB from file
182     def load_from_file(self, fname):
183         f = open(fname, "r")
184         self.root = cPickle.load(f)
185         f.close()
186
187     ## Save this MirrorSplitDB to a file
188     def save_to_file(self, fname):
189         f = open(fname, "w")
190         cPickle.dump( self.root, f, 1 )
191         f.close()
192
193         
194 ##############################################################################
195 # Helper functions for the tree syncing...
196 ##################
197
198 def _pth(a,b):
199     return "%s/%s" % (a,b)
200
201 def do_mkdir(targ,path):
202     if not os.path.exists( _pth(targ.root, path) ):
203         os.makedirs( _pth(targ.root, path) )
204
205 def do_mkdir_f(targ,path):
206     do_mkdir(targ, os.path.dirname(path))
207
208 def do_link(targ,path):
209     do_mkdir_f(targ,path)
210     os.link( _pth(MASTER_PATH, path),
211              _pth(targ.root, path))
212
213 def do_symlink(targ,path,link):
214     do_mkdir_f(targ,path)
215     os.symlink( link, _pth(targ.root, path) )
216
217 def do_unlink(targ,path):
218     os.unlink( _pth(targ.root, path) )
219
220 def do_unlink_dir(targ,path):
221     os.system( "rm -Rf '%s'" % _pth(targ.root, path) )
222
223 ##############################################################################
224 # Reconciling a target with the sourcedb
225 ################
226
227 def _internal_reconcile( path, srcdir, targdir, targ ):
228     # Remove any links in targdir which aren't in srcdir
229     # Or which aren't applicable
230     rm = []
231     for k in targdir.links.keys():
232         if applicable( _pth(path, k), targ ):
233             if not srcdir.links.has_key(k):
234                 rm.append(k)
235         else:
236             rm.append(k)
237     for k in rm:
238         #print "-L-", _pth(path,k)
239         do_unlink(targ, _pth(path,k))
240         del targdir.links[k]
241     
242     # Remove any files in targdir which aren't in srcdir
243     # Or which aren't applicable
244     rm = []
245     for k in targdir.files.keys():
246         if applicable( _pth(path, k), targ ):
247             if not srcdir.files.has_key(k):
248                 rm.append(k)
249         else:
250             rm.append(k)
251     for k in rm:
252         #print "-F-", _pth(path,k)
253         do_unlink(targ, _pth(path,k))
254         del targdir.files[k]
255
256     # Remove any dirs in targdir which aren't in srcdir
257     rm = []
258     for k in targdir.dirs.keys():
259         if not srcdir.dirs.has_key(k):
260             rm.append(k)
261     for k in rm:
262         #print "-D-", _pth(path,k)
263         do_unlink_dir(targ, _pth(path,k))
264         del targdir.dirs[k]
265
266     # Add/update files
267     for k in srcdir.files.keys():
268         if applicable( _pth(path,k), targ ):
269             if not targdir.files.has_key(k):
270                 #print "+F+", _pth(path,k)
271                 do_link( targ, _pth(path,k) )
272                 targdir.files[k] = srcdir.files[k]
273             else:
274                 if targdir.files[k] != srcdir.files[k]:
275                     #print "*F*", _pth(path,k)
276                     do_unlink( targ, _pth(path,k) )
277                     do_link( targ, _pth(path,k) )
278                     targdir.files[k] = srcdir.files[k]
279
280     # Add/update links
281     for k in srcdir.links.keys():
282         if applicable( _pth(path,k), targ ):
283             if not targdir.links.has_key(k):
284                 targdir.links[k] = srcdir.links[k]; 
285                 #print "+L+",_pth(path,k), "->", srcdir.links[k]
286                 do_symlink( targ, _pth(path,k), targdir.links[k] )
287             else:
288                 if targdir.links[k] != srcdir.links[k]:
289                     do_unlink( targ, _pth(path,k) )
290                     targdir.links[k] = srcdir.links[k]
291                     #print "*L*", _pth(path,k), "to ->", srcdir.links[k]
292                     do_symlink( targ, _pth(path,k), targdir.links[k] )
293
294     # Do dirs
295     for k in srcdir.dirs.keys():
296         if not targdir.dirs.has_key(k):
297             targdir.dirs[k] = MirrorSplitDir()
298             #print "+D+", _pth(path,k)
299         _internal_reconcile( _pth(path,k), srcdir.dirs[k],
300                              targdir.dirs[k], targ )
301
302
303 def reconcile_target_db( src, targ ):
304     _internal_reconcile( "", src.root, targ.db.root, targ )
305
306 ###############################################################################
307
308 def load_config():
309     global MASTER_PATH
310     global TREE_ROOT
311     global TREE_DB_ROOT
312     global trees
313
314     MASTER_PATH = Cnf["Mirror-Split::FTPPath"]
315     TREE_ROOT = Cnf["Mirror-Split::TreeRootPath"]
316     TREE_DB_ROOT = Cnf["Mirror-Split::TreeDatabasePath"]
317     
318     for a in Cnf.ValueList("Mirror-Split::BasicTrees"):
319         trees.append( MirrorSplitTarget( a, "%s,all" % a, 1 ) )
320
321     for n in Cnf.SubTree("Mirror-Split::CombinationTrees").List():
322         archs = Cnf.ValueList("Mirror-Split::CombinationTrees::%s" % n)
323         source = 0
324         if "source" in archs:
325             source = 1
326             archs.remove("source")
327         archs = ",".join(archs)
328         trees.append( MirrorSplitTarget( n, archs, source ) )
329
330 def do_list ():
331     print "Master path",MASTER_PATH
332     print "Trees at",TREE_ROOT
333     print "DBs at",TREE_DB_ROOT
334
335     for tree in trees:
336         print tree.name,"contains",", ".join(tree.archs),
337         if tree.source:
338             print " [source]"
339         else:
340             print ""
341         
342 def do_help ():
343     print """Usage: dak mirror-split [OPTIONS]
344 Generate hardlink trees of certain architectures
345
346   -h, --help                 show this help and exit
347   -l, --list                 list the configuration and exit
348 """
349
350
351 def main ():
352     global Cnf
353
354     Cnf = utils.get_conf()
355
356     Arguments = [('h',"help","Mirror-Split::Options::Help"),
357                  ('l',"list","Mirror-Split::Options::List"),
358                  ]
359
360     arguments = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv)
361     Cnf["Mirror-Split::Options::cake"] = ""
362     Options = Cnf.SubTree("Mirror-Split::Options")
363
364     print "Loading configuration..."
365     load_config()
366     print "Loaded."
367
368     if Options.has_key("Help"):
369         do_help()
370         return
371     if Options.has_key("List"):
372         do_list()
373         return
374     
375
376     src = MirrorSplitDB()
377     print "Scanning", MASTER_PATH
378     src.init_from_dir(MASTER_PATH)
379     print "Scanned"
380
381     for tree in trees:
382         print "Reconciling tree:",tree.name
383         reconcile_target_db( src, tree )
384         print "Saving updated DB...",
385         tree.save_db()
386         print "Done"
387     
388 ##############################################################################
389
390 if __name__ == '__main__':
391     main()