Coverage for dak/queue_report.py: 42%

384 statements  

« prev     ^ index     » next       coverage.py v7.6.0, created at 2026-02-10 22:10 +0000

1#! /usr/bin/env python3 

2 

3"""Produces a report on NEW and BYHAND packages""" 

4# Copyright (C) 2001, 2002, 2003, 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# <o-o> XP runs GCC, XFREE86, SSH etc etc,.,, I feel almost like linux.... 

23# <o-o> I am very confident that I can replicate any Linux application on XP 

24# <willy> o-o: *boggle* 

25# <o-o> building from source. 

26# <o-o> Viiru: I already run GIMP under XP 

27# <willy> o-o: why do you capitalise the names of all pieces of software? 

28# <o-o> willy: because I want the EMPHASIZE them.... 

29# <o-o> grr s/the/to/ 

30# <willy> o-o: it makes you look like ZIPPY the PINHEAD 

31# <o-o> willy: no idea what you are talking about. 

32# <willy> o-o: do some research 

33# <o-o> willy: for what reason? 

34 

35################################################################################ 

36 

37import datetime 

38import functools 

39import html 

40import os 

41import sys 

42import time 

43from typing import IO, Any, Literal, NoReturn, cast 

44 

45import apt_pkg 

46from sqlalchemy import sql 

47from sqlalchemy.engine import CursorResult 

48 

49from daklib import utils 

50from daklib.dak_exceptions import ParseMaintError 

51from daklib.dbconn import DBConn, PolicyQueue, get_uid_from_fingerprint, has_new_comment 

52from daklib.policy import PolicyQueueUploadHandler 

53from daklib.textutils import fix_maintainer 

54from daklib.utils import get_logins_from_ldap 

55 

56Cnf: apt_pkg.Configuration 

57direction: list[tuple[int, int, str | Literal[0]]] = [] 

58 

59################################################################################ 

60 

61 

62def usage(exit_code=0) -> NoReturn: 

63 print( 

64 """Usage: dak queue-report 

65Prints a report of packages in queues (usually new and byhand). 

66 

67 -h, --help show this help and exit. 

68 -8, --822 writes 822 formated output to the location set in dak.conf 

69 -n, --new produce html-output 

70 -s, --sort=key sort output according to key, see below. 

71 -a, --age=key if using sort by age, how should time be treated? 

72 If not given a default of hours will be used. 

73 -r, --rrd=key Directory where rrd files to be updated are stored 

74 -d, --directories=key A comma separated list of queues to be scanned 

75 

76 Sorting Keys: ao=age, oldest first. an=age, newest first. 

77 na=name, ascending nd=name, descending 

78 nf=notes, first nl=notes, last 

79 

80 Age Keys: m=minutes, h=hours, d=days, w=weeks, o=months, y=years 

81 

82""" 

83 ) 

84 sys.exit(exit_code) 

85 

86 

87################################################################################ 

88 

89 

90def plural(x: float | int) -> str: 

91 if x > 1: 

92 return "s" 

93 else: 

94 return "" 

95 

96 

97################################################################################ 

98 

99 

100def time_pp(x: float | int) -> str: 

101 if x < 60: 101 ↛ 103line 101 didn't jump to line 103 because the condition on line 101 was always true

102 unit = "second" 

103 elif x < 3600: 

104 x /= 60 

105 unit = "minute" 

106 elif x < 86400: 

107 x /= 3600 

108 unit = "hour" 

109 elif x < 604800: 

110 x /= 86400 

111 unit = "day" 

112 elif x < 2419200: 

113 x /= 604800 

114 unit = "week" 

115 elif x < 29030400: 

116 x /= 2419200 

117 unit = "month" 

118 else: 

119 x /= 29030400 

120 unit = "year" 

121 x = int(x) 

122 return "%s %s%s" % (x, unit, plural(x)) 

123 

124 

125################################################################################ 

126 

127 

128def sg_compare(a, b) -> int: 

129 a1 = a[1] 

130 b1 = b[1] 

131 # Sort by have pending action, have note, time of oldest upload. 

