]> git.decadent.org.uk Git - dak.git/blob - lisa
more sorting fixeds and feedback when sorting large number of .changes
[dak.git] / lisa
1 #!/usr/bin/env python
2
3 # Handles NEW and BYHAND packages
4 # Copyright (C) 2001, 2002  James Troup <james@nocrew.org>
5 # $Id: lisa,v 1.13 2002-05-19 19:55:56 troup Exp $
6
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
11
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20
21 ################################################################################
22
23 # 23:12|<aj> I will not hush!
24 # 23:12|<elmo> :>
25 # 23:12|<aj> Where there is injustice in the world, I shall be there!
26 # 23:13|<aj> I shall not be silenced!
27 # 23:13|<aj> The world shall know!
28 # 23:13|<aj> The world *must* know!
29 # 23:13|<elmo> oh dear, he's gone back to powerpuff girls... ;-)
30 # 23:13|<aj> yay powerpuff girls!!
31 # 23:13|<aj> buttercup's my favourite, who's yours?
32 # 23:14|<aj> you're backing away from the keyboard right now aren't you?
33 # 23:14|<aj> *AREN'T YOU*?!
34 # 23:15|<aj> I will not be treated like this.
35 # 23:15|<aj> I shall have my revenge.
36 # 23:15|<aj> I SHALL!!!
37
38 ################################################################################
39
40 # TODO
41 # ----
42
43 # We don't check error codes very thoroughly; the old 'trust jennifer'
44 # chess nut... db_access calls in particular
45
46 # Possible TODO
47 # -------------
48
49 # Handle multiple different section/priorities (?)
50 # hardcoded debianness (debian-installer, source priority etc.) (?)
51 # Slang/ncurses interface (?)
52 # write changed sections/priority back to katie for later processing (?)
53
54 ################################################################################
55
56 import copy, errno, os, readline, string, stat, sys, tempfile;
57 import apt_pkg, apt_inst;
58 import db_access, fernanda, katie, logging, utils;
59
60 # Globals
61 lisa_version = "$Revision: 1.13 $";
62
63 Cnf = None;
64 Options = None;
65 Katie = None;
66 projectB = None;
67 Logger = None;
68
69 Priorities = None;
70 Sections = None;
71
72 ################################################################################
73 ################################################################################
74 ################################################################################
75
76 def determine_new (changes, files):
77     new = {};
78
79     # Build up a list of potentially new things
80     for file in files.keys():
81         f = files[file];
82         # Skip byhand elements
83         if f["type"] == "byhand":
84             continue;
85         pkg = f["package"];
86         priority = f["priority"];
87         section = f["section"];
88         # FIXME: unhardcode
89         if section == "non-US/main":
90             section = "non-US";
91         type = get_type(f);
92         component = f["component"];
93
94         if type == "dsc":
95             priority = "source";
96         if not new.has_key(pkg):
97             new[pkg] = {};
98             new[pkg]["priority"] = priority;
99             new[pkg]["section"] = section;
100             new[pkg]["type"] = type;
101             new[pkg]["component"] = component;
102             new[pkg]["files"] = [];
103         else:
104             old_type = new[pkg]["type"];
105             if old_type != type:
106                 # source gets trumped by deb or udeb
107                 if old_type == "dsc":
108                     new[pkg]["priority"] = priority;
109                     new[pkg]["section"] = section;
110                     new[pkg]["type"] = type;
111                     new[pkg]["component"] = component;
112         new[pkg]["files"].append(file);
113         if f.has_key("othercomponents"):
114             new[pkg]["othercomponents"] = f["othercomponents"];
115
116     for suite in changes["suite"].keys():
117         suite_id = db_access.get_suite_id(suite);
118         for pkg in new.keys():
119             component_id = db_access.get_component_id(new[pkg]["component"]);
120             type_id = db_access.get_override_type_id(new[pkg]["type"]);
121             q = projectB.query("SELECT package FROM override WHERE package = '%s' AND suite = %s AND component = %s AND type = %s" % (pkg, suite_id, component_id, type_id));
122             ql = q.getresult();
123             if ql:
124                 for file in new[pkg]["files"]:
125                     if files[file].has_key("new"):
126                         del files[file]["new"];
127                 del new[pkg];
128
129     if changes["suite"].has_key("stable"):
130         print "WARNING: overrides will be added for stable!";
131     for pkg in new.keys():
132         if new[pkg].has_key("othercomponents"):
133             print "WARNING: %s already present in %s distribution." % (pkg, new[pkg]["othercomponents"]);
134
135     return new;
136
137 ################################################################################
138
139 # Sort by 'have note', 'have source', by ctime, by source name, by
140 # source version number, and finally by filename
141
142 def changes_compare (a, b):
143     try:
144         Katie.pkg.changes_file = a;
145         Katie.init_vars();
146         Katie.update_vars();
147         a_changes = copy.copy(Katie.pkg.changes);
148     except:
149         return 1;
150
151     try:
152         Katie.pkg.changes_file = b;
153         Katie.init_vars();
154         Katie.update_vars();
155         b_changes = copy.copy(Katie.pkg.changes);
156     except:
157         return -1;
158
159     # Sort by 'have note';
160     a_has_note = a_changes.get("lisa note");
161     b_has_note = b_changes.get("lisa note");
162     if a_has_note and not b_has_note:
163         return 1;
164     elif b_has_note and not a_has_note:
165         return -1;
166
167     # Sort by 'have source'
168     a_has_source = a_changes["architecture"].get("source");
169     b_has_source = b_changes["architecture"].get("source");
170     if a_has_source and not b_has_source:
171         return -1;
172     elif b_has_source and not a_has_source:
173         return 1;
174
175     # Sort by ctime-per-source
176     a_source = a_changes.get("source");
177     b_source = b_changes.get("source");
178     if a_source != b_source:
179         a_ctime = os.stat(a)[stat.ST_CTIME];
180         b_ctime = os.stat(b)[stat.ST_CTIME];
181         q = cmp (a_ctime, b_ctime);
182         if q:
183             return q;
184
185     # Sort by source name
186     q = cmp (a_source, b_source);
187     if q:
188         return q;
189
190     # Sort by source version
191     a_version = a_changes.get("version");
192     b_version = b_changes.get("version");
193     q = apt_pkg.VersionCompare(a_version, b_version);
194     if q:
195         return -q;
196
197     # Fall back to sort by filename
198     return cmp(a, b);
199
200 ################################################################################
201
202 class Section_Completer:
203     def __init__ (self):
204         self.sections = [];
205         q = projectB.query("SELECT section FROM section");
206         for i in q.getresult():
207             self.sections.append(i[0]);
208
209     def complete(self, text, state):
210         if state == 0:
211             self.matches = [];
212             n = len(text);
213             for word in self.sections:
214                 if word[:n] == text:
215                     self.matches.append(word);
216         try:
217             return self.matches[state]
218         except IndexError:
219             return None
220
221 ############################################################
222
223 class Priority_Completer:
224     def __init__ (self):
225         self.priorities = [];
226         q = projectB.query("SELECT priority FROM priority");
227         for i in q.getresult():
228             self.priorities.append(i[0]);
229
230     def complete(self, text, state):
231         if state == 0:
232             self.matches = [];
233             n = len(text);
234             for word in self.priorities:
235                 if word[:n] == text:
236                     self.matches.append(word);
237         try:
238             return self.matches[state]
239         except IndexError:
240             return None
241
242 ################################################################################
243
244 def check_valid (new):
245     for pkg in new.keys():
246         section = new[pkg]["section"];
247         priority = new[pkg]["priority"];
248         type = new[pkg]["type"];
249         new[pkg]["section id"] = db_access.get_section_id(section);
250         new[pkg]["priority id"] = db_access.get_priority_id(new[pkg]["priority"]);
251         # Sanity checks
252         if (section == "debian-installer" and type != "udeb") or \
253            (section != "debian-installer" and type == "udeb"):
254             new[pkg]["section id"] = -1;
255         if (priority == "source" and type != "dsc") or \
256            (priority != "source" and type == "dsc"):
257             new[pkg]["priority id"] = -1;
258
259 ################################################################################
260
261 def print_new (new, indexed, file=sys.stdout):
262     check_valid(new);
263     broken = 0;
264     index = 0;
265     for pkg in new.keys():
266         index = index + 1;
267         section = new[pkg]["section"];
268         priority = new[pkg]["priority"];
269         if new[pkg]["section id"] == -1:
270             section = section + "[!]";
271             broken = 1;
272         if new[pkg]["priority id"] == -1:
273             priority = priority + "[!]";
274             broken = 1;
275         if indexed:
276             line = "(%s): %-20s %-20s %-20s" % (index, pkg, priority, section);
277         else:
278             line = "%-20s %-20s %-20s" % (pkg, priority, section);
279         line = string.strip(line)+'\n';
280         file.write(line);
281     note = Katie.pkg.changes.get("lisa note");
282     if note:
283         print "*"*75;
284         print note;
285         print "*"*75;
286     return broken, note;
287
288 ################################################################################
289
290 def get_type (f):
291     # Determine the type
292     if f.has_key("dbtype"):
293         type = f["dbtype"];
294     elif f["type"] == "orig.tar.gz" or f["type"] == "tar.gz" or f["type"] == "diff.gz" or f["type"] == "dsc":
295         type = "dsc";
296     else:
297         utils.fubar("invalid type (%s) for new.  Dazed, confused and sure as heck not continuing." % (type));
298
299     # Validate the override type
300     type_id = db_access.get_override_type_id(type);
301     if type_id == -1:
302         utils.fubar("invalid type (%s) for new.  Say wha?" % (type));
303
304     return type;
305
306 ################################################################################
307
308 def index_range (index):
309     if index == 1:
310         return "1";
311     else:
312         return "1-%s" % (index);
313
314 ################################################################################
315 ################################################################################
316
317 def edit_new (new):
318     # Write the current data to a temporary file
319     temp_filename = tempfile.mktemp();
320     fd = os.open(temp_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0700);
321     os.close(fd);
322     temp_file = utils.open_file(temp_filename, 'w');
323     print_new (new, 0, temp_file);
324     temp_file.close();
325     # Spawn an editor on that file
326     editor = os.environ.get("EDITOR","vi")
327     result = os.system("%s %s" % (editor, temp_filename))
328     if result != 0:
329         utils.fubar ("%s invocation failed for %s." % (editor, temp_filename), result)
330     # Read the edited data back in
331     temp_file = utils.open_file(temp_filename);
332     lines = temp_file.readlines();
333     temp_file.close();
334     os.unlink(temp_filename);
335     # Parse the new data
336     for line in lines:
337         line = string.strip(line[:-1]);
338         if line == "":
339             continue;
340         s = string.split(line);
341         # Pad the list if necessary
342         s[len(s):3] = [None] * (3-len(s));
343         (pkg, priority, section) = s[:3];
344         if not new.has_key(pkg):
345             utils.warn("Ignoring unknown package '%s'" % (pkg));
346         else:
347             # Strip off any invalid markers, print_new will readd them.
348             if section[-3:] == "[!]":
349                 section = section[:-3];
350             if priority[-3:] == "[!]":
351                 priority = priority[:-3];
352             for file in new[pkg]["files"]:
353                 Katie.pkg.files[file]["section"] = section;
354                 Katie.pkg.files[file]["priority"] = priority;
355             new[pkg]["section"] = section;
356             new[pkg]["priority"] = priority;
357
358 ################################################################################
359
360 def edit_index (new, index):
361     priority = new[index]["priority"]
362     section = new[index]["section"]
363     type = new[index]["type"];
364     done = 0
365     while not done:
366         print string.join([index, priority, section], '\t');
367
368         answer = "XXX";
369         if type != "dsc":
370             prompt = "[B]oth, Priority, Section, Done ? ";
371         else:
372             prompt = "[S]ection, Done ? ";
373         edit_priority = edit_section = 0;
374
375         while string.find(prompt, answer) == -1:
376             answer = utils.our_raw_input(prompt);
377             m = katie.re_default_answer.match(prompt)
378             if answer == "":
379                 answer = m.group(1)
380             answer = string.upper(answer[:1])
381
382         if answer == 'P':
383             edit_priority = 1;
384         elif answer == 'S':
385             edit_section = 1;
386         elif answer == 'B':
387             edit_priority = edit_section = 1;
388         elif answer == 'D':
389             done = 1;
390
391         # Edit the priority
392         if edit_priority:
393             readline.set_completer(Priorities.complete);
394             got_priority = 0;
395             while not got_priority:
396                 new_priority = string.strip(utils.our_raw_input("New priority: "));
397                 if Priorities.priorities.count(new_priority) == 0:
398                     print "E: '%s' is not a valid priority, try again." % (new_priority);
399                 else:
400                     got_priority = 1;
401                     priority = new_priority;
402
403         # Edit the section
404         if edit_section:
405             readline.set_completer(Sections.complete);
406             got_section = 0;
407             while not got_section:
408                 new_section = string.strip(utils.our_raw_input("New section: "));
409                 if Sections.sections.count(new_section) == 0:
410                     print "E: '%s' is not a valid section, try again." % (new_section);
411                 else:
412                     got_section = 1;
413                     section = new_section;
414
415         # Reset the readline completer
416         readline.set_completer(None);
417
418     for file in new[index]["files"]:
419         Katie.pkg.files[file]["section"] = section;
420         Katie.pkg.files[file]["priority"] = priority;
421     new[index]["priority"] = priority;
422     new[index]["section"] = section;
423     return new;
424
425 ################################################################################
426
427 def edit_overrides (new):
428     print;
429     done = 0
430     while not done:
431         print_new (new, 1);
432         new_index = {};
433         index = 0;
434         for i in new.keys():
435             index = index + 1;
436             new_index[index] = i;
437
438         prompt = "(%s) edit override <n>, Editor, Done ? " % (index_range(index));
439
440         got_answer = 0
441         while not got_answer:
442             answer = utils.our_raw_input(prompt);
443             answer = string.upper(answer[:1]);
444             if answer == "E" or answer == "D":
445                 got_answer = 1;
446             elif katie.re_isanum.match (answer):
447                 answer = int(answer);
448                 if (answer < 1) or (answer > index):
449                     print "%s is not a valid index (%s).  Please retry." % (index_range(index), answer);
450                 else:
451                     got_answer = 1;
452
453         if answer == 'E':
454             edit_new(new);
455         elif answer == 'D':
456             done = 1;
457         else:
458             edit_index (new, new_index[answer]);
459
460     return new;
461
462 ################################################################################
463
464 def edit_note(note):
465     # Write the current data to a temporary file
466     temp_filename = tempfile.mktemp();
467     fd = os.open(temp_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0700);
468     os.close(fd);
469     temp_file = utils.open_file(temp_filename, 'w');
470     temp_file.write(note);
471     temp_file.close();
472     editor = os.environ.get("EDITOR","vi")
473     answer = 'E';
474     while answer == 'E':
475         os.system("%s %s" % (editor, temp_filename))
476         temp_file = utils.open_file(temp_filename);
477         note = string.rstrip(temp_file.read());
478         temp_file.close();
479         print "Note:";
480         print utils.prefix_multi_line_string(note,"  ");
481         prompt = "[D]one, Edit, Abandon, Quit ?"
482         answer = "XXX";
483         while string.find(prompt, answer) == -1:
484             answer = utils.our_raw_input(prompt);
485             m = katie.re_default_answer.search(prompt);
486             if answer == "":
487                 answer = m.group(1);
488             answer = string.upper(answer[:1]);
489     os.unlink(temp_filename);
490     if answer == 'A':
491         return;
492     elif answer == 'Q':
493         sys.exit(0);
494     Katie.pkg.changes["lisa note"] = note;
495     Katie.dump_vars(Cnf["Dir::Queue::New"]);
496
497 ################################################################################
498
499 def check_pkg ():
500     try:
501         less_fd = os.popen("less -", 'w', 0);
502         stdout_fd = sys.stdout;
503         try:
504             sys.stdout = less_fd;
505             fernanda.display_changes(Katie.pkg.changes_file);
506             files = Katie.pkg.files;
507             for file in files.keys():
508                 if files[file].has_key("new"):
509                     type = files[file]["type"];
510                     if type == "deb":
511                         fernanda.check_deb(file);
512                     elif type == "dsc":
513                         fernanda.check_dsc(file);
514         finally:
515             sys.stdout = stdout_fd;
516     except IOError, e:
517         if errno.errorcode[e.errno] == 'EPIPE':
518             utils.warn("[fernanda] Caught EPIPE; skipping.");
519             pass;
520         else:
521             raise;
522     except KeyboardInterrupt:
523         utils.warn("[fernanda] Caught C-c; skipping.");
524         pass;
525
526 ################################################################################
527
528 ## FIXME: horribly Debian specific
529
530 def do_bxa_notification():
531     files = Katie.pkg.files;
532     summary = "";
533     for file in files.keys():
534         if files[file]["type"] == "deb":
535             control = apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file)));
536             summary = summary + "\n";
537             summary = summary + "Package: %s\n" % (control.Find("Package"));
538             summary = summary + "Description: %s\n" % (control.Find("Description"));
539     Katie.Subst["__BINARY_DESCRIPTIONS__"] = summary;
540     bxa_mail = utils.TemplateSubst(Katie.Subst,Cnf["Dir::Templates"]+"/lisa.bxa_notification");
541     utils.send_mail(bxa_mail,"");
542
543 ################################################################################
544
545 def add_overrides (new):
546     changes = Katie.pkg.changes;
547     files = Katie.pkg.files;
548
549     projectB.query("BEGIN WORK");
550     for suite in changes["suite"].keys():
551         suite_id = db_access.get_suite_id(suite);
552         for pkg in new.keys():
553             component_id = db_access.get_component_id(new[pkg]["component"]);
554             type_id = db_access.get_override_type_id(new[pkg]["type"]);
555             priority_id = new[pkg]["priority id"];
556             section_id = new[pkg]["section id"];
557             projectB.query("INSERT INTO override (suite, component, type, package, priority, section) VALUES (%s, %s, %s, '%s', %s, %s)" % (suite_id, component_id, type_id, pkg, priority_id, section_id));
558             for file in new[pkg]["files"]:
559                 if files[file].has_key("new"):
560                     del files[file]["new"];
561             del new[pkg];
562
563     projectB.query("COMMIT WORK");
564
565     if Cnf.FindB("Dinstall::BXANotify"):
566         do_bxa_notification();
567
568 ################################################################################
569
570 def do_new():
571     print "NEW\n";
572     files = Katie.pkg.files;
573     changes = Katie.pkg.changes;
574
575     # Make a copy of distribution we can happily trample on
576     changes["suite"] = copy.copy(changes["distribution"]);
577
578     # Fix up the list of target suites
579     for suite in changes["suite"].keys():
580         override = Cnf.Find("Suite::%s::OverrideSuite" % (suite));
581         if override:
582             del changes["suite"][suite];
583             changes["suite"][override] = 1;
584     # Validate suites
585     for suite in changes["suite"].keys():
586         suite_id = db_access.get_suite_id(suite);
587         if suite_id == -1:
588             utils.fubar("%s has invalid suite '%s' (possibly overriden).  say wha?" % (changes, suite));
589
590     # The main NEW processing loop
591     done = 0;
592     while not done:
593         # Find out what's new
594         new = determine_new(changes, files);
595
596         if not new:
597             break;
598
599         answer = "XXX";
600         if Options["No-Action"] or Options["Automatic"]:
601             answer = 'S';
602
603         (broken, note) = print_new(new, 0);
604         prompt = "";
605
606         if not broken and not note:
607             prompt = "Add overrides, ";
608         if broken:
609             print "W: [!] marked entries must be fixed before package can be processed.";
610         if note:
611             print "W: note must be removed before package can be processed.";
612             prompt = prompt + "Remove note, ";
613
614         prompt = prompt + "Edit overrides, Check, Manual reject, Note edit, [S]kip, Quit ?";
615
616         while string.find(prompt, answer) == -1:
617             answer = utils.our_raw_input(prompt);
618             m = katie.re_default_answer.search(prompt);
619             if answer == "":
620                 answer = m.group(1)
621             answer = string.upper(answer[:1])
622
623         if answer == 'A':
624             done = add_overrides (new);
625         elif answer == 'C':
626             check_pkg();
627         elif answer == 'E':
628             new = edit_overrides (new);
629         elif answer == 'M':
630             aborted = Katie.do_reject(1, Options["Manual-Reject"]);
631             if not aborted:
632                 os.unlink(Katie.pkg.changes_file[:-8]+".katie");
633                 done = 1;
634         elif answer == 'N':
635             edit_note(changes.get("lisa note", ""));
636         elif answer == 'R':
637             confirm = string.lower(utils.our_raw_input("Really clear note (y/N)? "));
638             if confirm == "y":
639                 del changes["lisa note"];
640         elif answer == 'S':
641             done = 1;
642         elif answer == 'Q':
643             sys.exit(0)
644
645 ################################################################################
646 ################################################################################
647 ################################################################################
648
649 def usage (exit_code=0):
650     print """Usage: lisa [OPTION]... [CHANGES]...
651   -a, --automatic           automatic run
652   -h, --help                show this help and exit.
653   -m, --manual-reject=MSG   manual reject with `msg'
654   -n, --no-action           don't do anything
655   -V, --version             display the version number and exit"""
656     sys.exit(exit_code)
657
658 ################################################################################
659
660 def init():
661     global Cnf, Options, Logger, Katie, projectB, Sections, Priorities;
662
663     Cnf = utils.get_conf();
664
665     Arguments = [('a',"automatic","Lisa::Options::Automatic"),
666                  ('h',"help","Lisa::Options::Help"),
667                  ('m',"manual-reject","Lisa::Options::Manual-Reject", "HasArg"),
668                  ('n',"no-action","Lisa::Options::No-Action"),
669                  ('V',"version","Lisa::Options::Version")];
670
671     for i in ["automatic", "help", "manual-reject", "no-action", "version"]:
672         if not Cnf.has_key("Lisa::Options::%s" % (i)):
673             Cnf["Lisa::Options::%s" % (i)] = "";
674
675     changes_files = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv);
676     Options = Cnf.SubTree("Lisa::Options")
677
678     if Options["Help"]:
679         usage();
680
681     if Options["Version"]:
682         print "lisa %s" % (lisa_version);
683         sys.exit(0);
684
685     Katie = katie.Katie(Cnf);
686
687     if not Options["No-Action"]:
688         Logger = Katie.Logger = logging.Logger(Cnf, "lisa");
689
690     projectB = Katie.projectB;
691
692     Sections = Section_Completer();
693     Priorities = Priority_Completer();
694     readline.parse_and_bind("tab: complete");
695
696     return changes_files;
697
698 ################################################################################
699
700 def do_byhand():
701     done = 0;
702     while not done:
703         files = Katie.pkg.files;
704         will_install = 1;
705         byhand = [];
706
707         for file in files.keys():
708             if files[file]["type"] == "byhand":
709                 if os.path.exists(file):
710                     print "W: %s still present; please process byhand components and try again." % (file);
711                     will_install = 0;
712                 else:
713                     byhand.append(file);
714
715         answer = "XXXX";
716         if Options["No-Action"]:
717             answer = "S";
718         if will_install:
719             if Options["Automatic"] and not Options["No-Action"]:
720                 answer = 'A';
721             prompt = "[A]ccept, Manual reject, Skip, Quit ?";
722         else:
723             prompt = "Manual reject, [S]kip, Quit ?";
724
725         while string.find(prompt, answer) == -1:
726             answer = utils.our_raw_input(prompt);
727             m = katie.re_default_answer.search(prompt);
728             if answer == "":
729                 answer = m.group(1);
730             answer = string.upper(answer[:1]);
731
732         if answer == 'A':
733             done = 1;
734             for file in byhand:
735                 del files[file];
736         elif answer == 'M':
737             Katie.do_reject(1, Options["Manual-Reject"]);
738             os.unlink(Katie.pkg.changes_file[:-8]+".katie");
739             done = 1;
740         elif answer == 'S':
741             done = 1;
742         elif answer == 'Q':
743             sys.exit(0);
744
745 ################################################################################
746
747 def do_accept():
748     print "ACCEPT";
749     if not Options["No-Action"]:
750         (summary, short_summary) = Katie.build_summaries();
751         Katie.accept(summary, short_summary);
752         os.unlink(Katie.pkg.changes_file[:-8]+".katie");
753
754 def check_status(files):
755     new = byhand = 0;
756     for file in files.keys():
757         if files[file]["type"] == "byhand":
758             byhand = 1;
759         elif files[file].has_key("new"):
760             new = 1;
761     return (new, byhand);
762
763 def do_pkg(changes_file):
764     Katie.pkg.changes_file = changes_file;
765     Katie.init_vars();
766     Katie.update_vars();
767     Katie.update_subst();
768     files = Katie.pkg.files;
769
770     (new, byhand) = check_status(files);
771     if new or byhand:
772         if new:
773             do_new();
774         if byhand:
775             do_byhand();
776         (new, byhand) = check_status(files);
777
778     if not new and not byhand:
779         do_accept();
780
781 ################################################################################
782
783 def end():
784     accept_count = Katie.accept_count;
785     accept_bytes = Katie.accept_bytes;
786
787     if accept_count:
788         sets = "set"
789         if accept_count > 1:
790             sets = "sets"
791         sys.stderr.write("Accepted %d package %s, %s.\n" % (accept_count, sets, utils.size_type(int(accept_bytes))));
792         Logger.log(["total",accept_count,accept_bytes]);
793
794     if not Options["No-Action"]:
795         Logger.close();
796
797 ################################################################################
798
799 def main():
800     changes_files = init();
801     if len(changes_files) > 50:
802         sys.stderr.write("Sorting changes...\n");
803     changes_files.sort(changes_compare);
804
805     # Kill me now? **FIXME**
806     Cnf["Dinstall::Options::No-Mail"] = "";
807     bcc = "X-Katie: %s" % (lisa_version);
808     if Cnf.has_key("Dinstall::Bcc"):
809         Katie.Subst["__BCC__"] = bcc + "\nBcc: %s" % (Cnf["Dinstall::Bcc"]);
810     else:
811         Katie.Subst["__BCC__"] = bcc;
812
813     for changes_file in changes_files:
814         changes_file = utils.validate_changes_file_arg(changes_file, 0);
815         if not changes_file:
816             continue;
817         print "\n" + changes_file;
818         do_pkg (changes_file);
819
820     end();
821
822 ################################################################################
823
824 if __name__ == '__main__':
825     main()