jobs.php 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
  1. <?php
  2. require_once __DIR__ . '/db.php';
  3. // Apply a single-field change to a job. If the column is audited
  4. // (status or ack) and the value changed, append a job_history row.
  5. function apply_job_change(array $job, string $col, $new, string $actor): void {
  6. $pdo = db();
  7. $old = $job[$col];
  8. if ((string) $old === (string) $new) return;
  9. // Use BEGIN IMMEDIATE so the write lock is acquired up front (PDO's
  10. // beginTransaction uses BEGIN DEFERRED, which can short-circuit
  11. // SQLite's busy handler). Application-level retry on SQLITE_BUSY
  12. // is a backstop in case the PDO timeout isn't honoured.
  13. $attempts = 0;
  14. while (true) {
  15. $attempts++;
  16. try {
  17. $pdo->exec('BEGIN IMMEDIATE');
  18. break;
  19. } catch (PDOException $e) {
  20. if ($attempts >= 50 || !is_busy_error($e)) throw $e;
  21. usleep(100000); // 100 ms
  22. }
  23. }
  24. try {
  25. $stmt = $pdo->prepare("UPDATE jobs SET $col = ?, updated_at = datetime('now') WHERE id = ?");
  26. $stmt->execute([$new, $job['id']]);
  27. if (in_array($col, ['status', 'ack'], true)) {
  28. $hist = $pdo->prepare(
  29. 'INSERT INTO job_history(job_id, field, old_value, new_value, actor)
  30. VALUES (?, ?, ?, ?, ?)'
  31. );
  32. $hist->execute([$job['id'], $col, (string) $old, (string) $new, $actor]);
  33. }
  34. $pdo->exec('COMMIT');
  35. } catch (Throwable $e) {
  36. $pdo->exec('ROLLBACK');
  37. throw $e;
  38. }
  39. }
  40. function is_busy_error(PDOException $e): bool {
  41. return stripos($e->getMessage(), 'database is locked') !== false
  42. || stripos($e->getMessage(), 'database table is locked') !== false;
  43. }
  44. // Parse the same M-D / M-D-Y forms the original PDQUpdates.php accepted.
  45. // Returns ISO 'YYYY-MM-DD' or null (which clears the date).
  46. function parse_due_date(string $raw): ?string {
  47. $raw = trim(str_replace('/', '-', $raw));
  48. if ($raw === '') return null;
  49. $parts = explode('-', $raw);
  50. $today = getdate();
  51. if (count($parts) === 3) {
  52. [$m, $d, $y] = $parts;
  53. } elseif (count($parts) === 2) {
  54. [$m, $d] = $parts;
  55. $y = $today['year'];
  56. } else {
  57. return null;
  58. }
  59. $m = (int) $m; $d = (int) $d; $y = (int) $y;
  60. if ($y < 100) $y += 2000;
  61. if (!checkdate($m, $d, $y)) return null;
  62. return sprintf('%04d-%02d-%02d', $y, $m, $d);
  63. }