132 # Sort by have pending action 

133 a_note_state = a1["processed"] 

134 b_note_state = b1["processed"] 

135 if a_note_state < b_note_state: 135 ↛ 136line 135 didn't jump to line 136 because the condition on line 135 was never true

136 return -1 

137 elif a_note_state > b_note_state: 137 ↛ 138line 137 didn't jump to line 138 because the condition on line 137 was never true

138 return 1 

139 

140 # Sort by have note 

141 a_note_state = a1["note_state"] 

142 b_note_state = b1["note_state"] 

143 if a_note_state < b_note_state: 143 ↛ 144line 143 didn't jump to line 144 because the condition on line 143 was never true

144 return -1 

145 elif a_note_state > b_note_state: 145 ↛ 146line 145 didn't jump to line 146 because the condition on line 145 was never true

146 return 1 

147 

148 # Sort by time of oldest upload 

149 return a1["oldest"] - b1["oldest"] 

150 

151 

152############################################################ 

153 

154 

155def sortfunc(a, b) -> int: 

156 for sorting in direction: 156 ↛ 157line 156 didn't jump to line 157 because the loop on line 156 never started

157 (sortkey, way, time) = sorting 

158 ret = 0 

159 if time == "m": 

160 x = int(a[sortkey] / 60) 

161 y = int(b[sortkey] / 60) 

162 elif time == "h": 

163 x = int(a[sortkey] / 3600) 

164 y = int(b[sortkey] / 3600) 

165 elif time == "d": 

166 x = int(a[sortkey] / 86400) 

167 y = int(b[sortkey] / 86400) 

168 elif time == "w": 

169 x = int(a[sortkey] / 604800) 

170 y = int(b[sortkey] / 604800) 

171 elif time == "o": 

172 x = int(a[sortkey] / 2419200) 

173 y = int(b[sortkey] / 2419200) 

174 elif time == "y": 

175 x = int(a[sortkey] / 29030400) 

176 y = int(b[sortkey] / 29030400) 

177 else: 

178 x = a[sortkey] 

179 y = b[sortkey] 

180 if x < y: 

181 ret = -1 

182 elif x > y: 

183 ret = 1 

184 if ret != 0: 

185 if way < 0: 

186 ret = ret * -1 

187 return ret 

188 return 0 

189 

190 

191############################################################ 

192 

193 

194def header() -> None: 

195 print( 

196 """<!DOCTYPE html> 

197<html lang="en"> 

198 <head> 

199 <meta charset="utf-8"> 

200 <link rel="stylesheet" href="style.css"> 

201 <link rel="shortcut icon" href="https://www.debian.org/favicon.ico"> 

202 <title> 

203 Debian NEW and BYHAND Packages 

204 </title> 

205 <script> 

206 function togglePkg() { 

207 for (const el of document.getElementsByClassName('sourceNEW')) { 

208 el.style.display = el.style.display == '' ? 'none' : ''; 

209 } 

210 } 

211 </script> 

212 </head> 

213 <body id="NEW"> 

214 <div id="logo"> 

215 <a href="https://www.debian.org/"> 

216 <img src="https://www.debian.org/logos/openlogo-nd-50.png" 

217 alt=""></a> 

218 <a href="https://www.debian.org/"> 

219 <img src="https://www.debian.org/Pics/debian.png" 

220 alt="Debian Project"></a> 

221 </div> 

222 <div id="titleblock"> 

223 

224 <img src="https://www.debian.org/Pics/red-upperleft.png" 

225 id="red-upperleft" alt=""> 

226 <img src="https://www.debian.org/Pics/red-lowerleft.png" 

227 id="red-lowerleft" alt=""> 

228 <img src="https://www.debian.org/Pics/red-upperright.png" 

229 id="red-upperright" alt=""> 

230 <img src="https://www.debian.org/Pics/red-lowerright.png" 

231 id="red-lowerright" alt=""> 

232 <span class="title"> 

233 Debian NEW and BYHAND Packages 

234 </span> 

235 </div> 

236 """ 

237 ) 

238 

239 

240def footer() -> None: 

