Created
February 25, 2021 07:42
-
-
Save eghojansu/e05b797eec0fc7d570e5b6779d3c5fcc to your computer and use it in GitHub Desktop.
Directory Cleaner with Schedule to Delete after defined lifespan
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@ECHO OFF | |
REM Cleaner.php runner | |
SET PHP_EXE="C:\XAMPP73\php\php.exe" | |
SET CLEANER=%~dp0cleaner.php | |
%PHP_EXE% %CLEANER% %* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Author: Eko Kurniawan | |
* Created At: February 25, 2021 08:07 AM +0700 | |
* | |
* Description: This utility used to clean/delete files on spesific condition. | |
*/ | |
if ('cli' !== PHP_SAPI) { | |
exit(1); | |
} | |
const APP_NAME = 'DirectoryCleaner'; | |
const APP_VERSION = 'alpha-1.0.0'; | |
$commands = array( | |
'clean' => function() { | |
writeln('cleaning in progress...'); | |
$startTime = microtime(true); | |
$successMove = 0; | |
$failureMove = 0; | |
$successDelete = 0; | |
$failureDelete = 0; | |
$skipDelete = 0; | |
$successDirectory = 0; | |
$failureDirectory = 0; | |
$skipDirectory = 0; | |
$ignoreDirectories = array(); | |
$directories = array_map(function(string $pair) { | |
return explode(';', $pair); | |
}, setupGet('directory')); | |
list($deleteInDays) = setupGet('lifespan'); | |
$scheduleToDelete = (new DateTime())->modify("+{$deleteInDays} days")->getTimestamp(); | |
$db = db(); | |
$insert = $db->prepare(<<<SQL | |
insert into entries (base_name, path_name, source_path, file_type, created_by, group_name, created_at, accessed_at, modified_at, inserted_at, scheduled_to_delete_at) values | |
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) | |
SQL | |
); | |
foreach ($directories as list($target, $destination)) { | |
$targetLength = strlen($target); | |
foreach (files($target) as $sourcePath => $file) { | |
$pathName = $destination . substr($sourcePath, $targetLength); | |
if ($file->isDir()) { | |
if (!is_dir($pathName)) { | |
mkdir($pathName, 0755, true); | |
} | |
$ignoreDirectories[$pathName] = true; | |
continue; | |
} | |
$insert->execute(array( | |
$file->getBasename(), | |
$pathName, | |
$sourcePath, | |
$file->getType(), | |
$file->getOwner(), | |
$file->getGroup(), | |
$file->getCTime(), | |
$file->getATime(), | |
$file->getMTime(), | |
time(), | |
$scheduleToDelete, | |
)); | |
if (@rename($sourcePath, $pathName)) { | |
$successMove++; | |
} else { | |
$failureMove++; | |
} | |
} | |
} | |
// deletes | |
$deletes = $db->query('select * from entries where deleted_at is not null and scheduled_to_delete_at < ' . time()); | |
$update = $db->prepare('update entries set deleted_at = ?, remark = ? where id = ?'); | |
foreach ($deletes as $row) { | |
if (file_exists($row['path_name'])) { | |
if (@unlink($row['path_name'])) { | |
$successDelete++; | |
$remark = 'success'; | |
} else { | |
$failureDelete++; | |
$remark = 'failed'; | |
} | |
} else { | |
$skipDelete++; | |
$remark = 'skipped'; | |
} | |
$update->execute(array(time(), $remark, $row['id'])); | |
} | |
// remove empty folder | |
foreach ($directories as $pairs) { | |
foreach ($pairs as $pair) { | |
foreach (files($pair, true) as $pathName => $file) { | |
if ($file->isDir()) { | |
if (glob($pathName . '/*') || isset($ignoreDirectories[$pathName])) { | |
$skipDirectory++; | |
} else { | |
if (@rmdir($pathName)) { | |
$successDirectory++; | |
} else { | |
$failureDirectory++; | |
} | |
} | |
} | |
} | |
} | |
} | |
$benchmark = microtime(true) - $startTime; | |
writeln(' done [', number_format($benchmark, 6, ',', '.'), ' s]'); | |
writeln(' Moving files (', $successMove, ' success, ', $failureMove, ' failure)'); | |
writeln(' Delete files (', $successDelete, ' success, ', $failureDelete, ' failure, ', $skipDelete, ' skipped)'); | |
writeln(' Delete directories (', $successDirectory, ' success, ', $failureDirectory, ' failure, ', $skipDirectory, ' skipped)'); | |
}, | |
'add-directory' => function() { | |
$pair = arg(); | |
if (!$pair || false === strpos($pair, ';')) { | |
error('No pair directory given', PHP_EOL, ' Directory should be in form TARGET:DESTINATION.'); | |
} | |
list($target, $destination) = explode(';', $pair); | |
if (!$target || !$destination || !is_dir($target) || !is_dir($destination)) { | |
error('Directory pair not exists: ' . $pair); | |
} | |
if ($target == $destination) { | |
error('Same target and destination is forbidden'); | |
} | |
setup('directory', false, fixslash($target) . ';' . fixslash($destination)); | |
writeln('Directory added.'); | |
}, | |
'set-lifespan' => function() { | |
$lifespan = arg(); | |
if (!$lifespan || !is_numeric($lifespan) || !is_int($lifespan + 0)) { | |
error('Life span should be a number of days.'); | |
} | |
setup('lifespan', false, $lifespan, true); | |
writeln('File lifespan saved.'); | |
}, | |
'show-setup' => function() { | |
$setups = db()->query('select * from setup'); | |
writeln('Current setup:'); | |
foreach ($setups as $row) { | |
writeln(' ', $row['name'], ': ', $row['content']); | |
} | |
if (empty($row)) { | |
writeln(' ~ no setup ~'); | |
} | |
}, | |
'count' => function() { | |
$count = db()->query('select count(*) from entries'); | |
writeln('Entries contains ', $count->fetchColumn(0), ' rows'); | |
}, | |
'reset' => function() { | |
$affected = db()->exec('delete from entries'); | |
writeln('Entries deleted [', $affected, ' rows affected]'); | |
}, | |
); | |
$commands['show'] = function() use ($commands) { | |
writeln(APP_NAME, PHP_EOL, 'Version: ', APP_VERSION, PHP_EOL, PHP_EOL, 'Available commands:'); | |
foreach ($commands as $key => $value) { | |
writeln(' ', $key); | |
} | |
}; | |
function arg(int $position = 1): ?string { | |
return $GLOBALS['argv'][$position + 1] ?? null; | |
} | |
function write(?string ...$texts): void { | |
foreach ($texts as $text) { | |
echo $text; | |
} | |
} | |
function writeln(?string ...$texts): void { | |
write(...$texts); | |
write(PHP_EOL); | |
} | |
function error(?string ...$texts): void { | |
writeln(...$texts); | |
exit(1); | |
} | |
function fixslash(string $str): string { | |
return rtrim(strtr($str, '\\', '/'), '/'); | |
} | |
function setup(string $name, bool $check = true, string $value = null, bool $replace = false): array { | |
$db = db(); | |
$sql = 'SELECT * FROM setup WHERE name = ' . $db->quote($name); | |
$query = $db->query($sql); | |
if ((!$query || !$rows = $query->fetchAll(PDO::FETCH_ASSOC)) && $check) { | |
error("Setup not found: {$name}."); | |
} | |
if ($value && !$check) { | |
if ($replace && isset($rows) && $rows) { | |
$db->query('UPDATE setup SET content = ' . $db->quote($value) . ' WHERE id = ' . $db->quote($rows[0]['id'], PDO::PARAM_INT)); | |
} else { | |
$db->query('INSERT INTO setup (name, content) VALUES (' . $db->quote($name) . ', ' . $db->quote($value) . ')'); | |
$rows = array(array('id' => $db->lastInsertId(), 'name' => $name, 'content' => $value)); | |
} | |
} | |
return $rows; | |
} | |
function setupGet(string $name, bool $check = true): array { | |
return array_column(setup($name, $check), 'content'); | |
} | |
function files(string $directory, bool $childFirst = false): Iterator { | |
$mode = FilesystemIterator::SKIP_DOTS | FilesystemIterator::UNIX_PATHS | FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::KEY_AS_PATHNAME; | |
$directory = new RecursiveDirectoryIterator($directory, $mode); | |
$iteratorMode = $childFirst ? RecursiveIteratorIterator::CHILD_FIRST : RecursiveIteratorIterator::SELF_FIRST; | |
return new RecursiveIteratorIterator($directory, $iteratorMode); | |
} | |
function db(): PDO { | |
static $pdo; | |
if (!$pdo) { | |
try { | |
$db = __DIR__ . '/cleaner.db'; | |
$exists = file_exists($db); | |
$pdo = new PDO('sqlite:' . $db); | |
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); | |
if (!$exists) { | |
$pdo->exec(<<<'SQL' | |
CREATE TABLE setup ( | |
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, | |
name VARCHAR(16) NOT NULL, | |
content VARCHAR(255) NULL | |
); | |
CREATE TABLE entries ( | |
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, | |
base_name TEXT NOT NULL, | |
path_name TEXT NOT NULL, | |
source_path TEXT NULL, | |
file_type TEXT NULL, | |
created_by TEXT NULL, | |
group_name TEXT NULL, | |
created_at INTEGER NULL, | |
accessed_at INTEGER NULL, | |
modified_at INTEGER NULL, | |
inserted_at INTEGER NULL, | |
updated_at INTEGER NULL, | |
deleted_at INTEGER NULL, | |
remark TEXT NULL, | |
scheduled_to_delete_at INTEGER NULL | |
); | |
CREATE INDEX setup_name_idx ON setup (name); | |
CREATE INDEX entry_path_name_idx ON entries (path_name); | |
CREATE INDEX entry_source_path_idx ON entries (source_path); | |
SQL | |
); | |
} | |
} catch (Throwable $e) { | |
error($e->getMessage()); | |
} | |
} | |
return $pdo; | |
} | |
$command = $argv[1] ?? 'show'; | |
$commands[$command](); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment