mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-06-25 08:09:18 +02:00
cleanup modal: list stale files to remove; storage restructure: documents/ → {objet}/
This commit is contained in:
211
scripts/migrate-storage-paths.php
Executable file
211
scripts/migrate-storage-paths.php
Executable file
@@ -0,0 +1,211 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/**
|
||||
* Migration script: move files from theses/ and documents/ to {objet}/YYYY/...
|
||||
* and update file_path in the thesis_files table.
|
||||
*
|
||||
* Self-contained — no framework bootstrap needed.
|
||||
*
|
||||
* Usage:
|
||||
* php scripts/migrate-storage-paths.php [--dry-run]
|
||||
*
|
||||
* What it does:
|
||||
* 1. Reads all thesis_files rows where file_path starts with theses/ or documents/
|
||||
* 2. For each row, looks up the parent thesis's objet field
|
||||
* 3. Constructs the new path: {objet}/YYYY/FOLDERNAME/filename
|
||||
* 4. Creates the target directory if needed
|
||||
* 5. Renames the file on disk
|
||||
* 6. Updates file_path in the DB
|
||||
* 7. Cleans up empty directories under theses/ and documents/
|
||||
*
|
||||
* Dry-run mode prints what would be done without making changes.
|
||||
*
|
||||
* Note: Orphaned files on disk (no DB row) are NOT moved — use:
|
||||
* find storage/theses storage/documents -type f 2>/dev/null
|
||||
*/
|
||||
|
||||
$dryRun = in_array('--dry-run', $argv, true);
|
||||
|
||||
echo ($dryRun ? "[DRY RUN] " : "") . "Storage path migration: theses/ | documents/ → {objet}/YYYY/...\n";
|
||||
echo "─────────────────────────────────────────────────────────────\n\n";
|
||||
|
||||
// ── Auto-detect paths ──────────────────────────────────────────────────────
|
||||
// Remote deploy: repo root = /var/www/xamxam, storage in storage/
|
||||
// Local dev: script in scripts/, storage in ../app/storage/
|
||||
$repoRoot = file_exists(__DIR__ . '/bootstrap.php') ? __DIR__ : dirname(__DIR__);
|
||||
|
||||
// DB path: try xamxam.db first, fall back to database.sqlite
|
||||
$dbPath = $repoRoot . '/storage/xamxam.db';
|
||||
if (!file_exists($dbPath)) {
|
||||
$dbPath = $repoRoot . '/storage/database.sqlite';
|
||||
}
|
||||
$storageRoot = $repoRoot . '/storage';
|
||||
|
||||
echo "Repo root: {$repoRoot}\n";
|
||||
echo "DB path: {$dbPath}\n";
|
||||
echo "Storage root: {$storageRoot}\n\n";
|
||||
|
||||
if (!file_exists($dbPath)) {
|
||||
die("ERROR: Database not found at {$dbPath}\n");
|
||||
}
|
||||
|
||||
// ── Connect to DB ───────────────────────────────────────────────────────────
|
||||
$pdo = new PDO('sqlite:' . $dbPath);
|
||||
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
|
||||
// Fetch all files that need migration
|
||||
$sql = "SELECT tf.id, tf.thesis_id, tf.file_path, tf.file_name, t.objet, t.year
|
||||
FROM thesis_files tf
|
||||
JOIN theses t ON t.id = tf.thesis_id
|
||||
WHERE (tf.file_path LIKE 'theses/%' OR tf.file_path LIKE 'documents/%')
|
||||
AND t.objet IS NOT NULL
|
||||
ORDER BY tf.id ASC";
|
||||
|
||||
$stmt = $pdo->query($sql);
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$total = count($rows);
|
||||
echo "Found {$total} file(s) to migrate.\n\n";
|
||||
|
||||
if ($total === 0) {
|
||||
echo "Nothing to do.\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
$moved = 0;
|
||||
$skipped = 0;
|
||||
$errors = [];
|
||||
$folderCache = [];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$fileId = (int)$row['id'];
|
||||
$thesisId = (int)$row['thesis_id'];
|
||||
$oldPath = $row['file_path'];
|
||||
$objet = $row['objet'];
|
||||
$year = $row['year'];
|
||||
|
||||
// Parse old path: theses/2025/FOLDERNAME/file.pdf or documents/2025/FOLDERNAME/file.pdf
|
||||
$parts = explode('/', $oldPath);
|
||||
if (count($parts) < 4) {
|
||||
$skipped++;
|
||||
$errors[] = "SKIP file #{$fileId}: unexpected path format '{$oldPath}'";
|
||||
continue;
|
||||
}
|
||||
|
||||
$yearDir = $parts[1]; // '2025'
|
||||
$folderName = $parts[2]; // '2025_SMITH_Titre'
|
||||
$fileNameOnly = implode('/', array_slice($parts, 3)); // rest of path
|
||||
|
||||
// Construct new path
|
||||
$newPath = $objet . '/' . $yearDir . '/' . $folderName . '/' . $fileNameOnly;
|
||||
|
||||
// Skip if paths are identical
|
||||
if ($newPath === $oldPath) {
|
||||
$skipped++;
|
||||
echo " #{$fileId} SKIP: already correct '{$oldPath}'\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
$oldAbs = $storageRoot . '/' . $oldPath;
|
||||
$newAbs = $storageRoot . '/' . $newPath;
|
||||
|
||||
// Ensure target directory exists
|
||||
$newDir = dirname($newAbs);
|
||||
if (!isset($folderCache[$newDir])) {
|
||||
if (!is_dir($newDir)) {
|
||||
if (!$dryRun) {
|
||||
mkdir($newDir, 0755, true);
|
||||
}
|
||||
echo " MKDIR {$newDir}\n";
|
||||
}
|
||||
$folderCache[$newDir] = true;
|
||||
}
|
||||
|
||||
// Check source exists
|
||||
if (!file_exists($oldAbs)) {
|
||||
// If the target already exists (moved by a previous duplicate row), just update DB
|
||||
if (file_exists($newAbs)) {
|
||||
echo " #{$fileId} {$oldPath}\n → {$newPath} (target exists, DB-only update)\n";
|
||||
if (!$dryRun) {
|
||||
$pdo->prepare('UPDATE thesis_files SET file_path = ? WHERE id = ?')
|
||||
->execute([$newPath, $fileId]);
|
||||
}
|
||||
$moved++;
|
||||
continue;
|
||||
}
|
||||
$skipped++;
|
||||
echo " #{$fileId} SKIP: source not found '{$oldAbs}'\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check target doesn't exist
|
||||
if (file_exists($newAbs)) {
|
||||
$skipped++;
|
||||
echo " #{$fileId} SKIP: target already exists '{$newAbs}'\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
echo " #{$fileId} {$oldPath}\n → {$newPath}\n";
|
||||
|
||||
if (!$dryRun) {
|
||||
if (!rename($oldAbs, $newAbs)) {
|
||||
$skipped++;
|
||||
$errors[] = "FAIL file #{$fileId}: rename failed '{$oldAbs}' → '{$newAbs}'";
|
||||
echo " ✗ rename failed\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
$pdo->prepare('UPDATE thesis_files SET file_path = ? WHERE id = ?')
|
||||
->execute([$newPath, $fileId]);
|
||||
}
|
||||
|
||||
$moved++;
|
||||
}
|
||||
|
||||
echo "\n─────────────────────────────────────────────────────────────\n";
|
||||
echo "Summary: {$moved} moved, {$skipped} skipped, " . count($errors) . " errors\n";
|
||||
|
||||
if (!empty($errors)) {
|
||||
echo "\nErrors:\n";
|
||||
foreach ($errors as $err) {
|
||||
echo " {$err}\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up empty directories
|
||||
if (!$dryRun) {
|
||||
$oldRoots = [$storageRoot . '/theses', $storageRoot . '/documents'];
|
||||
foreach ($oldRoots as $oldRoot) {
|
||||
removeEmptyDirs($oldRoot);
|
||||
}
|
||||
echo "\nCleaned up empty directories.\n";
|
||||
}
|
||||
|
||||
if ($dryRun) {
|
||||
echo "\nRun without --dry-run to apply changes.\n";
|
||||
}
|
||||
|
||||
exit(empty($errors) ? 0 : 1);
|
||||
|
||||
// ── Helpers ────────────────────────────────────────────────────────────────
|
||||
|
||||
function removeEmptyDirs(string $dir): void
|
||||
{
|
||||
if (!is_dir($dir)) {
|
||||
return;
|
||||
}
|
||||
$items = @scandir($dir);
|
||||
if ($items === false) {
|
||||
return;
|
||||
}
|
||||
foreach ($items as $item) {
|
||||
if ($item === '.' || $item === '..') {
|
||||
continue;
|
||||
}
|
||||
$path = $dir . '/' . $item;
|
||||
if (is_dir($path)) {
|
||||
removeEmptyDirs($path);
|
||||
}
|
||||
}
|
||||
@rmdir($dir);
|
||||
}
|
||||
Reference in New Issue
Block a user