241 print( 

242 '<p class="timestamp">Timestamp: %s (UTC)</p>' 

243 % (time.strftime("%d.%m.%Y / %H:%M:%S", time.gmtime())) 

244 ) 

245 print( 

246 """ 

247 <p> 

248 There are <a href=\"/stat.html\">graphs about the queues</a> available. 

249 You can also look at the <a href="/new.822">RFC822 version</a>. 

250 </p> 

251 """ 

252 ) 

253 

254 print( 

255 """ 

256 <div class="footer"> 

257 <p>Hint: Age is the youngest upload of the package, if there is more than 

258 one version.<br> 

259 You may want to look at <a href="https://ftp-master.debian.org/REJECT-FAQ.html">the REJECT-FAQ</a> 

260 for possible reasons why one of the above packages may get rejected.</p> 

261 </div> </body> </html> 

262 """ 

263 ) 

264 

265 

266def table_header(type: str, source_count: int, total_count: int) -> None: 

267 print("<h1 class='sourceNEW'>Summary for: %s</h1>" % (type)) 

268 print( 

269 "<h1 class='sourceNEW' style='display: none'>Summary for: binary-%s only</h1>" 

270 % (type) 

271 ) 

272 print( 

273 """ 

274 <p class="togglepkg" onclick="togglePkg()">Click to toggle all/binary-NEW packages</p> 

275 <table class="NEW"> 

276 <caption class="sourceNEW"> 

277 """ 

278 ) 

279 print( 

280 "Package count in <strong>%s</strong>: <em>%s</em>&nbsp;|&nbsp; Total Package count: <em>%s</em>" 

281 % (type, source_count, total_count) 

282 ) 

283 print( 

284 """ 

285 </caption> 

286 <thead> 

287 <tr> 

288 <th>Package</th> 

289 <th>Version</th> 

290 <th>Arch</th> 

291 <th>Distribution</th> 

292 <th>Age</th> 

293 <th>Upload info</th> 

294 <th>Closes</th> 

295 </tr> 

296 </thead> 

297 <tbody> 

298 """ 

299 ) 

300 

301 

302def table_footer(type) -> None: 

303 print("</tbody></table>") 

304 

305 

306def table_row( 

307 source: str, 

308 version: str, 

309 arch: str, 

310 last_mod, 

311 maint, 

312 distribution, 

313 closes, 

314 fingerprint, 

315 sponsor, 

316 changedby, 

317) -> None: 

318 trclass = "sid" 

319 session = DBConn().session() 

320 for dist in distribution: 

321 if dist == "experimental": 

322 trclass = "exp" 

323 

324 query = """SELECT source 

325 FROM source_suite 

326 WHERE source = :source 

327 AND suite_name IN ('unstable', 'experimental')""" 

328 if not cast( 

329 CursorResult, session.execute(sql.text(query), {"source": source}) 

330 ).rowcount: 

331 trclass += " sourceNEW" 

332 session.commit() 

333 

334 print('<tr class="%s">' % (trclass)) 

335 

336 if "sourceNEW" in trclass: 

337 print( 

338 '<td class="package"><a href="https://dfsg-new-queue.debian.org/reviews/%(source)s">%(source)s</a></td>' 

339 % {"source": source} 

340 ) 

341 else: 

342 print( 

343 '<td class="package"><a href="https://tracker.debian.org/pkg/%(source)s">%(source)s</a></td>' 

344 % {"source": source} 

345 ) 

346 print('<td class="version">') 

347 for vers in version.split(): 

348 print( 

349 '<a href="https://dfsg-new-queue.debian.org/reviews/%s/%s">%s</a><br>' 

350 % (source, html.escape(vers), html.escape(vers, quote=False)) 

351 ) 

352 print("</td>") 

353 print('<td class="arch">%s</td>' % (arch)) 

354 print('<td class="distribution">') 

355 for dist in distribution: 

356 print("%s<br>" % (dist)) 

357 print("</td>") 

