Coverage for dak/queue_report.py: 42%

384 statements  

« prev     ^ index     » next       coverage.py v7.6.0, created at 2026-01-04 16:18 +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('<td class="package">%s</td>' % (source)) 

338 else: 

339 print( 

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

341 % {"source": source} 

342 ) 

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

344 for vers in version.split(): 

345 print( 

346 '<a href="new/%s_%s.html">%s</a><br>' 

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

348 ) 

349 print("</td>") 

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

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

352 for dist in distribution: 

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

354 print("</td>") 

355 print( 

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

357 % ( 

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

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

360 ), 

361 time_pp(last_mod), 

362 ) 

363 ) 

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

365 

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

367 print( 

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

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

370 ) 

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

372 print( 

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

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

375 ) 

376 

377 if sponsor: 

378 print( 

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

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

381 ) 

382 

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

384 print("</td>") 

385 

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

387 for close in closes: 

388 print( 

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

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

391 ) 

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

393 

394 

395############################################################ 

396 

397 

398def update_graph_database( 

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

400) -> None: 

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

402 return 

403 

404 import rrdtool 

405 

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

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

408 

409 try: 

410 rrdtool.update(*update) 

411 except rrdtool.error: 

412 create = ( 

413 [rrd_file] 

414 + """ 

415--step 

416300 

417--start 

4180 

419DS:ds0:GAUGE:7200:0:1000 

420DS:ds1:GAUGE:7200:0:1000 

421RRA:AVERAGE:0.5:1:599 

422RRA:AVERAGE:0.5:6:700 

423RRA:AVERAGE:0.5:24:775 

424RRA:AVERAGE:0.5:288:795 

425RRA:MAX:0.5:1:600 

426RRA:MAX:0.5:6:700 

427RRA:MAX:0.5:24:775 

428RRA:MAX:0.5:288:795 

429""".strip().split( 

430 "\n" 

431 ) 

432 ) 

433 try: 

434 rrdtool.create(*create) 

435 rrdtool.update(*update) 

436 except rrdtool.error as e: 

437 print( 

438 ( 

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

440 % (type, e) 

441 ) 

442 ) 

443 except NameError: 

444 pass 

445 

446 

447############################################################ 

448 

449 

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

451 msg = "" 

452 type = queue.queue_name 

453 session = DBConn().session() 

454 

455 # Divide the .changes into per-source groups 

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

457 total_pending = 0 

458 for upload in queue.uploads: 

459 source = upload.changes.source 

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

461 per_source[source] = {} 

462 per_source[source]["list"] = [] 

463 per_source[source]["processed"] = "" 

464 handler = PolicyQueueUploadHandler(upload, session) 

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

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

467 total_pending += 1 

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

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

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

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

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

473 first = source_list[0] 

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

475 have_note = 0 

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

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

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

479 if mtime > oldest: 

480 oldest = mtime 

481 else: 

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

483 oldest = mtime 

484 have_note += has_new_comment( 

485 d.policy_queue, d.changes.source, d.changes.version 

486 ) 

487 per_source[source]["oldest"] = oldest 

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

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

490 elif have_note < len(source_list): 

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

492 else: 

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

494 per_source_items = list(per_source.items()) 

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

496 

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

498 

499 entries = [] 

500 max_source_len = 0 

501 max_version_len = 0 

502 max_arch_len = 0 

503 try: 

504 logins = get_logins_from_ldap() 

505 except: 

506 logins = dict() 

507 for i in per_source_items: 

508 maintainer = {} 

509 maint = "" 

510 distribution = "" 

511 closes = "" 

512 fingerprint = "" 

513 changeby = {} 

514 changedby = "" 

515 sponsor = "" 

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

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

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

519 if len(source) > max_source_len: 519 ↛ 521line 519 didn't jump to line 521 because the condition on line 519 was always true

520 max_source_len = len(source) 

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

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

523 arches = set() 

524 versions = set() 

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

526 dbc = j.changes 

527 

528 if ( 528 ↛ 532line 528 didn't jump to line 532

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

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

531 ): 

532 try: 

533 ( 

534 maintainer["maintainer822"], 

535 maintainer["maintainer2047"], 

536 maintainer["maintainername"], 

537 maintainer["maintaineremail"], 

538 ) = fix_maintainer(dbc.maintainer) 

539 except ParseMaintError: 

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

541 maintainer["maintainername"] = "Unknown" 

542 maintainer["maintaineremail"] = "Unknown" 

543 maint = "%s:%s" % ( 

544 maintainer["maintainername"], 

545 maintainer["maintaineremail"], 

546 ) 

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

548 try: 

549 ( 

550 changeby["changedby822"], 

551 changeby["changedby2047"], 

552 changeby["changedbyname"], 

553 changeby["changedbyemail"], 

554 ) = fix_maintainer(dbc.changedby) 

555 except ParseMaintError: 

556 ( 

557 changeby["changedby822"], 

558 changeby["changedby2047"], 

559 changeby["changedbyname"], 

560 changeby["changedbyemail"], 

561 ) = ("", "", "", "") 

562 changedby = "%s:%s" % ( 

563 changeby["changedbyname"], 

564 changeby["changedbyemail"], 

565 ) 

566 

567 distribution = dbc.distribution.split() 

568 closes = dbc.closes 

569 

570 fingerprint = dbc.fingerprint 

571 sponsor_uid = get_uid_from_fingerprint(fingerprint, session) 

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

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

574 if "@" in sponsor_login: 

575 if fingerprint in logins: 

576 sponsor_login = logins[fingerprint] 

577 if ( 

578 sponsor_name != maintainer["maintainername"] 

579 and sponsor_name != changeby["changedbyname"] 

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

581 and sponsor_name != changeby["changedbyemail"] 

582 ): 

583 sponsor = sponsor_login 

584 

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

586 arches.add(arch) 

587 versions.add(dbc.version) 

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

589 arch_list = " ".join(arches_list) 

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

591 if len(version_list) > max_version_len: 

592 max_version_len = len(version_list) 

593 if len(arch_list) > max_arch_len: 

594 max_arch_len = len(arch_list) 

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

596 note = " | [N]" 

597 else: 

598 note = "" 

599 entries.append( 

600 [ 

601 source, 

602 binary, 

603 version_list, 

604 arch_list, 

605 per_source[source]["processed"], 

606 note, 

607 last_modified, 

608 maint, 

609 distribution, 

610 closes, 

611 fingerprint, 

612 sponsor, 

613 changedby, 

614 filename, 

615 ] 

616 ) 

617 

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

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

620 

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

622 age = "h" 

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

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

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

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

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

628 else: 

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

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

631 if j == "ao": 

632 # Age, oldest first. 

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

634 elif j == "an": 

635 # Age, newest first. 

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

637 elif j == "na": 

638 # Name, Ascending. 

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

640 elif j == "nd": 

641 # Name, Descending. 

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

643 elif j == "nl": 

644 # Notes last. 

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

646 elif j == "nf": 

647 # Notes first. 

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

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

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

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

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

653 # Will be enhanced in the future. 

654 

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

656 # print stuff out in 822 format 

657 for entry in entries: 

658 ( 

659 source, 

660 binary, 

661 version_list, 

662 arch_list, 

663 processed, 

664 note, 

665 last_modified, 

666 maint, 

667 distribution, 

668 closes, 

669 fingerprint, 

670 sponsor, 

671 changedby, 

672 changes_file, 

673 ) = entry 

674 

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

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

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

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

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

680 log.write("Architectures: ") 

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

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

683 log.write( 

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

685 ) 

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

687 

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

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

690 if changedby: 

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

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

693 if sponsor: 

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

695 log.write("Distribution:") 

696 for dist in distribution: 

697 log.write(" " + dist) 

698 log.write("\n") 

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

700 if closes: 

701 bug_string = "" 

702 for bugs in closes: 

703 bug_string += "#" + bugs + ", " 

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

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

706 log.write("\n") 

707 

708 total_count = len(queue.uploads) 

709 source_count = len(per_source_items) 

710 

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

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

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

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

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

716 if len(entries) > 0: 

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

718 for entry in entries: 

719 ( 

720 source, 

721 binary, 

722 version_list, 

723 arch_list, 

724 processed, 

725 note, 

726 last_modified, 

727 maint, 

728 distribution, 

729 closes, 

730 fingerprint, 

731 sponsor, 

732 changedby, 

733 _, 

734 ) = entry 

735 table_row( 

736 source, 

737 version_list, 

738 arch_list, 

739 last_modified, 

740 maint, 

741 distribution, 

742 closes, 

743 fingerprint, 

744 sponsor, 

745 changedby, 

746 ) 

747 table_footer(type.upper()) 

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

749 # The "normal" output without any formatting. 

750 msg = "" 

751 for entry in entries: 

752 ( 

753 source, 

754 binary, 

755 version_list, 

756 arch_list, 

757 processed, 

758 note, 

759 last_modified, 

760 _, 

761 _, 

762 _, 

763 _, 

764 _, 

765 _, 

766 _, 

767 ) = entry 

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

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

770 max_source_len, 

771 max_version_len, 

772 max_arch_len, 

773 ) 

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

775 else: 

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

777 max_source_len, 

778 max_version_len, 

779 max_arch_len, 

780 ) 

781 msg += format % ( 

782 source, 

783 version_list, 

784 arch_list, 

785 note, 

786 time_pp(last_modified), 

787 ) 

788 

789 if msg: 

790 print(type.upper()) 

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

792 print() 

793 print(msg) 

794 print( 

795 ( 

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

797 % ( 

798 source_count, 

799 type, 

800 plural(source_count), 

801 total_count, 

802 type, 

803 plural(total_count), 

804 total_pending, 

805 type, 

806 plural(total_pending), 

807 ) 

808 ) 

809 ) 

810 print() 

811 

812 

813################################################################################ 

814 

815 

816def main() -> None: 

817 global Cnf 

818 

819 Cnf = utils.get_conf() 

820 Arguments = [ 

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

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

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

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

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

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

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

828 ] 

829 for i in ["help"]: 

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

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

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

833 

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

835 

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

837 if Options["Help"]: 

838 usage() 

839 

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

841 header() 

842 

843 queue_names = [] 

844 

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

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

847 queue_names.append(i) 

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

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

850 else: 

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

852 

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

854 

855 f = None 

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

857 # Open the report file 

858 f = sys.stdout 

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

860 if filename822: 

861 f = open(filename822, "w") 

862 

863 session = DBConn().session() 

864 

865 for queue_name in queue_names: 

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

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

868 process_queue(queue, f, rrd_dir) 

869 else: 

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

871 

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

873 f.close() 

874 

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

876 footer() 

877 

878 

879################################################################################ 

880 

881 

882if __name__ == "__main__": 

883 main()