]> git.decadent.org.uk Git - dak.git/blob - lisa
fix changes_compare for real. fix broken prompt after note adding
[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.12 2002-05-19 02:00:48 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.12 $";
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         a_changes = utils.parse_changes(a);
145     except:
146         return 1;
147
148     try:
149         b_changes = utils.parse_changes(b);
150     except:
151         return -1;
152
153     utils.cc_fix_changes (a_changes);
154     utils.cc_fix_changes (b_changes);
155
156     # Sort by 'have note';
157     a_has_note = a_changes.get("lisa note");
158     b_has_note = b_changes.get("lisa note");
159     if a_has_note and not b_has_note:
160         return -1;
161     elif b_has_note and not a_has_note:
162         return 1;
163
164     # Sort by 'have source'
165     a_has_source = a_changes["architecture"].get("source");
166     b_has_source = b_changes["architecture"].get("source");
167     if a_has_source and not b_has_source:
168         return -1;
169     elif b_has_source and not a_has_source:
170         return 1;
171
172     # Sort by ctime-per-source
173     a_source = a_changes.get("source");
174     b_source = b_changes.get("source");
175     if a_source != b_source:
176         a_ctime = os.stat(a)[stat.ST_CTIME];
177         b_ctime = os.stat(b)[stat.ST_CTIME];
178         q = cmp (a_ctime, b_ctime);
179         if q:
180             return q;
181
182     # Sort by source name
183     q = cmp (a_source, b_source);
184     if q:
185         return q;
186
187     # Sort by source version
188     a_version = a_changes.get("version");
189     b_version = b_changes.get("version");
190     q = apt_pkg.VersionCompare(a_version, b_version);
191     if q:
192         return -q;
193
194     # Fall back to sort by filename
195     return cmp(a, b);
196
197 ################################################################################
198
199 class Section_Completer:
200     def __init__ (self):
201         self.sections = [];
202         q = projectB.query("SELECT section FROM section");
203         for i in q.getresult():
204             self.sections.append(i[0]);
205
206     def complete(self, text, state):
207         if state == 0:
208             self.matches = [];
209             n = len(text);
210             for word in self.sections:
211                 if word[:n] == text:
212                     self.matches.append(word);
213         try:
214             return self.matches[state]
215         except IndexError:
216             return None
217
218 ############################################################
219
220 class Priority_Completer:
221     def __init__ (self):
222         self.priorities = [];
223         q = projectB.query("SELECT priority FROM priority");
224         for i in q.getresult():
225             self.priorities.append(i[0]);
226
227     def complete(self, text, state):
228         if state == 0:
229             self.matches = [];
230             n = len(text);
231             for word in self.priorities:
232                 if word[:n] == text:
233                     self.matches.append(word);
234         try:
235             return self.matches[state]
236         except IndexError:
237             return None
238
239 ################################################################################
240
241 def check_valid (new):
242     for pkg in new.keys():
243         section = new[pkg]["section"];
244         priority = new[pkg]["priority"];
245         type = new[pkg]["type"];
246         new[pkg]["section id"] = db_access.get_section_id(section);
247         new[pkg]["priority id"] = db_access.get_priority_id(new[pkg]["priority"]);
248         # Sanity checks
249         if (section == "debian-installer" and type != "udeb") or \
250            (section != "debian-installer" and type == "udeb"):
251             new[pkg]["section id"] = -1;
252         if (priority == "source" and type != "dsc") or \
253            (priority != "source" and type == "dsc"):
254             new[pkg]["priority id"] = -1;
255
256 ################################################################################
257
258 def print_new (new, indexed, file=sys.stdout):
259     check_valid(new);
260     broken = 0;
261     index = 0;
262     for pkg in new.keys():
263         index = index + 1;
264         section = new[pkg]["section"];
265         priority = new[pkg]["priority"];
266         if new[pkg]["section id"] == -1:
267             section = section + "[!]";
268             broken = 1;
269         if new[pkg]["priority id"] == -1:
270             priority = priority + "[!]";
271             broken = 1;
272         if indexed:
273             line = "(%s): %-20s %-20s %-20s" % (index, pkg, priority, section);
274         else:
275             line = "%-20s %-20s %-20s" % (pkg, priority, section);
276         line = string.strip(line)+'\n';
277         file.write(line);
278     note = Katie.pkg.changes.get("lisa note");
279     if note:
280         print "*"*75;
281         print note;
282         print "*"*75;
283     return broken, note;
284
285 ################################################################################
286
287 def get_type (f):
288     # Determine the type
289     if f.has_key("dbtype"):
290         type = f["dbtype"];
291     elif f["type"] == "orig.tar.gz" or f["type"] == "tar.gz" or f["type"] == "diff.gz" or f["type"] == "dsc":
292         type = "dsc";
293     else:
294         utils.fubar("invalid type (%s) for new.  Dazed, confused and sure as heck not continuing." % (type));
295
296     # Validate the override type
297     type_id = db_access.get_override_type_id(type);
298     if type_id == -1:
299         utils.fubar("invalid type (%s) for new.  Say wha?" % (type));
300
301     return type;
302
303 ################################################################################
304
305 def index_range (index):
306     if index == 1:
307         return "1";
308     else:
309         return "1-%s" % (index);
310
311 ################################################################################
312 ################################################################################
313
314 def edit_new (new):
315     # Write the current data to a temporary file
316     temp_filename = tempfile.mktemp();
317     fd = os.open(temp_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0700);
318     os.close(fd);
319     temp_file = utils.open_file(temp_filename, 'w');
320     print_new (new, 0, temp_file);
321     temp_file.close();
322     # Spawn an editor on that file
323     editor = os.environ.get("EDITOR","vi")
324     result = os.system("%s %s" % (editor, temp_filename))
325     if result != 0:
326         utils.fubar ("%s invocation failed for %s." % (editor, temp_filename), result)
327     # Read the edited data back in
328     temp_file = utils.open_file(temp_filename);
329     lines = temp_file.readlines();
330     temp_file.close();
331     os.unlink(temp_filename);
332     # Parse the new data
333     for line in lines:
334         line = string.strip(line[:-1]);
335         if line == "":
336             continue;
337         s = string.split(line);
338         # Pad the list if necessary
339         s[len(s):3] = [None] * (3-len(s));
340         (pkg, priority, section) = s[:3];
341         if not new.has_key(pkg):
342             utils.warn("Ignoring unknown package '%s'" % (pkg));
343         else:
344             # Strip off any invalid markers, print_new will readd them.
345             if section[-3:] == "[!]":
346                 section = section[:-3];
347             if priority[-3:] == "[!]":
348                 priority = priority[:-3];
349             for file in new[pkg]["files"]:
350                 Katie.pkg.files[file]["section"] = section;
351                 Katie.pkg.files[file]["priority"] = priority;
352             new[pkg]["section"] = section;
353             new[pkg]["priority"] = priority;
354
355 ################################################################################
356
357 def edit_index (new, index):
358     priority = new[index]["priority"]
359     section = new[index]["section"]
360     type = new[index]["type"];
361     done = 0
362     while not done:
363         print string.join([index, priority, section], '\t');
364
365         answer = "XXX";
366         if type != "dsc":
367             prompt = "[B]oth, Priority, Section, Done ? ";
368         else:
369             prompt = "[S]ection, Done ? ";
370         edit_priority = edit_section = 0;
371
372         while string.find(prompt, answer) == -1:
373             answer = utils.our_raw_input(prompt);
374             m = katie.re_default_answer.match(prompt)
375             if answer == "":
376                 answer = m.group(1)
377             answer = string.upper(answer[:1])
378
379         if answer == 'P':
380             edit_priority = 1;
381         elif answer == 'S':
382             edit_section = 1;
383         elif answer == 'B':
384             edit_priority = edit_section = 1;
385         elif answer == 'D':
386             done = 1;
387
388         # Edit the priority
389         if edit_priority:
390             readline.set_completer(Priorities.complete);
391             got_priority = 0;
392             while not got_priority:
393                 new_priority = string.strip(utils.our_raw_input("New priority: "));
394                 if Priorities.priorities.count(new_priority) == 0:
395                     print "E: '%s' is not a valid priority, try again." % (new_priority);
396                 else:
397                     got_priority = 1;
398                     priority = new_priority;
399
400         # Edit the section
401         if edit_section:
402             readline.set_completer(Sections.complete);
403             got_section = 0;
404             while not got_section:
405                 new_section = string.strip(utils.our_raw_input("New section: "));
406                 if Sections.sections.count(new_section) == 0:
407                     print "E: '%s' is not a valid section, try again." % (new_section);
408                 else:
409                     got_section = 1;
410                     section = new_section;
411
412         # Reset the readline completer
413         readline.set_completer(None);
414
415     for file in new[index]["files"]:
416         Katie.pkg.files[file]["section"] = section;
417         Katie.pkg.files[file]["priority"] = priority;
418     new[index]["priority"] = priority;
419     new[index]["section"] = section;
420     return new;
421
422 ################################################################################
423
424 def edit_overrides (new):
425     print;
426     done = 0
427     while not done:
428         print_new (new, 1);
429         new_index = {};
430         index = 0;
431         for i in new.keys():
432             index = index + 1;
433             new_index[index] = i;
434
435         prompt = "(%s) edit override <n>, Editor, Done ? " % (index_range(index));
436
437         got_answer = 0
438         while not got_answer:
439             answer = utils.our_raw_input(prompt);
440             answer = string.upper(answer[:1]);
441             if answer == "E" or answer == "D":
442                 got_answer = 1;
443             elif katie.re_isanum.match (answer):
444                 answer = int(answer);
445                 if (answer < 1) or (answer > index):
446                     print "%s is not a valid index (%s).  Please retry." % (index_range(index), answer);
447                 else:
448                     got_answer = 1;
449
450         if answer == 'E':
451             edit_new(new);
452         elif answer == 'D':
453             done = 1;
454         else:
455             edit_index (new, new_index[answer]);
456
457     return new;
458
459 ################################################################################
460
461 def edit_note(note):
462     # Write the current data to a temporary file
463     temp_filename = tempfile.mktemp();
464     fd = os.open(temp_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0700);
465     os.close(fd);
466     temp_file = utils.open_file(temp_filename, 'w');
467     temp_file.write(note);
468     temp_file.close();
469     editor = os.environ.get("EDITOR","vi")
470     answer = 'E';
471     while answer == 'E':
472         os.system("%s %s" % (editor, temp_filename))
473         temp_file = utils.open_file(temp_filename);
474         note = string.rstrip(temp_file.read());
475         temp_file.close();
476         print "Note:";
477         print utils.prefix_multi_line_string(note,"  ");
478         prompt = "[D]one, Edit, Abandon, Quit ?"
479         answer = "XXX";
480         while string.find(prompt, answer) == -1:
481             answer = utils.our_raw_input(prompt);
482             m = katie.re_default_answer.search(prompt);
483             if answer == "":
484                 answer = m.group(1);
485             answer = string.upper(answer[:1]);
486     os.unlink(temp_filename);
487     if answer == 'A':
488         return;
489     elif answer == 'Q':
490         sys.exit(0);
491     Katie.pkg.changes["lisa note"] = note;
492     Katie.dump_vars(Cnf["Dir::Queue::New"]);
493
494 ################################################################################
495
496 def check_pkg ():
497     try:
498         less_fd = os.popen("less -", 'w', 0);
499         stdout_fd = sys.stdout;
500         try:
501             sys.stdout = less_fd;
502             fernanda.display_changes(Katie.pkg.changes_file);
503             files = Katie.pkg.files;
504             for file in files.keys():
505                 if files[file].has_key("new"):
506                     type = files[file]["type"];
507                     if type == "deb":
508                         fernanda.check_deb(file);
509                     elif type == "dsc":
510                         fernanda.check_dsc(file);
511         finally:
512             sys.stdout = stdout_fd;
513     except IOError, e:
514         if errno.errorcode[e.errno] == 'EPIPE':
515             utils.warn("[fernanda] Caught EPIPE; skipping.");
516             pass;
517         else:
518             raise;
519     except KeyboardInterrupt:
520         utils.warn("[fernanda] Caught C-c; skipping.");
521         pass;
522
523 ################################################################################
524
525 ## FIXME: horribly Debian specific
526
527 def do_bxa_notification():
528     files = Katie.pkg.files;
529     summary = "";
530     for file in files.keys():
531         if files[file]["type"] == "deb":
532             control = apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file)));
533             summary = summary + "\n";
534             summary = summary + "Package: %s\n" % (control.Find("Package"));
535             summary = summary + "Description: %s\n" % (control.Find("Description"));
536     Katie.Subst["__BINARY_DESCRIPTIONS__"] = summary;
537     bxa_mail = utils.TemplateSubst(Katie.Subst,Cnf["Dir::Templates"]+"/lisa.bxa_notification");
538     utils.send_mail(bxa_mail,"");
539
540 ################################################################################
541
542 def add_overrides (new):
543     changes = Katie.pkg.changes;
544     files = Katie.pkg.files;
545
546     projectB.query("BEGIN WORK");
547     for suite in changes["suite"].keys():
548         suite_id = db_access.get_suite_id(suite);
549         for pkg in new.keys():
550             component_id = db_access.get_component_id(new[pkg]["component"]);
551             type_id = db_access.get_override_type_id(new[pkg]["type"]);
552             priority_id = new[pkg]["priority id"];
553             section_id = new[pkg]["section id"];
554             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));
555             for file in new[pkg]["files"]:
556                 if files[file].has_key("new"):
557                     del files[file]["new"];
558             del new[pkg];
559
560     projectB.query("COMMIT WORK");
561
562     if Cnf.FindB("Dinstall::BXANotify"):
563         do_bxa_notification();
564
565 ################################################################################
566
567 def do_new():
568     print "NEW\n";
569     files = Katie.pkg.files;
570     changes = Katie.pkg.changes;
571
572     # Make a copy of distribution we can happily trample on
573     changes["suite"] = copy.copy(changes["distribution"]);
574
575     # Fix up the list of target suites
576     for suite in changes["suite"].keys():
577         override = Cnf.Find("Suite::%s::OverrideSuite" % (suite));
578         if override:
579             del changes["suite"][suite];
580             changes["suite"][override] = 1;
581     # Validate suites
582     for suite in changes["suite"].keys():
583         suite_id = db_access.get_suite_id(suite);
584         if suite_id == -1:
585             utils.fubar("%s has invalid suite '%s' (possibly overriden).  say wha?" % (changes, suite));
586
587     # The main NEW processing loop
588     done = 0;
589     while not done:
590         # Find out what's new
591         new = determine_new(changes, files);
592
593         if not new:
594             break;
595
596         answer = "XXX";
597         if Options["No-Action"] or Options["Automatic"]:
598             answer = 'S';
599
600         (broken, note) = print_new(new, 0);
601         prompt = "";
602
603         if not broken and not note:
604             prompt = "Add overrides, ";
605         if broken:
606             print "W: [!] marked entries must be fixed before package can be processed.";
607         if note:
608             print "W: note must be removed before package can be processed.";
609             prompt = prompt + "Remove note, ";
610
611         prompt = prompt + "Edit overrides, Check, Manual reject, Note edit, [S]kip, Quit ?";
612
613         while string.find(prompt, answer) == -1:
614             answer = utils.our_raw_input(prompt);
615             m = katie.re_default_answer.search(prompt);
616             if answer == "":
617                 answer = m.group(1)
618             answer = string.upper(answer[:1])
619
620         if answer == 'A':
621             done = add_overrides (new);
622         elif answer == 'C':
623             check_pkg();
624         elif answer == 'E':
625             new = edit_overrides (new);
626         elif answer == 'M':
627             aborted = Katie.do_reject(1, Options["Manual-Reject"]);
628             if not aborted:
629                 os.unlink(Katie.pkg.changes_file[:-8]+".katie");
630                 done = 1;
631         elif answer == 'N':
632             edit_note(changes.get("lisa note", ""));
633         elif answer == 'R':
634             confirm = string.lower(utils.our_raw_input("Really clear note (y/N)? "));
635             if confirm == "y":
636                 del changes["lisa note"];
637         elif answer == 'S':
638             done = 1;
639         elif answer == 'Q':
640             sys.exit(0)
641
642 ################################################################################
643 ################################################################################
644 ################################################################################
645
646 def usage (exit_code=0):
647     print """Usage: lisa [OPTION]... [CHANGES]...
648   -a, --automatic           automatic run
649   -h, --help                show this help and exit.
650   -m, --manual-reject=MSG   manual reject with `msg'
651   -n, --no-action           don't do anything
652   -V, --version             display the version number and exit"""
653     sys.exit(exit_code)
654
655 ################################################################################
656
657 def init():
658     global Cnf, Options, Logger, Katie, projectB, Sections, Priorities;
659
660     Cnf = utils.get_conf();
661
662     Arguments = [('a',"automatic","Lisa::Options::Automatic"),
663                  ('h',"help","Lisa::Options::Help"),
664                  ('m',"manual-reject","Lisa::Options::Manual-Reject", "HasArg"),
665                  ('n',"no-action","Lisa::Options::No-Action"),
666                  ('V',"version","Lisa::Options::Version")];
667
668     for i in ["automatic", "help", "manual-reject", "no-action", "version"]:
669         if not Cnf.has_key("Lisa::Options::%s" % (i)):
670             Cnf["Lisa::Options::%s" % (i)] = "";
671
672     changes_files = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv);
673     Options = Cnf.SubTree("Lisa::Options")
674
675     if Options["Help"]:
676         usage();
677
678     if Options["Version"]:
679         print "lisa %s" % (lisa_version);
680         sys.exit(0);
681
682     Katie = katie.Katie(Cnf);
683
684     if not Options["No-Action"]:
685         Logger = Katie.Logger = logging.Logger(Cnf, "lisa");
686
687     projectB = Katie.projectB;
688
689     Sections = Section_Completer();
690     Priorities = Priority_Completer();
691     readline.parse_and_bind("tab: complete");
692
693     return changes_files;
694
695 ################################################################################
696
697 def do_byhand():
698     done = 0;
699     while not done:
700         files = Katie.pkg.files;
701         will_install = 1;
702         byhand = [];
703
704         for file in files.keys():
705             if files[file]["type"] == "byhand":
706                 if os.path.exists(file):
707                     print "W: %s still present; please process byhand components and try again." % (file);
708                     will_install = 0;
709                 else:
710                     byhand.append(file);
711
712         answer = "XXXX";
713         if Options["No-Action"]:
714             answer = "S";
715         if will_install:
716             if Options["Automatic"] and not Options["No-Action"]:
717                 answer = 'A';
718             prompt = "[A]ccept, Manual reject, Skip, Quit ?";
719         else:
720             prompt = "Manual reject, [S]kip, Quit ?";
721
722         while string.find(prompt, answer) == -1:
723             answer = utils.our_raw_input(prompt);
724             m = katie.re_default_answer.search(prompt);
725             if answer == "":
726                 answer = m.group(1);
727             answer = string.upper(answer[:1]);
728
729         if answer == 'A':
730             done = 1;
731             for file in byhand:
732                 del files[file];
733         elif answer == 'M':
734             Katie.do_reject(1, Options["Manual-Reject"]);
735             os.unlink(Katie.pkg.changes_file[:-8]+".katie");
736             done = 1;
737         elif answer == 'S':
738             done = 1;
739         elif answer == 'Q':
740             sys.exit(0);
741
742 ################################################################################
743
744 def do_accept():
745     print "ACCEPT";
746     if not Options["No-Action"]:
747         (summary, short_summary) = Katie.build_summaries();
748         Katie.accept(summary, short_summary);
749         os.unlink(Katie.pkg.changes_file[:-8]+".katie");
750
751 def check_status(files):
752     new = byhand = 0;
753     for file in files.keys():
754         if files[file]["type"] == "byhand":
755             byhand = 1;
756         elif files[file].has_key("new"):
757             new = 1;
758     return (new, byhand);
759
760 def do_pkg(changes_file):
761     Katie.pkg.changes_file = changes_file;
762     Katie.init_vars();
763     Katie.update_vars();
764     Katie.update_subst();
765     files = Katie.pkg.files;
766
767     (new, byhand) = check_status(files);
768     if new or byhand:
769         if new:
770             do_new();
771         if byhand:
772             do_byhand();
773         (new, byhand) = check_status(files);
774
775     if not new and not byhand:
776         do_accept();
777
778 ################################################################################
779
780 def end():
781     accept_count = Katie.accept_count;
782     accept_bytes = Katie.accept_bytes;
783
784     if accept_count:
785         sets = "set"
786         if accept_count > 1:
787             sets = "sets"
788         sys.stderr.write("Accepted %d package %s, %s.\n" % (accept_count, sets, utils.size_type(int(accept_bytes))));
789         Logger.log(["total",accept_count,accept_bytes]);
790
791     if not Options["No-Action"]:
792         Logger.close();
793
794 ################################################################################
795
796 def main():
797     changes_files = init();
798     changes_files.sort(changes_compare);
799
800     # Kill me now? **FIXME**
801     Cnf["Dinstall::Options::No-Mail"] = "";
802     bcc = "X-Katie: %s" % (lisa_version);
803     if Cnf.has_key("Dinstall::Bcc"):
804         Katie.Subst["__BCC__"] = bcc + "\nBcc: %s" % (Cnf["Dinstall::Bcc"]);
805     else:
806         Katie.Subst["__BCC__"] = bcc;
807
808     for changes_file in changes_files:
809         changes_file = utils.validate_changes_file_arg(changes_file, 0);
810         if not changes_file:
811             continue;
812         print "\n" + changes_file;
813         do_pkg (changes_file);
814
815     end();
816
817 ################################################################################
818
819 if __name__ == '__main__':
820     main()