358 print( 

359 '<td class="age"><abbr title="%s">%s</abbr></td>' 

360 % ( 

361 datetime.datetime.utcfromtimestamp(int(time.time()) - last_mod).strftime( 

362 "%a, %d %b %Y %T UTC" 

363 ), 

364 time_pp(last_mod), 

365 ) 

366 ) 

367 (name, mail) = maint.split(":", 1) 

368 

369 print('<td class="upload-data">') 

370 print( 

371 '<span class="maintainer">Maintainer: <a href="https://qa.debian.org/developer.php?login=%s">%s</a></span><br>' 

372 % (html.escape(mail), html.escape(name, quote=False)) 

373 ) 

374 (name, mail) = changedby.split(":", 1) 

375 print( 

376 '<span class="changed-by">Changed-By: <a href="https://qa.debian.org/developer.php?login=%s">%s</a></span><br>' 

377 % (html.escape(mail), html.escape(name, quote=False)) 

378 ) 

379 

380 if sponsor: 

381 print( 

382 '<span class="sponsor">Sponsor: <a href="https://qa.debian.org/developer.php?login=%s">%s</a>@debian.org</span><br>' 

383 % (html.escape(sponsor), html.escape(sponsor, quote=False)) 

384 ) 

385 

386 print('<span class="signature">Fingerprint: %s</span>' % (fingerprint)) 

387 print("</td>") 

388 

389 print('<td class="closes">') 

390 for close in closes: 

391 print( 

392 '<a href="https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=%s">#%s</a><br>' 

393 % (html.escape(close), html.escape(close, quote=False)) 

394 ) 

395 print("</td></tr>") 

396 

397 

398############################################################ 

399 

400 

401def update_graph_database( 

402 rrd_dir: str | None, type: str, n_source: int, n_binary: int 

403) -> None: 

404 if not rrd_dir: 404 ↛ 407line 404 didn't jump to line 407 because the condition on line 404 was always true

405 return 

406 

407 import rrdtool 

408 

409 rrd_file = os.path.join(rrd_dir, type.lower() + ".rrd") 

410 update = [rrd_file, "N:%s:%s" % (n_source, n_binary)] 

411 

412 try: 

413 rrdtool.update(*update) 

414 except rrdtool.error: 

415 create = ( 

416 [rrd_file] 

417 + """ 

418--step 

419300 

420--start 

4210 

422DS:ds0:GAUGE:7200:0:1000 

423DS:ds1:GAUGE:7200:0:1000 

424RRA:AVERAGE:0.5:1:599 

425RRA:AVERAGE:0.5:6:700 

426RRA:AVERAGE:0.5:24:775 

427RRA:AVERAGE:0.5:288:795 

428RRA:MAX:0.5:1:600 

429RRA:MAX:0.5:6:700 

430RRA:MAX:0.5:24:775 

431RRA:MAX:0.5:288:795 

432""".strip().split( 

433 "\n" 

434 ) 

435 ) 

436 try: 

437 rrdtool.create(*create) 

438 rrdtool.update(*update) 

439 except rrdtool.error as e: 

440 print( 

441 ( 

442 "warning: queue_report: rrdtool error, skipping %s.rrd: %s" 

443 % (type, e) 

444 ) 

445 ) 

446 except NameError: 

447 pass 

448 

449 

450############################################################ 

451 

452 

453def process_queue(queue: PolicyQueue, log: IO[str] | None, rrd_dir: str | None) -> None: 

454 msg = "" 

455 type = queue.queue_name 

456 session = DBConn().session() 

457 

458 # Divide the .changes into per-source groups 

459 per_source: dict[str, dict[str, Any]] = {} 

460 total_pending = 0 

461 for upload in queue.uploads: 

462 source = upload.changes.source 

463 if source not in per_source: 463 ↛ 471line 463 didn't jump to line 471 because the condition on line 463 was always true

464 per_source[source] = {} 

465 per_source[source]["list"] = [] 

466 per_source[source]["processed"] = "" 

467 handler = PolicyQueueUploadHandler(upload, session) 

468 if handler.get_action(): 468 ↛ 469line 468 didn't jump to line 469 because the condition on line 468 was never true

469 per_source[source]["processed"] = "PENDING %s" % handler.get_action() 

