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