Skip to content

Instantly share code, notes, and snippets.

@eghojansu
Created February 25, 2021 07:42
Show Gist options
  • Save eghojansu/e05b797eec0fc7d570e5b6779d3c5fcc to your computer and use it in GitHub Desktop.
Save eghojansu/e05b797eec0fc7d570e5b6779d3c5fcc to your computer and use it in GitHub Desktop.
Directory Cleaner with Schedule to Delete after defined lifespan
@ECHO OFF
REM Cleaner.php runner
SET PHP_EXE="C:\XAMPP73\php\php.exe"
SET CLEANER=%~dp0cleaner.php
%PHP_EXE% %CLEANER% %*
<?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