470 total_pending += 1 

471 per_source[source]["list"].append(upload) 

472 per_source[source]["list"].sort(key=lambda x: x.changes.created, reverse=True) 

473 # Determine oldest time and have note status for each source group 

474 for source in list(per_source.keys()): 

475 source_list = per_source[source]["list"] 

476 first = source_list[0] 

477 oldest = time.mktime(first.changes.created.timetuple()) 

478 have_note = 0 

479 for d in per_source[source]["list"]: 

480 mtime = time.mktime(d.changes.created.timetuple()) 

481 if "Queue-Report::Options::New" in Cnf: 481 ↛ 482line 481 didn't jump to line 482 because the condition on line 481 was never true

482 if mtime > oldest: 

483 oldest = mtime 

484 else: 

485 if mtime < oldest: 485 ↛ 486line 485 didn't jump to line 486 because the condition on line 485 was never true

486 oldest = mtime 

487 have_note += has_new_comment( 

488 d.policy_queue, d.changes.source, d.changes.version 

489 ) 

490 per_source[source]["oldest"] = oldest 

491 if not have_note: 491 ↛ 493line 491 didn't jump to line 493 because the condition on line 491 was always true

492 per_source[source]["note_state"] = 0 # none 

493 elif have_note < len(source_list): 

494 per_source[source]["note_state"] = 1 # some 

495 else: 

496 per_source[source]["note_state"] = 2 # all 

497 per_source_items = list(per_source.items()) 

498 per_source_items.sort(key=functools.cmp_to_key(sg_compare)) 

499 

500 update_graph_database(rrd_dir, type, len(per_source_items), len(queue.uploads)) 

501 

502 entries = [] 

503 max_source_len = 0 

504 max_version_len = 0 

505 max_arch_len = 0 

506 try: 

507 logins = get_logins_from_ldap() 

508 except: 

509 logins = dict() 

510 for i in per_source_items: 

511 maintainer = {} 

512 maint = "" 

513 distribution = "" 

514 closes = "" 

515 fingerprint = "" 

516 changeby = {} 

517 changedby = "" 

518 sponsor = "" 

519 filename = i[1]["list"][0].changes.changesname 

520 last_modified = time.time() - i[1]["oldest"] 

521 source = i[1]["list"][0].changes.source 

522 if len(source) > max_source_len: 

523 max_source_len = len(source) 

524 binary_list = i[1]["list"][0].binaries 

525 binary = ", ".join([b.package for b in binary_list]) 

526 arches = set() 

527 versions = set() 

528 for j in i[1]["list"]: 

529 dbc = j.changes 

530 

531 if ( 531 ↛ 535line 531 didn't jump to line 535

532 "Queue-Report::Options::New" in Cnf 

533 or "Queue-Report::Options::822" in Cnf 

534 ): 

535 try: 

536 ( 

537 maintainer["maintainer822"], 

538 maintainer["maintainer2047"], 

539 maintainer["maintainername"], 

540 maintainer["maintaineremail"], 

541 ) = fix_maintainer(dbc.maintainer) 

542 except ParseMaintError: 

543 print("Problems while parsing maintainer address\n") 

544 maintainer["maintainername"] = "Unknown" 

545 maintainer["maintaineremail"] = "Unknown" 

546 maint = "%s:%s" % ( 

547 maintainer["maintainername"], 

548 maintainer["maintaineremail"], 

549 ) 

550 # ...likewise for the Changed-By: field if it exists. 

551 try: 

552 ( 

553 changeby["changedby822"], 

554 changeby["changedby2047"], 

555 changeby["changedbyname"], 

556 changeby["changedbyemail"], 

557 ) = fix_maintainer(dbc.changedby) 

558 except ParseMaintError: 

559 ( 

560 changeby["changedby822"], 

561 changeby["changedby2047"], 

562 changeby["changedbyname"], 

563 changeby["changedbyemail"], 

564 ) = ("", "", "", "") 

565 changedby = "%s:%s" % ( 

566 changeby["changedbyname"], 

567 changeby["changedbyemail"], 

568 ) 

