Coverage for dak/queue_report.py: 41%
378 statements
« prev ^ index » next coverage.py v7.6.0, created at 2026-08-03 16:46 +0000
« prev ^ index » next coverage.py v7.6.0, created at 2026-08-03 16:46 +0000
1"""Produces a report on NEW and BYHAND packages"""
3# Copyright (C) 2001, 2002, 2003, 2005, 2006 James Troup <james@nocrew.org>
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation; either version 2 of the License, or
8# (at your option) any later version.
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
15# You should have received a copy of the GNU General Public License
16# along with this program; if not, write to the Free Software
17# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19################################################################################
21# <o-o> XP runs GCC, XFREE86, SSH etc etc,.,, I feel almost like linux....
22# <o-o> I am very confident that I can replicate any Linux application on XP
23# <willy> o-o: *boggle*
24# <o-o> building from source.
25# <o-o> Viiru: I already run GIMP under XP
26# <willy> o-o: why do you capitalise the names of all pieces of software?
27# <o-o> willy: because I want the EMPHASIZE them....
28# <o-o> grr s/the/to/
29# <willy> o-o: it makes you look like ZIPPY the PINHEAD
30# <o-o> willy: no idea what you are talking about.
31# <willy> o-o: do some research
32# <o-o> willy: for what reason?
34################################################################################
36import datetime
37import functools
38import html
39import os
40import sys
41import time
42from typing import IO, Any, Literal, NoReturn, cast
44import apt_pkg
45from sqlalchemy import select, sql
46from sqlalchemy.engine import CursorResult
48from daklib import utils
49from daklib.dak_exceptions import ParseMaintError
50from daklib.dbconn import DBConn, PolicyQueue, get_uid_from_fingerprint, has_new_comment
51from daklib.policy import PolicyQueueUploadHandler
52from daklib.textutils import fix_maintainer
53from daklib.utils import get_logins_from_ldap
55Cnf: apt_pkg.Configuration
56direction: list[tuple[int, int, str | Literal[0]]] = []
58################################################################################
61def usage(exit_code=0) -> NoReturn:
62 print(
63 """Usage: dak queue-report
64Prints a report of packages in queues (usually new and byhand).
66 -h, --help show this help and exit.
67 -8, --822 writes 822 formated output to the location set in dak.conf
68 -n, --new produce html-output
69 -s, --sort=key sort output according to key, see below.
70 -a, --age=key if using sort by age, how should time be treated?
71 If not given a default of hours will be used.
72 -r, --rrd=key Directory where rrd files to be updated are stored
73 -d, --directories=key A comma separated list of queues to be scanned
75 Sorting Keys: ao=age, oldest first. an=age, newest first.
76 na=name, ascending nd=name, descending
77 nf=notes, first nl=notes, last
79 Age Keys: m=minutes, h=hours, d=days, w=weeks, o=months, y=years
81"""
82 )
83 sys.exit(exit_code)
86################################################################################
89def plural(x: float | int) -> str:
90 if x > 1:
91 return "s"
92 else:
93 return ""
96################################################################################
99def time_pp(x: float | int) -> str:
100 if x < 60: 100 ↛ 102line 100 didn't jump to line 102 because the condition on line 100 was always true
101 unit = "second"
102 elif x < 3600:
103 x /= 60
104 unit = "minute"
105 elif x < 86400:
106 x /= 3600
107 unit = "hour"
108 elif x < 604800:
109 x /= 86400
110 unit = "day"
111 elif x < 2419200:
112 x /= 604800
113 unit = "week"
114 elif x < 29030400:
115 x /= 2419200
116 unit = "month"
117 else:
118 x /= 29030400
119 unit = "year"
120 x = int(x)
121 return "%s %s%s" % (x, unit, plural(x))
124################################################################################
127def sg_compare(a, b) -> int:
128 a1 = a[1]
129 b1 = b[1]
130 # Sort by have pending action, have note, time of oldest upload.
131 # Sort by have pending action
132 a_note_state = a1["processed"]
133 b_note_state = b1["processed"]
134 if a_note_state < b_note_state: 134 ↛ 135line 134 didn't jump to line 135 because the condition on line 134 was never true
135 return -1
136 elif a_note_state > b_note_state: 136 ↛ 137line 136 didn't jump to line 137 because the condition on line 136 was never true
137 return 1
139 # Sort by have note
140 a_note_state = a1["note_state"]
141 b_note_state = b1["note_state"]
142 if a_note_state < b_note_state: 142 ↛ 143line 142 didn't jump to line 143 because the condition on line 142 was never true
143 return -1
144 elif a_note_state > b_note_state: 144 ↛ 145line 144 didn't jump to line 145 because the condition on line 144 was never true
145 return 1
147 # Sort by time of oldest upload
148 return a1["oldest"] - b1["oldest"]
151############################################################
154def sortfunc(a, b) -> int:
155 for sorting in direction: 155 ↛ 156line 155 didn't jump to line 156 because the loop on line 155 never started
156 (sortkey, way, time) = sorting
157 ret = 0
158 if time == "m":
159 x = int(a[sortkey] / 60)
160 y = int(b[sortkey] / 60)
161 elif time == "h":
162 x = int(a[sortkey] / 3600)
163 y = int(b[sortkey] / 3600)
164 elif time == "d":
165 x = int(a[sortkey] / 86400)
166 y = int(b[sortkey] / 86400)
167 elif time == "w":
168 x = int(a[sortkey] / 604800)
169 y = int(b[sortkey] / 604800)
170 elif time == "o":
171 x = int(a[sortkey] / 2419200)
172 y = int(b[sortkey] / 2419200)
173 elif time == "y":
174 x = int(a[sortkey] / 29030400)
175 y = int(b[sortkey] / 29030400)
176 else:
177 x = a[sortkey]
178 y = b[sortkey]
179 if x < y:
180 ret = -1
181 elif x > y:
182 ret = 1
183 if ret != 0:
184 if way < 0:
185 ret = ret * -1
186 return ret
187 return 0
190############################################################
193def header() -> None:
194 print(
195 """<!DOCTYPE html>
196<html lang="en">
197 <head>
198 <meta charset="utf-8">
199 <link rel="stylesheet" href="style.css">
200 <link rel="shortcut icon" href="https://www.debian.org/favicon.ico">
201 <title>
202 Debian NEW and BYHAND Packages
203 </title>
204 <script>
205 function togglePkg() {
206 for (const el of document.getElementsByClassName('sourceNEW')) {
207 el.style.display = el.style.display == '' ? 'none' : '';
208 }
209 }
210 </script>
211 </head>
212 <body id="NEW">
213 <div id="logo">
214 <a href="https://www.debian.org/">
215 <img src="https://www.debian.org/logos/openlogo-nd-50.png"
216 alt=""></a>
217 <a href="https://www.debian.org/">
218 <img src="https://www.debian.org/Pics/debian.png"
219 alt="Debian Project"></a>
220 </div>
221 <div id="titleblock">
223 <img src="https://www.debian.org/Pics/red-upperleft.png"
224 id="red-upperleft" alt="">
225 <img src="https://www.debian.org/Pics/red-lowerleft.png"
226 id="red-lowerleft" alt="">
227 <img src="https://www.debian.org/Pics/red-upperright.png"
228 id="red-upperright" alt="">
229 <img src="https://www.debian.org/Pics/red-lowerright.png"
230 id="red-lowerright" alt="">
231 <span class="title">
232 Debian NEW and BYHAND Packages
233 </span>
234 </div>
235 """
236 )
239def footer() -> None:
240 print(
241 '<p class="timestamp">Timestamp: %s (UTC)</p>'
242 % (time.strftime("%d.%m.%Y / %H:%M:%S", time.gmtime()))
243 )
244 print(
245 """
246 <p>
247 There are <a href=\"/stat.html\">graphs about the queues</a> available.
248 You can also look at the <a href="/new.822">RFC822 version</a>.
249 </p>
250 """
251 )
253 print(
254 """
255 <div class="footer">
256 <p>Hint: Age is the youngest upload of the package, if there is more than
257 one version.<br>
258 You may want to look at <a href="https://ftp-master.debian.org/REJECT-FAQ.html">the REJECT-FAQ</a>
259 for possible reasons why one of the above packages may get rejected.</p>
260 </div> </body> </html>
261 """
262 )
265def table_header(type: str, source_count: int, total_count: int) -> None:
266 print("<h1 class='sourceNEW'>Summary for: %s</h1>" % (type))
267 print(
268 "<h1 class='sourceNEW' style='display: none'>Summary for: binary-%s only</h1>"
269 % (type)
270 )
271 print(
272 """
273 <p class="togglepkg" onclick="togglePkg()">Click to toggle all/binary-NEW packages</p>
274 <table class="NEW">
275 <caption class="sourceNEW">
276 """
277 )
278 print(
279 "Package count in <strong>%s</strong>: <em>%s</em> | Total Package count: <em>%s</em>"
280 % (type, source_count, total_count)
281 )
282 print(
283 """
284 </caption>
285 <thead>
286 <tr>
287 <th>Package</th>
288 <th>Version</th>
289 <th>Arch</th>
290 <th>Distribution</th>
291 <th>Age</th>
292 <th>Upload info</th>
293 <th>Closes</th>
294 </tr>
295 </thead>
296 <tbody>
297 """
298 )
301def table_footer(type) -> None:
302 print("</tbody></table>")
305def table_row(
306 source: str,
307 version: str,
308 arch: str,
309 last_mod,
310 maint,
311 distribution,
312 closes,
313 fingerprint,
314 sponsor,
315 changedby,
316) -> None:
317 trclass = "sid"
318 session = DBConn().session()
319 for dist in distribution:
320 if dist == "experimental":
321 trclass = "exp"
323 query = """SELECT source
324 FROM source_suite
325 WHERE source = :source
326 AND suite_name IN ('unstable', 'experimental')"""
327 if not cast(
328 CursorResult, session.execute(sql.text(query), {"source": source})
329 ).rowcount:
330 trclass += " sourceNEW"
331 session.commit()
333 print('<tr class="%s">' % (trclass))
335 if "sourceNEW" in trclass:
336 print(
337 '<td class="package"><a href="https://dfsg-new-queue.debian.org/reviews/%(source)s">%(source)s</a></td>'
338 % {"source": source}
339 )
340 else:
341 print(
342 '<td class="package"><a href="https://tracker.debian.org/pkg/%(source)s">%(source)s</a></td>'
343 % {"source": source}
344 )
345 print('<td class="version">')
346 for vers in version.split():
347 print(
348 '<a href="https://dfsg-new-queue.debian.org/reviews/%s/%s">%s</a><br>'
349 % (source, html.escape(vers), html.escape(vers, quote=False))
350 )
351 print("</td>")
352 print('<td class="arch">%s</td>' % (arch))
353 print('<td class="distribution">')
354 for dist in distribution:
355 print("%s<br>" % (dist))
356 print("</td>")
357 print(
358 '<td class="age"><abbr title="%s">%s</abbr></td>'
359 % (
360 datetime.datetime.utcfromtimestamp(int(time.time()) - last_mod).strftime(
361 "%a, %d %b %Y %T UTC"
362 ),
363 time_pp(last_mod),
364 )
365 )
366 (name, mail) = maint.split(":", 1)
368 print('<td class="upload-data">')
369 print(
370 '<span class="maintainer">Maintainer: <a href="https://qa.debian.org/developer.php?login=%s">%s</a></span><br>'
371 % (html.escape(mail), html.escape(name, quote=False))
372 )
373 (name, mail) = changedby.split(":", 1)
374 print(
375 '<span class="changed-by">Changed-By: <a href="https://qa.debian.org/developer.php?login=%s">%s</a></span><br>'
376 % (html.escape(mail), html.escape(name, quote=False))
377 )
379 if sponsor:
380 print(
381 '<span class="sponsor">Sponsor: <a href="https://qa.debian.org/developer.php?login=%s">%s</a>@debian.org</span><br>'
382 % (html.escape(sponsor), html.escape(sponsor, quote=False))
383 )
385 print('<span class="signature">Fingerprint: %s</span>' % (fingerprint))
386 print("</td>")
388 print('<td class="closes">')
389 for close in closes:
390 print(
391 '<a href="https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=%s">#%s</a><br>'
392 % (html.escape(close), html.escape(close, quote=False))
393 )
394 print("</td></tr>")
397############################################################
400def update_graph_database(
401 rrd_dir: str | None, type: str, n_source: int, n_binary: int
402) -> None:
403 if not rrd_dir: 403 ↛ 406line 403 didn't jump to line 406 because the condition on line 403 was always true
404 return
406 import rrdtool
408 rrd_file = os.path.join(rrd_dir, type.lower() + ".rrd")
409 update = [rrd_file, "N:%s:%s" % (n_source, n_binary)]
411 try:
412 rrdtool.update(*update)
413 except rrdtool.error:
414 create = (
415 [rrd_file]
416 + """
417--step
418300
419--start
4200
421DS:ds0:GAUGE:7200:0:1000
422DS:ds1:GAUGE:7200:0:1000
423RRA:AVERAGE:0.5:1:599
424RRA:AVERAGE:0.5:6:700
425RRA:AVERAGE:0.5:24:775
426RRA:AVERAGE:0.5:288:795
427RRA:MAX:0.5:1:600
428RRA:MAX:0.5:6:700
429RRA:MAX:0.5:24:775
430RRA:MAX:0.5:288:795
431""".strip().split(
432 "\n"
433 )
434 )
435 try:
436 rrdtool.create(*create)
437 rrdtool.update(*update)
438 except rrdtool.error as e:
439 print(
440 "warning: queue_report: rrdtool error, skipping %s.rrd: %s" % (type, e)
441 )
442 except NameError:
443 pass
446############################################################
449def process_queue(queue: PolicyQueue, log: IO[str] | None, rrd_dir: str | None) -> None:
450 msg = ""
451 type = queue.queue_name
452 session = DBConn().session()
454 # Divide the .changes into per-source groups
455 per_source: dict[str, dict[str, Any]] = {}
456 total_pending = 0
457 for upload in queue.uploads:
458 source = upload.changes.source
459 if source not in per_source: 459 ↛ 467line 459 didn't jump to line 467 because the condition on line 459 was always true
460 per_source[source] = {}
461 per_source[source]["list"] = []
462 per_source[source]["processed"] = ""
463 handler = PolicyQueueUploadHandler(upload, session)
464 if handler.get_action(): 464 ↛ 465line 464 didn't jump to line 465 because the condition on line 464 was never true
465 per_source[source]["processed"] = "PENDING %s" % handler.get_action()
466 total_pending += 1
467 per_source[source]["list"].append(upload)
468 per_source[source]["list"].sort(key=lambda x: x.changes.created, reverse=True)
469 # Determine oldest time and have note status for each source group
470 for source in list(per_source.keys()):
471 source_list = per_source[source]["list"]
472 first = source_list[0]
473 oldest = time.mktime(first.changes.created.timetuple())
474 have_note = 0
475 for d in per_source[source]["list"]:
476 mtime = time.mktime(d.changes.created.timetuple())
477 if "Queue-Report::Options::New" in Cnf: 477 ↛ 478line 477 didn't jump to line 478 because the condition on line 477 was never true
478 oldest = max(oldest, mtime)
479 else:
480 oldest = min(oldest, mtime)
481 have_note += has_new_comment(
482 d.policy_queue, d.changes.source, d.changes.version
483 )
484 per_source[source]["oldest"] = oldest
485 if not have_note: 485 ↛ 487line 485 didn't jump to line 487 because the condition on line 485 was always true
486 per_source[source]["note_state"] = 0 # none
487 elif have_note < len(source_list):
488 per_source[source]["note_state"] = 1 # some
489 else:
490 per_source[source]["note_state"] = 2 # all
491 per_source_items = list(per_source.items())
492 per_source_items.sort(key=functools.cmp_to_key(sg_compare))
494 update_graph_database(rrd_dir, type, len(per_source_items), len(queue.uploads))
496 entries = []
497 max_source_len = 0
498 max_version_len = 0
499 max_arch_len = 0
500 try:
501 logins = get_logins_from_ldap()
502 except:
503 logins = {}
504 for i in per_source_items:
505 maintainer = {}
506 maint = ""
507 distribution = ""
508 closes = ""
509 fingerprint = ""
510 changeby = {}
511 changedby = ""
512 sponsor = ""
513 filename = i[1]["list"][0].changes.changesname
514 last_modified = time.time() - i[1]["oldest"]
515 source = i[1]["list"][0].changes.source
516 max_source_len = max(max_source_len, len(source))
517 binary_list = i[1]["list"][0].binaries
518 binary = ", ".join([b.package for b in binary_list])
519 arches = set()
520 versions = set()
521 for j in i[1]["list"]:
522 dbc = j.changes
524 if ( 524 ↛ 528line 524 didn't jump to line 528
525 "Queue-Report::Options::New" in Cnf
526 or "Queue-Report::Options::822" in Cnf
527 ):
528 try:
529 (
530 maintainer["maintainer822"],
531 maintainer["maintainer2047"],
532 maintainer["maintainername"],
533 maintainer["maintaineremail"],
534 ) = fix_maintainer(dbc.maintainer)
535 except ParseMaintError:
536 print("Problems while parsing maintainer address\n")
537 maintainer["maintainername"] = "Unknown"
538 maintainer["maintaineremail"] = "Unknown"
539 maint = "%s:%s" % (
540 maintainer["maintainername"],
541 maintainer["maintaineremail"],
542 )
543 # ...likewise for the Changed-By: field if it exists.
544 try:
545 (
546 changeby["changedby822"],
547 changeby["changedby2047"],
548 changeby["changedbyname"],
549 changeby["changedbyemail"],
550 ) = fix_maintainer(dbc.changedby)
551 except ParseMaintError:
552 (
553 changeby["changedby822"],
554 changeby["changedby2047"],
555 changeby["changedbyname"],
556 changeby["changedbyemail"],
557 ) = ("", "", "", "")
558 changedby = "%s:%s" % (
559 changeby["changedbyname"],
560 changeby["changedbyemail"],
561 )
563 distribution = dbc.distribution.split()
564 closes = dbc.closes
566 fingerprint = dbc.fingerprint
567 sponsor_uid = get_uid_from_fingerprint(fingerprint, session)
568 sponsor_name = sponsor_uid.name if sponsor_uid else "(Unknown)"
569 sponsor_login = sponsor_uid.uid if sponsor_uid else "(Unknown)"
570 if "@" in sponsor_login and fingerprint in logins:
571 sponsor_login = logins[fingerprint]
572 if (
573 sponsor_name != maintainer["maintainername"]
574 and sponsor_name != changeby["changedbyname"]
575 and sponsor_login + "@debian.org" != maintainer["maintaineremail"]
576 and sponsor_name != changeby["changedbyemail"]
577 ):
578 sponsor = sponsor_login
580 for arch in dbc.architecture.split():
581 arches.add(arch)
582 versions.add(dbc.version)
583 arches_list = sorted(arches, key=utils.ArchKey)
584 arch_list = " ".join(arches_list)
585 version_list = " ".join(sorted(versions, reverse=True))
586 max_version_len = max(max_version_len, len(version_list))
587 max_arch_len = max(max_arch_len, len(arch_list))
588 if i[1]["note_state"]: 588 ↛ 589line 588 didn't jump to line 589 because the condition on line 588 was never true
589 note = " | [N]"
590 else:
591 note = ""
592 entries.append(
593 [
594 source,
595 binary,
596 version_list,
597 arch_list,
598 per_source[source]["processed"],
599 note,
600 last_modified,
601 maint,
602 distribution,
603 closes,
604 fingerprint,
605 sponsor,
606 changedby,
607 filename,
608 ]
609 )
611 # direction entry consists of "Which field, which direction, time-consider" where
612 # time-consider says how we should treat last_modified. Thats all.
614 # Look for the options for sort and then do the sort.
615 age = "h"
616 if "Queue-Report::Options::Age" in Cnf: 616 ↛ 617line 616 didn't jump to line 617 because the condition on line 616 was never true
617 age = Cnf["Queue-Report::Options::Age"]
618 if "Queue-Report::Options::New" in Cnf: 618 ↛ 620line 618 didn't jump to line 620 because the condition on line 618 was never true
619 # If we produce html we always have oldest first.
620 direction.append((6, -1, "ao"))
621 else:
622 if "Queue-Report::Options::Sort" in Cnf: 622 ↛ 623line 622 didn't jump to line 623 because the condition on line 622 was never true
623 for j in Cnf["Queue-Report::Options::Sort"].split(","):
624 if j == "ao":
625 # Age, oldest first.
626 direction.append((6, -1, age))
627 elif j == "an":
628 # Age, newest first.
629 direction.append((6, 1, age))
630 elif j == "na":
631 # Name, Ascending.
632 direction.append((0, 1, 0))
633 elif j == "nd":
634 # Name, Descending.
635 direction.append((0, -1, 0))
636 elif j == "nl":
637 # Notes last.
638 direction.append((5, 1, 0))
639 elif j == "nf":
640 # Notes first.
641 direction.append((5, -1, 0))
642 entries.sort(key=functools.cmp_to_key(sortfunc))
643 # Yes, in theory you can add several sort options at the commandline with. But my mind is to small
644 # at the moment to come up with a real good sorting function that considers all the sidesteps you
645 # have with it. (If you combine options it will simply take the last one at the moment).
646 # Will be enhanced in the future.
648 if log is not None: 648 ↛ 650line 648 didn't jump to line 650 because the condition on line 648 was never true
649 # print stuff out in 822 format
650 for entry in entries:
651 (
652 source,
653 binary,
654 version_list,
655 arch_list,
656 processed,
657 note,
658 last_modified,
659 maint,
660 distribution,
661 closes,
662 fingerprint,
663 sponsor,
664 changedby,
665 changes_file,
666 ) = entry
668 # We'll always have Source, Version, Arch, Mantainer, and Dist
669 # For the rest, check to see if we have them, then print them out
670 log.write("Source: " + source + "\n")
671 log.write("Binary: " + binary + "\n")
672 log.write("Version: " + version_list + "\n")
673 log.write("Architectures: ")
674 log.write((", ".join(arch_list.split(" "))) + "\n")
675 log.write("Age: " + time_pp(last_modified) + "\n")
676 log.write(
677 "Last-Modified: " + str(int(time.time()) - int(last_modified)) + "\n"
678 )
679 log.write("Queue: " + type + "\n")
681 (name, mail) = maint.split(":", 1)
682 log.write("Maintainer: " + name + " <" + mail + ">" + "\n")
683 if changedby:
684 (name, mail) = changedby.split(":", 1)
685 log.write("Changed-By: " + name + " <" + mail + ">" + "\n")
686 if sponsor:
687 log.write("Sponsored-By: %s@debian.org\n" % sponsor)
688 log.write("Distribution:")
689 for dist in distribution:
690 log.write(" " + dist)
691 log.write("\n")
692 log.write("Fingerprint: " + fingerprint + "\n")
693 if closes:
694 bug_string = ""
695 for bugs in closes:
696 bug_string += "#" + bugs + ", "
697 log.write("Closes: " + bug_string[:-2] + "\n")
698 log.write("Changes-File: " + os.path.basename(changes_file) + "\n")
699 log.write("\n")
701 total_count = len(queue.uploads)
702 source_count = len(per_source_items)
704 if "Queue-Report::Options::New" in Cnf: 704 ↛ 705line 704 didn't jump to line 705 because the condition on line 704 was never true
705 direction.append((6, 1, "ao"))
706 entries.sort(key=functools.cmp_to_key(sortfunc))
707 # Output for a html file. First table header. then table_footer.
708 # Any line between them is then a <tr> printed from subroutine table_row.
709 if len(entries) > 0:
710 table_header(type.upper(), source_count, total_count)
711 for entry in entries:
712 (
713 source,
714 binary,
715 version_list,
716 arch_list,
717 processed,
718 note,
719 last_modified,
720 maint,
721 distribution,
722 closes,
723 fingerprint,
724 sponsor,
725 changedby,
726 _,
727 ) = entry
728 table_row(
729 source,
730 version_list,
731 arch_list,
732 last_modified,
733 maint,
734 distribution,
735 closes,
736 fingerprint,
737 sponsor,
738 changedby,
739 )
740 table_footer(type.upper())
741 elif "Queue-Report::Options::822" not in Cnf: 741 ↛ exitline 741 didn't return from function 'process_queue' because the condition on line 741 was always true
742 # The "normal" output without any formatting.
743 msg = ""
744 for entry in entries:
745 (
746 source,
747 binary,
748 version_list,
749 arch_list,
750 processed,
751 note,
752 last_modified,
753 _,
754 _,
755 _,
756 _,
757 _,
758 _,
759 _,
760 ) = entry
761 if processed: 761 ↛ 762line 761 didn't jump to line 762 because the condition on line 761 was never true
762 format = "%%-%ds | %%-%ds | %%-%ds | %%s\n" % (
763 max_source_len,
764 max_version_len,
765 max_arch_len,
766 )
767 msg += format % (source, version_list, arch_list, processed)
768 else:
769 format = "%%-%ds | %%-%ds | %%-%ds%%s | %%s old\n" % (
770 max_source_len,
771 max_version_len,
772 max_arch_len,
773 )
774 msg += format % (
775 source,
776 version_list,
777 arch_list,
778 note,
779 time_pp(last_modified),
780 )
782 if msg:
783 print(type.upper())
784 print("-" * len(type))
785 print()
786 print(msg)
787 print(
788 "%s %s source package%s / %s %s package%s in total / %s %s package%s to be processed."
789 % (
790 source_count,
791 type,
792 plural(source_count),
793 total_count,
794 type,
795 plural(total_count),
796 total_pending,
797 type,
798 plural(total_pending),
799 )
800 )
801 print()
804################################################################################
807def main() -> None:
808 global Cnf
810 Cnf = utils.get_conf()
811 Arguments = [
812 ("h", "help", "Queue-Report::Options::Help"),
813 ("n", "new", "Queue-Report::Options::New"),
814 ("8", "822", "Queue-Report::Options::822"),
815 ("s", "sort", "Queue-Report::Options::Sort", "HasArg"),
816 ("a", "age", "Queue-Report::Options::Age", "HasArg"),
817 ("r", "rrd", "Queue-Report::Options::Rrd", "HasArg"),
818 ("d", "directories", "Queue-Report::Options::Directories", "HasArg"),
819 ]
820 for i in ["help"]:
821 key = "Queue-Report::Options::%s" % i
822 if key not in Cnf: 822 ↛ 820line 822 didn't jump to line 820 because the condition on line 822 was always true
823 Cnf[key] = "" # type: ignore[index]
825 apt_pkg.parse_commandline(Cnf, Arguments, sys.argv) # type: ignore[attr-defined]
827 Options = Cnf.subtree("Queue-Report::Options") # type: ignore[attr-defined]
828 if Options["Help"]:
829 usage()
831 if "Queue-Report::Options::New" in Cnf: 831 ↛ 832line 831 didn't jump to line 832 because the condition on line 831 was never true
832 header()
834 queue_names = []
836 if "Queue-Report::Options::Directories" in Cnf: 836 ↛ 837line 836 didn't jump to line 837 because the condition on line 836 was never true
837 for i in Cnf["Queue-Report::Options::Directories"].split(","):
838 queue_names.append(i)
839 elif "Queue-Report::Directories" in Cnf: 839 ↛ 840line 839 didn't jump to line 840 because the condition on line 839 was never true
840 queue_names = Cnf.value_list("Queue-Report::Directories")
841 else:
842 queue_names = ["byhand", "new"]
844 rrd_dir = Cnf.get("Queue-Report::Options::Rrd") or Cnf.get("Dir::Rrd") or None
846 f = None
847 if "Queue-Report::Options::822" in Cnf: 847 ↛ 849line 847 didn't jump to line 849 because the condition on line 847 was never true
848 # Open the report file
849 f = sys.stdout
850 filename822 = Cnf.get("Queue-Report::ReportLocations::822Location")
851 if filename822:
852 f = open(filename822, "w")
854 session = DBConn().session()
856 for queue_name in queue_names:
857 queue = session.scalars(
858 select(PolicyQueue).filter_by(queue_name=queue_name).limit(1)
859 ).first()
860 if queue is not None: 860 ↛ 863line 860 didn't jump to line 863 because the condition on line 860 was always true
861 process_queue(queue, f, rrd_dir)
862 else:
863 utils.warn("Cannot find queue %s" % queue_name)
865 if f is not None: 865 ↛ 866line 865 didn't jump to line 866 because the condition on line 865 was never true
866 f.close()
868 if "Queue-Report::Options::New" in Cnf: 868 ↛ 869line 868 didn't jump to line 869 because the condition on line 868 was never true
869 footer()