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