569 

570 distribution = dbc.distribution.split() 

571 closes = dbc.closes 

572 

573 fingerprint = dbc.fingerprint 

574 sponsor_uid = get_uid_from_fingerprint(fingerprint, session) 

575 sponsor_name = sponsor_uid.name if sponsor_uid else "(Unknown)" 

576 sponsor_login = sponsor_uid.uid if sponsor_uid else "(Unknown)" 

577 if "@" in sponsor_login: 

578 if fingerprint in logins: 

579 sponsor_login = logins[fingerprint] 

580 if ( 

581 sponsor_name != maintainer["maintainername"] 

582 and sponsor_name != changeby["changedbyname"] 

583 and sponsor_login + "@debian.org" != maintainer["maintaineremail"] 

584 and sponsor_name != changeby["changedbyemail"] 

585 ): 

586 sponsor = sponsor_login 

587 

588 for arch in dbc.architecture.split(): 

589 arches.add(arch) 

590 versions.add(dbc.version) 

591 arches_list = sorted(arches, key=utils.ArchKey) 

592 arch_list = " ".join(arches_list) 

593 version_list = " ".join(sorted(versions, reverse=True)) 

594 if len(version_list) > max_version_len: 

595 max_version_len = len(version_list) 

596 if len(arch_list) > max_arch_len: 

597 max_arch_len = len(arch_list) 

598 if i[1]["note_state"]: 598 ↛ 599line 598 didn't jump to line 599 because the condition on line 598 was never true

599 note = " | [N]" 

600 else: 

601 note = "" 

602 entries.append( 

603 [ 

604 source, 

605 binary, 

606 version_list, 

607 arch_list, 

608 per_source[source]["processed"], 

609 note, 

610 last_modified, 

611 maint, 

612 distribution, 

613 closes, 

614 fingerprint, 

615 sponsor, 

616 changedby, 

617 filename, 

618 ] 

619 ) 

620 

621 # direction entry consists of "Which field, which direction, time-consider" where 

622 # time-consider says how we should treat last_modified. Thats all. 

623 

624 # Look for the options for sort and then do the sort. 

625 age = "h" 

626 if "Queue-Report::Options::Age" in Cnf: 626 ↛ 627line 626 didn't jump to line 627 because the condition on line 626 was never true

627 age = Cnf["Queue-Report::Options::Age"] 

628 if "Queue-Report::Options::New" in Cnf: 628 ↛ 630line 628 didn't jump to line 630 because the condition on line 628 was never true

629 # If we produce html we always have oldest first. 

630 direction.append((6, -1, "ao")) 

631 else: 

632 if "Queue-Report::Options::Sort" in Cnf: 632 ↛ 633line 632 didn't jump to line 633 because the condition on line 632 was never true

633 for j in Cnf["Queue-Report::Options::Sort"].split(","): 

634 if j == "ao": 

635 # Age, oldest first. 

636 direction.append((6, -1, age)) 

637 elif j == "an": 

638 # Age, newest first. 

639 direction.append((6, 1, age)) 

640 elif j == "na": 

641 # Name, Ascending. 

642 direction.append((0, 1, 0)) 

643 elif j == "nd": 

644 # Name, Descending. 

645 direction.append((0, -1, 0)) 

646 elif j == "nl": 

647 # Notes last. 

648 direction.append((5, 1, 0)) 

649 elif j == "nf": 

650 # Notes first. 

651 direction.append((5, -1, 0)) 

652 entries.sort(key=functools.cmp_to_key(sortfunc)) 

653 # Yes, in theory you can add several sort options at the commandline with. But my mind is to small 

654 # at the moment to come up with a real good sorting function that considers all the sidesteps you 

655 # have with it. (If you combine options it will simply take the last one at the moment). 

656 # Will be enhanced in the future. 

657 

658 if log is not None: 658 ↛ 660line 658 didn't jump to line 660 because the condition on line 658 was never true

659 # print stuff out in 822 format 

660 for entry in entries: 

