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