busyTimeout(1000); /* Paths / lists */ $base_symlink = $home . '/BirdSongs/Extracted/By_Date'; $base = realpath($base_symlink); // used only for safety checks $exclude_file = __DIR__ . '/exclude_species_list.txt'; $whitelist_file = __DIR__ . '/whitelist_species_list.txt'; $excluded_species = file_exists($exclude_file) ? file($exclude_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) : []; $whitelisted_species = file_exists($whitelist_file) ? file($whitelist_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) : []; $config = get_config(); $sf_thresh = isset($config['SF_THRESH']) ? (float)$config['SF_THRESH'] : 0.0; /* ---------- helpers (tiny, single-purpose) ---------- */ function join_path(...$parts): string { return preg_replace('#/+#', '/', implode('/', $parts)); } function can_unlink(string $p): bool { // unlink-able: symlink (even dangling) or regular file return is_link($p) || is_file($p); } function under_base(string $path, string $base): bool { if ($base === false) return false; $baseReal = rtrim(realpath($base) ?: $base, DIRECTORY_SEPARATOR); $resolved = realpath($path); if ($resolved === false) { $parent = realpath(dirname($path)); if ($parent === false) return false; $resolved = $parent . DIRECTORY_SEPARATOR . basename($path); } return $resolved === $baseReal || strpos($resolved, $baseReal . DIRECTORY_SEPARATOR) === 0; } /** * Collect detection count, files to delete (unique), first scientific name, * and dirs to try rmdir later — for a given species. */ function collect_species_targets(SQLite3 $db, string $species, string $home, $base): array { $stmt = $db->prepare('SELECT Date, Com_Name, Sci_Name, File_Name FROM detections WHERE Com_Name = :name'); ensure_db_ok($stmt); $stmt->bindValue(':name', $species, SQLITE3_TEXT); $res = $stmt->execute(); $count = 0; $files = []; $dirs = []; $sci = null; while ($row = $res->fetchArray(SQLITE3_ASSOC)) { $count++; if ($sci === null) $sci = $row['Sci_Name']; $dir = str_replace([' ', "'"], ['_', ''], $row['Com_Name']); $candidates = [ join_path($home, 'BirdSongs/Extracted/By_Date', $row['Date'], $dir, $row['File_Name']), join_path($home, 'BirdSongs/Extracted/By_Date/shifted', $row['Date'], $dir, $row['File_Name']), ]; foreach ($candidates as $c) { if (can_unlink($c) && under_base($c, $base)) { $files[$c] = true; $dirs[] = dirname($c); continue; } $d = realpath(dirname($c)); if ($d !== false) { $alt = $d . DIRECTORY_SEPARATOR . basename($c); if (can_unlink($alt) && under_base($alt, $base)) { $files[$alt] = true; $dirs[] = dirname($alt); } } } } return [ 'count' => $count, 'files' => array_keys($files), 'dirs' => array_values(array_unique($dirs)), 'sci' => $sci, ]; } /* ---------- toggle exclude/whitelist ---------- */ if (isset($_GET['toggle'], $_GET['species'], $_GET['action'])) { $list = $_GET['toggle']; $species = htmlspecialchars_decode($_GET['species'], ENT_QUOTES); if ($list === 'exclude') { $file = $exclude_file; } elseif ($list === 'whitelist') { $file = $whitelist_file; } else { header('Content-Type: text/plain'); echo 'Invalid list type'; exit; } $lines = file_exists($file) ? file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) : []; if ($_GET['action'] === 'add') { if (!in_array($species, $lines, true)) $lines[] = $species; } else { $lines = array_values(array_filter($lines, fn($l) => $l !== $species)); } file_put_contents($file, implode("\n", $lines) . (empty($lines) ? "" : "\n")); header('Content-Type: text/plain'); echo 'OK'; exit; } /* ---------- count (keeps your old "getcounts=" API) ---------- */ if (isset($_GET['getcounts'])) { header('Content-Type: application/json'); if ($base === false) { http_response_code(500); exit(json_encode(['error' => 'Base directory not found'])); } $species = htmlspecialchars_decode($_GET['getcounts'], ENT_QUOTES); $info = collect_species_targets($db, $species, $home, $base); echo json_encode(['count' => $info['count'], 'files' => count($info['files'])]); exit; } /* ---------- delete (keeps your old "delete=" API) ---------- */ if (isset($_GET['delete'])) { header('Content-Type: application/json'); if ($base === false) { http_response_code(500); exit(json_encode(['error' => 'Base directory not found'])); } $species = htmlspecialchars_decode($_GET['delete'], ENT_QUOTES); $info = collect_species_targets($db, $species, $home, $base); $deleted = 0; foreach ($info['files'] as $fp) { if (!under_base($fp, $base)) continue; if (can_unlink($fp) && @unlink($fp)) { $deleted++; // thumbnails: "file.wav.png" and "file.png" foreach ([$fp . '.png', preg_replace('/\.[^.]+$/', '.png', $fp)] as $png) { if (can_unlink($png)) @unlink($png); } } } foreach ($info['dirs'] as $dir) { if (under_base($dir, $base)) @rmdir($dir); // best effort } // DB rows $del = $db->prepare('DELETE FROM detections WHERE Com_Name = :name'); ensure_db_ok($del); $del->bindValue(':name', $species, SQLITE3_TEXT); $del->execute(); $lines_deleted = $db->changes(); echo json_encode(['lines' => $lines_deleted, 'files' => $deleted]); exit; } /* ---------- page (unchanged semantics; minor tidy) ---------- */ $result = fetch_species_array('alphabetical'); ?>
| Common Name | Scientific Name | Identifications | Max Confidence | Threshold | Excluded | Whitelisted | Delete |
|---|---|---|---|---|---|---|---|
| {$common_link} | {$scient} | {$count} | " . "{$max_confidence}% | " . "0.0000 | " . "".$excl_cell." | " . "".$white_cell." | " . "