661 ( 

662 source, 

663 binary, 

664 version_list, 

665 arch_list, 

666 processed, 

667 note, 

668 last_modified, 

669 maint, 

670 distribution, 

671 closes, 

672 fingerprint, 

673 sponsor, 

674 changedby, 

675 changes_file, 

676 ) = entry 

677 

678 # We'll always have Source, Version, Arch, Mantainer, and Dist 

679 # For the rest, check to see if we have them, then print them out 

680 log.write("Source: " + source + "\n") 

681 log.write("Binary: " + binary + "\n") 

682 log.write("Version: " + version_list + "\n") 

683 log.write("Architectures: ") 

684 log.write((", ".join(arch_list.split(" "))) + "\n") 

685 log.write("Age: " + time_pp(last_modified) + "\n") 

686 log.write( 

687 "Last-Modified: " + str(int(time.time()) - int(last_modified)) + "\n" 

688 ) 

689 log.write("Queue: " + type + "\n") 

690 

691 (name, mail) = maint.split(":", 1) 

692 log.write("Maintainer: " + name + " <" + mail + ">" + "\n") 

693 if changedby: 

694 (name, mail) = changedby.split(":", 1) 

695 log.write("Changed-By: " + name + " <" + mail + ">" + "\n") 

696 if sponsor: 

697 log.write("Sponsored-By: %s@debian.org\n" % sponsor) 

698 log.write("Distribution:") 

699 for dist in distribution: 

700 log.write(" " + dist) 

701 log.write("\n") 

702 log.write("Fingerprint: " + fingerprint + "\n") 

703 if closes: 

704 bug_string = "" 

705 for bugs in closes: 

706 bug_string += "#" + bugs + ", " 

707 log.write("Closes: " + bug_string[:-2] + "\n") 

708 log.write("Changes-File: " + os.path.basename(changes_file) + "\n") 

709 log.write("\n") 

710 

711 total_count = len(queue.uploads) 

712 source_count = len(per_source_items) 

713 

714 if "Queue-Report::Options::New" in Cnf: 714 ↛ 715line 714 didn't jump to line 715 because the condition on line 714 was never true

715 direction.append((6, 1, "ao")) 

716 entries.sort(key=functools.cmp_to_key(sortfunc)) 

717 # Output for a html file. First table header. then table_footer. 

718 # Any line between them is then a <tr> printed from subroutine table_row. 

719 if len(entries) > 0: 

720 table_header(type.upper(), source_count, total_count) 

721 for entry in entries: 

722 ( 

723 source, 

724 binary, 

725 version_list, 

726 arch_list, 

727 processed, 

728 note, 

729 last_modified, 

730 maint, 

731 distribution, 

732 closes, 

733 fingerprint, 

734 sponsor, 

735 changedby, 

736 _, 

737 ) = entry 

738 table_row( 

739 source, 

740 version_list, 

741 arch_list, 

742 last_modified, 

743 maint, 

744 distribution, 

745 closes, 

746 fingerprint, 

747 sponsor, 

748 changedby, 

749 ) 

750 table_footer(type.upper()) 

751 elif "Queue-Report::Options::822" not in Cnf: 751 ↛ exitline 751 didn't return from function 'process_queue' because the condition on line 751 was always true

752 # The "normal" output without any formatting. 

753 msg = "" 

754 for entry in entries: 

755 ( 

756 source, 

757 binary, 

758 version_list, 

759 arch_list, 

760 processed, 

761 note, 

762 last_modified, 

763 _, 

764 _, 

765 _, 

766 _, 

767 _, 

768 _, 

769 _, 

770 ) = entry 

771 if processed: 771 ↛ 772line 771 didn't jump to line 772 because the condition on line 771 was never true

772 format = "%%-%ds | %%-%ds | %%-%ds | %%s\n" % ( 

773 max_source_len, 

774 max_version_len, 

775 max_arch_len, 

776 ) 

777 msg += format % (source, version_list, arch_list, processed) 

778 else: 

779 format = "%%-%ds | %%-%ds | %%-%ds%%s | %%s old\n" % ( 

780 max_source_len, 

781 max_version_len, 

782 max_arch_len, 

783 ) 

