From 2169bc4975e154eb3ba002fcd82fa4db9ed7695b Mon Sep 17 00:00:00 2001 From: Alexandre <44178713+alexbelgium@users.noreply.github.com> Date: Tue, 26 Aug 2025 08:45:57 +0200 Subject: [PATCH] Add species_tools.php for species management --- scripts/species_tools.php | 284 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 284 insertions(+) create mode 100644 scripts/species_tools.php diff --git a/scripts/species_tools.php b/scripts/species_tools.php new file mode 100644 index 0000000..ddeec81 --- /dev/null +++ b/scripts/species_tools.php @@ -0,0 +1,284 @@ +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'); +?> + + +
+ + + + + + + + + + + + + + +fetchArray(SQLITE3_ASSOC)) { + $common = htmlspecialchars($row['Com_Name'], ENT_QUOTES); + $scient = htmlspecialchars($row['Sci_Name'], ENT_QUOTES); + $count = (int)$row['Count']; + $max_confidence = round((float)$row['MaxConfidence'] * 100, 1); + $identifier = str_replace("'", '', $row['Sci_Name'].'_'.$row['Com_Name']); + + $common_link = "{$common}"; + + $is_excluded = in_array($identifier, $excluded_species, true); + $is_whitelisted = in_array($identifier, $whitelisted_species, true); + + $excl_cell = $is_excluded + ? "" + : ""; + + $white_cell = $is_whitelisted + ? "" + : ""; + + echo "" + . "" + . "" + . "" + . "" + . ""; +} ?> + +
Common NameScientific NameIdentificationsMax ConfidenceThresholdExcludedWhitelistedDelete
{$common_link}{$scient}{$count}{$max_confidence}%0.0000".$excl_cell."".$white_cell."
+
+ +