784 msg += format % ( 

785 source, 

786 version_list, 

787 arch_list, 

788 note, 

789 time_pp(last_modified), 

790 ) 

791 

792 if msg: 

793 print(type.upper()) 

794 print("-" * len(type)) 

795 print() 

796 print(msg) 

797 print( 

798 ( 

799 "%s %s source package%s / %s %s package%s in total / %s %s package%s to be processed." 

800 % ( 

801 source_count, 

802 type, 

803 plural(source_count), 

804 total_count, 

805 type, 

806 plural(total_count), 

807 total_pending, 

808 type, 

809 plural(total_pending), 

810 ) 

811 ) 

812 ) 

813 print() 

814 

815 

816################################################################################ 

817 

818 

819def main() -> None: 

820 global Cnf 

821 

822 Cnf = utils.get_conf() 

823 Arguments = [ 

824 ("h", "help", "Queue-Report::Options::Help"), 

825 ("n", "new", "Queue-Report::Options::New"), 

826 ("8", "822", "Queue-Report::Options::822"), 

827 ("s", "sort", "Queue-Report::Options::Sort", "HasArg"), 

828 ("a", "age", "Queue-Report::Options::Age", "HasArg"), 

829 ("r", "rrd", "Queue-Report::Options::Rrd", "HasArg"), 

830 ("d", "directories", "Queue-Report::Options::Directories", "HasArg"), 

831 ] 

832 for i in ["help"]: 

833 key = "Queue-Report::Options::%s" % i 

834 if key not in Cnf: 834 ↛ 832line 834 didn't jump to line 832 because the condition on line 834 was always true

835 Cnf[key] = "" # type: ignore[index] 

836 

837 apt_pkg.parse_commandline(Cnf, Arguments, sys.argv) # type: ignore[attr-defined] 

838 

839 Options = Cnf.subtree("Queue-Report::Options") # type: ignore[attr-defined] 

840 if Options["Help"]: 

841 usage() 

842 

843 if "Queue-Report::Options::New" in Cnf: 843 ↛ 844line 843 didn't jump to line 844 because the condition on line 843 was never true

844 header() 

845 

846 queue_names = [] 

847 

848 if "Queue-Report::Options::Directories" in Cnf: 848 ↛ 849line 848 didn't jump to line 849 because the condition on line 848 was never true

849 for i in Cnf["Queue-Report::Options::Directories"].split(","): 

850 queue_names.append(i) 

851 elif "Queue-Report::Directories" in Cnf: 851 ↛ 852line 851 didn't jump to line 852 because the condition on line 851 was never true

852 queue_names = Cnf.value_list("Queue-Report::Directories") 

853 else: 

854 queue_names = ["byhand", "new"] 

855 

856 rrd_dir = Cnf.get("Queue-Report::Options::Rrd") or Cnf.get("Dir::Rrd") or None 

857 

858 f = None 

859 if "Queue-Report::Options::822" in Cnf: 859 ↛ 861line 859 didn't jump to line 861 because the condition on line 859 was never true

860 # Open the report file 

861 f = sys.stdout 

862 filename822 = Cnf.get("Queue-Report::ReportLocations::822Location") 

863 if filename822: 

864 f = open(filename822, "w") 

865 

866 session = DBConn().session() 

867 

868 for queue_name in queue_names: 

869 queue = session.query(PolicyQueue).filter_by(queue_name=queue_name).first() 

870 if queue is not None: 870 ↛ 873line 870 didn't jump to line 873 because the condition on line 870 was always true

871 process_queue(queue, f, rrd_dir) 

872 else: 

873 utils.warn("Cannot find queue %s" % queue_name) 

874 

875 if f is not None: 875 ↛ 876line 875 didn't jump to line 876 because the condition on line 875 was never true

876 f.close() 

877 

878 if "Queue-Report::Options::New" in Cnf: 878 ↛ 879line 878 didn't jump to line 879 because the condition on line 878 was never true

879 footer() 

880 

881 

882################################################################################ 

883 

884 

885if __name__ == "__main__": 

886 main()