How to delete the next file in php - php

Let's say I have a folder containing 99tf.txt, 40.txt, 65.txt , in any order
If the current script var is 40.txt: I would like it to delete 65.txt (or the next file)
I was looking into something like that:
$file='40.txt';
if ($handle = opendir('./log/')) {
$entry= readdir($handle);
//move pointer to 40.txt
while ($file != $entry && $entry !== false) {
$entry = readdir($handle)
}
//go to the next file
$entry = readdir($handle)
if(is_file('./log/'.$entry)){
unlink('./log/'.$entry);
}
}
But I would like to avoid to go into a loop each time since there could be a lot of files in the folder.
So is there a way to change the $handle pointer to the '$file' directly and delete the next file?

If you don't mind using scandir, then this should work better for you.
$file = '40.txt';
$contents = scandir('./log/');
// Should already be sorted, but do again for safe measure
sort($contents);
// Make sure the file is in there.
if (false !== $index = array_search($file, $contents)) {
// If the file is at the end, do nothing.
if ($index !== count($contents)) {
// Remove the next index
unlink('./log/' . $contents[$index + 1]);
}
}
In regard to the order not mattering, you don't need to sort it. Worth noting however, is that your method, takes longer, but uses less memory, while this method is the reverse, much faster, but potentially more memory consumption.

<?php
$file = '40.txt';
$scan_folder = scandir('./log/');
$num_files = count($scan_folder);
if ($num_files > 1) {
$file_key = array_search($file, $scan_folder) +1;
unlink('./log/'.$file_key);
} else {
// here you will preserve only 1 file all others can be removed every time this script is executed
}
?>

Related

How to calculate entire directory size with FTP access using PHP

I have a number of different hosting accounts set up for clients and need to calculate the amount of storage space being used on each account, which would update regularly.
I have a database set up to record each clients storage usage.
I attempted this first using a PHP file on each account, run by a Cron Job. If run manually by myself, it would output the correct filesize and update the correct size to the database, although when run from the Cron Job, it would output 0.
I then attempted to run this file from a Cron Job from the main account but figured this wouldn't actually work as my hosting would block files from another server and I would end up with the same result as before.
I am now playing around with FTP access to each account from a Cron Job from the main account which looks something like below, the only problem is I don't know how to calculate directory size rather than single file sizes using FTP access, and don't know how to reiterate this way? Hoping somebody might be able to help here before I end up going around in circles?
I will also add the previous first attempt too.
$ftp_conn = ftp_connect($ftp_host, 21, 420) or die("Could not connect to server");
$ftp_login = ftp_login($ftp_conn, $ftp_username, 'mypassword');
$total_size = 0;
$contents = ftp_nlist($ftp_conn, ".");
// output $contents
foreach($contents as $folder){
while($search == true){
if($folder == '..' || $folder == '.'){
} else {
$file = $folder;
$res = ftp_size($ftp_conn, $file);
if ($res != -1) {
$total_size = $total_size + $res;
} else {
$total_size = $total_size;
}
}
}
}
ftp_close($ftp_conn);
This doesn't work as it doesn't calculate folder sizes and I don't know how to open the reiterate using this method?
This second script did work but would only work if opened manually, and return 0 if run by the cron job.
class Directory_Calculator {
function calculate_whole_directory($directory)
{
if ($handle = opendir($directory))
{
$size = 0;
$folders = 0;
$files = 0;
while (false !== ($file = readdir($handle)))
{
if ($file != "." && $file != "..")
{
if(is_dir($directory.$file))
{
$array = $this->calculate_whole_directory($directory.$file.'/');
$size += $array['size'];
$files += $array['files'];
$folders += $array['folders'];
}
else
{
$size += filesize($directory.$file);
$files++;
}
}
}
closedir($handle);
}
$folders++;
return array('size' => $size, 'files' => $files, 'folders' => $folders);
}
}
/* Path to Directory - IMPORTANT: with '/' at the end */
$directory = '../public_html/';
// return an array with: size, total files & folders
$array = $directory_size->size($directory);
$size_of_site = $array['size'];
echo $size_of_site;
Please bare in mind that I am currently testing and none of the MySQLi or PHP scripts are secure yet.
If your server supports MLSD command and you have PHP 7.2 or newer, you can use ftp_mlsd function:
function calculate_whole_directory($ftp_conn, $directory)
{
$files = ftp_mlsd($ftp_conn, $directory) or die("Cannot list $directory");
$result = 0;
foreach ($files as $file)
{
if (($file["type"] == "cdir") || ($file["type"] == "pdir"))
{
$size = 0;
}
else if ($file["type"] == "dir")
{
$size = calculate_whole_directory($ftp_conn, $directory."/".$file["name"]);
}
else
{
$size = intval($file["size"]);
}
$result += $size;
}
return $result;
}
If you do not have PHP 7.2, you can try to implement the MLSD command on your own. For a start, see user comment of the ftp_rawlist command:
https://www.php.net/manual/en/function.ftp-rawlist.php#101071
If you cannot use MLSD, you will particularly have problems telling if an entry is a file or folder. While you can use the ftp_size trick, as you do, calling ftp_size for each entry can take ages.
But if you need to work against one specific FTP server only, you can use ftp_rawlist to retrieve a file listing in a platform-specific format and parse that.
The following code assumes a common *nix format.
function calculate_whole_directory($ftp_conn, $directory)
{
$lines = ftp_rawlist($ftp_conn, $directory) or die("Cannot list $directory");
$result = 0;
foreach ($lines as $line)
{
$tokens = preg_split("/\s+/", $line, 9);
$name = $tokens[8];
if ($tokens[0][0] === 'd')
{
$size = calculate_whole_directory($ftp_conn, "$directory/$name");
}
else
{
$size = intval($tokens[4]);
}
$result += $size;
}
return $result;
}
Based on PHP FTP recursive directory listing.
Regarding cron: I'd guess that the cron does not start your script with a correct working directory, so you calculate a size of a non-existing directory.
Use an absolute path here:
$directory = '../public_html/';
Though you better add some error checking so that you can see yourself what goes wrong.

excluding certain files based on filename

So I have a code snippet that reads a directory and performs certain actions on the files inside. I have an array of filenames to exclude. My code looks like this:
$excluded = array(".","..","thumbs.db");
if($fh = #opendir($dir))
{
while(false !== ($file = #readdir($fh)))
{
if(in_array(strtolower($file),$excluded))
{
continue;
}
//do processing here...
Now, I want it that zip files also should be excluded. Since I do not know what name they might exist in, I will need to skip them based on extension.
Now I know I can split the filename and look at the last element to see if it zip etc, but what I wanted to ask is, is there a way to achieve it within the constraints of what is coded already - like adding it like this, and then tweaking the loop to handle it...
$excluded = array(".","..","thumbs.db","*.zip");
This should do the trick:
$excluded = array(".","..","thumbs.db");
$excludedExtensions = array(".zip",".rar");
if($fh = #opendir($dir))
{
while(false !== ($file = #readdir($fh)))
{
if(in_array(strtolower($file),$excluded) ||
in_array(strtolower(substr($file, -4)), $excludedExtensions) )
{
continue;
}
//do processing here...
It's not exactly what you're looking for, but i don't think it's possible to do the way you wanted it to :(
-------------------------------------------------------------------------------------
EDIT
I wanted to make a more reliable way to do this, since there is some files which have 4 or even 5 letters in their extension. After looking though the PHP manual, i found this:
$excluded = array(".","..","thumbs.db");
$excludedExtensions = array(".zip",".rar", ".7z", ".jpeg", ".phtml");
if($fh = #opendir($dir))
{
while(false !== ($file = #readdir($fh)))
{
$path_parts = pathinfo($file);
if(in_array(strtolower($file),$excluded) ||
in_array(strtolower($path_parts['extension'])) )
{
continue;
}
//do processing here...
See more here: PHP manual: pathinfo

PHP: How can I grab a single file from a directory without scanning entire directory?

I have a directory with 1.3 Million files that I need to move into a database. I just need to grab a single filename from the directory WITHOUT scanning the whole directory. It does not matter which file I grab as I will delete it when I am done with it and then move on to the next. Is this possible? All the examples I can find seem to scan the whole directory listing into an array. I only need to grab one at a time for processing... not 1.3 Million every time.
This should do it:
<?php
$h = opendir('./'); //Open the current directory
while (false !== ($entry = readdir($h))) {
if($entry != '.' && $entry != '..') { //Skips over . and ..
echo $entry; //Do whatever you need to do with the file
break; //Exit the loop so no more files are read
}
}
?>
readdir
Returns the name of the next entry in the directory. The entries are returned in the order in which they are stored by the filesystem.
Just obtain the directories iterator and look for the first entry that is a file:
foreach(new DirectoryIterator('.') as $file)
{
if ($file->isFile()) {
echo $file, "\n";
break;
}
}
This also ensures that your code is executed on some other file-system behaviour than the one you expect.
See DirectoryIterator and SplFileInfo.
readdir will do the trick. Check the exampl on that page but instead of doing the readdir call in the loop, just do it once. You'll get the first file in the directory.
Note: you might get ".", "..", and other similar responses depending on the server, so you might want to at least loop until you get a valid file.
do you want return first directory OR first file? both? use this:
create function "pickfirst" with 2 argument (address and mode dir or file?)
function pickfirst($address,$file) { // $file=false >> pick first dir , $file=true >> pick first file
$h = opendir($address);
while (false !== ($entry = readdir($h))) {
if($entry != '.' && $entry != '..' && ( ($file==false && !is_file($address.$entry)) || ($file==true && is_file($address.$entry)) ) )
{ return $entry; break; }
} // end while
} // end function
if you want pick first directory in your address set $file to false and if you want pick first file in your address set $file to true.
good luck :)

Auto delete all files after x-time

How do you auto delete all files under a sub directory after x-time (let say after 24 hours) - without using a cronjob command from server or pl. How can you do this just using PHP code or by just visiting the page without clicking something and the command auto runs.
Response for last comment from my first answer. I'm going to write code sample, so I've created another answer instead of addition one more comment.
To remove files with custom extension you have to implement code:
<?php
$path = dirname(__FILE__).'/files';
if ($handle = opendir($path)) {
while (false !== ($file = readdir($handle))) {
if ((time()-filectime($path.'/'.$file)) < 86400) { // 86400 = 60*60*24
if (preg_match('/\.txt$/i', $file)) {
unlink($path.'/'.$file);
}
}
}
}
?>
Comment: 1. This example uses regular expression /\.txt$/i, which means, that only files with extension txt will be removed. '$' sign means, that filename has to be ended with string '.txt'. Flag 'i' indicates, that comparison will be case-insensitive. More about preg_match() function.
Besides you can use strripos() function to search files with certain extension. Here is code snippet:
<?php
$path = dirname(__FILE__).'/files';
if ($handle = opendir($path)) {
while (false !== ($file = readdir($handle))) {
if ((time()-filectime($path.'/'.$file)) < 86400) { // 86400 = 60*60*24
if (strripos($file, '.txt') !== false) {
unlink($path.'/'.$file);
}
}
}
}
?>
Comment: This example seems more obvious. Result of strripos() also can be achieved with a combining of two functions: strrpos(strtolower($file), '.txt'), but, IMHO, it's a good rule to use less functions in your code to make it more readable and smaller. Please, read attentively warning on the page of strripos() function(return values block).
One more important notice: if you're using UNIX system, file removing could fail because of file permissions. You can check manual about chmod() function.
Good luck.
You can use PHP core functions filectime() and unlink() to check time of file creation and delete its file/files.
EDIT.
Code example:
if ($handle = opendir('/path/to/files')) {
while (false !== ($file = readdir($handle))) {
if (filectime($file)< (time()-86400)) { // 86400 = 60*60*24
unlink($file);
}
}
}
Well here we go, the PHP script that deletes the files that is X number of days old.
<?
$days = 1;
$dir = dirname ( __FILE__ );
$nofiles = 0;
if ($handle = opendir($dir)) {
while (( $file = readdir($handle)) !== false ) {
if ( $file == '.' || $file == '..' || is_dir($dir.'/'.$file) ) {
continue;
}
if ((time() - filemtime($dir.'/'.$file)) > ($days *86400)) {
$nofiles++;
unlink($dir.'/'.$file);
}
}
closedir($handle);
echo "Total files deleted: $nofiles \n";
}
?>
Now paste this code and save it as a php file, upload it to the folder from where you want to delete the files. You can see at the beginning of this php code
$days = 1;
that sets the number of days, for example if you set it to 2 then files older than 2 days will be deleted. Basically this is what happens when you run the script, gets the current directory and reads the file entries, skips ‘.’ for current directory and further checks if there are any other directories,
if ( $file == '.' || $file == '..' || is_dir($dir.'/'.$file) ) {
continue;
}
if the file entry is not a directory then it fetches the file modified time (last modified time) and compares, if it is number of days old
if ((time() - filemtime($dir.'/'.$file)) > ($days *86400)) {
$nofiles++;
unlink($dir.'/'.$file);
}
if the condition becomes true then it deletes the file with the help of unlink( ) php function. Finally closes the directory and exits. I have also added a counter to count the number of files being deleted, which will be displayed at the end of deletion process. So place the php file in the directory that needs the file deletion and execute it.
Hopefully that helps :)
I use shell AT command, it's like a cronjob though
php:
exec("echo rm /somedir/somefile.ext|at now +24 hours");
Here is another example that uses GLOB and it will delete any file
$files = glob('path/to/your/files/*');
foreach($files as $file) { // iterate files
// if file creation time is more than 5 minutes
if ((time() - filectime($file)) > 3600) { // 86400 = 60*60*24
unlink($file);
}
}
or if you want to exclude certain files
$files = preg_grep('#\.txt#', glob('/path/to/your/files/*'), PREG_GREP_INVERT);
After trying to use these examples, I hit a couple of issues:
The comparison operator should be greater than, not less than
filemtime returns the modified time. filectime is the time when a file's inode data is changed; that is, when the permissions, owner, group, or other metadata from the inode is updated, which may lead to unexpected results
I changed the example to use filemtime and fixed the comparison as follows:
<?php
$path = dirname(__FILE__).'/files';
if ($handle = opendir($path)) {
while (false !== ($file = readdir($handle))) {
if ((time()-filemtime($path.'/'.$file)) > 86400) { // 86400 = 60*60*24
if (preg_match('/\.txt$/i', $file)) {
unlink($path.'/'.$file);
}
}
}
}
?>
function deleteCachedData($hours=24)
{
$files = glob(ROOTDIR.DIRECTORY_SEPARATOR.'tmp'.DIRECTORY_SEPARATOR.'*.txt');
foreach($files as $file) {
if(is_file($file) && (time() - filectime($file)) > $hours*3600) {
unlink($file);
}
}
}
$path = 'folder/subfolder/';
/* foreach (glob($path.'.txt') as $file) { */
foreach (glob($path.'*') as $file) {
if(time() - filectime($file) > 86400){
unlink($file);
}
}

How to check if directory contents has changed with PHP?

I'm writing a photo gallery script in PHP and have a single directory where the user will store their pictures. I'm attempting to set up page caching and have the cache refresh only if the contents of the directory has changed. I thought I could do this by caching the last modified time of the directory using the filemtime() function and compare it to the current modified time of the directory. However, as I've come to realize, the directory modified time does not change as files are added or removed from that directory (at least on Windows, not sure about Linux machines yet).
So my questions is, what is the simplest way to check if the contents of a directory have been modified?
As already mentioned by others, a better way to solve this would be to trigger a function when particular events happen, that changes the folder.
However, if your server is a unix, you can use inotifywait to watch the directory, and then invoke a PHP script.
Here's a simple example:
#!/bin/sh
inotifywait --recursive --monitor --quiet --event modify,create,delete,move --format '%f' /path/to/directory/to/watch |
while read FILE ; do
php /path/to/trigger.php $FILE
done
See also: http://linux.die.net/man/1/inotifywait
What about touching the directory after a user has submitted his image?
Changelog says: Requires php 5.3 for windows to work, but I think it should work on all other environments
with inotifywait inside php
$watchedDir = 'watch';
$in = popen("inotifywait --monitor --quiet --format '%e %f' --event create,moved_to '$watchedDir'", 'r');
if ($in === false)
throw new Exception ('fail start notify');
while (($line = fgets($in)) !== false)
{
list($event, $file) = explode(' ', rtrim($line, PHP_EOL), 2);
echo "$event $file\n";
}
Uh. I'd simply store the md5 of a directory listing. If the contents change, the md5(directory-listing) will change. You might get the very occasional md5 clash, but I think that chance is tiny enough..
Alternatively, you could store a little file in that directory that contains the "last modified" date. But I'd go with md5.
PS. on second thought, seeing as how you're looking at performance (caching) requesting and hashing the directory listing might not be entirely optimal..
IMO edubem's answer is the way to go, however you can do something like this:
if (sha1(serialize(Map('/path/to/directory/', true))) != /* previous stored hash */)
{
// directory contents has changed
}
Or a more weak / faster version:
if (Size('/path/to/directory/', true) != /* previous stored size */)
{
// directory contents has changed
}
Here are the functions used:
function Map($path, $recursive = false)
{
$result = array();
if (is_dir($path) === true)
{
$path = Path($path);
$files = array_diff(scandir($path), array('.', '..'));
foreach ($files as $file)
{
if (is_dir($path . $file) === true)
{
$result[$file] = ($recursive === true) ? Map($path . $file, $recursive) : $this->Size($path . $file, true);
}
else if (is_file($path . $file) === true)
{
$result[$file] = Size($path . $file);
}
}
}
else if (is_file($path) === true)
{
$result[basename($path)] = Size($path);
}
return $result;
}
function Size($path, $recursive = true)
{
$result = 0;
if (is_dir($path) === true)
{
$path = Path($path);
$files = array_diff(scandir($path), array('.', '..'));
foreach ($files as $file)
{
if (is_dir($path . $file) === true)
{
$result += ($recursive === true) ? Size($path . $file, $recursive) : 0;
}
else if (is_file() === true)
{
$result += sprintf('%u', filesize($path . $file));
}
}
}
else if (is_file($path) === true)
{
$result += sprintf('%u', filesize($path));
}
return $result;
}
function Path($path)
{
if (file_exists($path) === true)
{
$path = rtrim(str_replace('\\', '/', realpath($path)), '/');
if (is_dir($path) === true)
{
$path .= '/';
}
return $path;
}
return false;
}
Here's what you may try. Store all pictures in a single directory (or in /username subdirectories inside it to speed things up and to lessen the stress on the FS) and set up Apache (or whaterver you're using) to serve them as static content with "expires-on" set to 100 years in the future. File names should contain some unique prefix or suffix (timestamp, SHA1 hash of file content, etc), so whenever uses changes the file its name gets changed and Apache will serve a new version, which will get cached along the way.
You're thinking the wrong way.
You should execute your directory indexer script as soon as someone's uploaded a new file and it's moved to the target location.
Try deleting the cached version when a user uploads a file to his directory.
When someone tries to view the gallery, look if there's a cached version first. If there's a cached version, load it, otherwise, generate the page, cache it, done.
I was looking for something similar and I just found this:
http://www.franzone.com/2008/06/05/php-script-to-monitor-ftp-directory-changes/
For me looks like a great solution since I'll have a lot of control (I'll be doing an AJAX call to see if anything changed).
Hope that this helps.
Here is a code sample, that would return 0 if the directory was changed.
I use it in backups.
The changed status is determined by presence of files and their filesizes.
You could easily change this, to compare file contents by replacing
$longString .= filesize($file);
with
$longString .= crc32(file_get_contents($file));
but it will affect execution speed.
#!/usr/bin/php
<?php
$dirName = $argv[1];
$basePath = '/var/www/vhosts/majestichorseporn.com/web/';
$dataFile = './backup_dir_if_changed.dat';
# startup checks
if (!is_writable($dataFile))
die($dataFile . ' is not writable!');
if (!is_dir($basePath . $dirName))
die($basePath . $dirName . ' is not a directory');
$dataFileContent = file_get_contents($dataFile);
$data = #unserialize($dataFileContent);
if ($data === false)
$data = array();
# find all files ang concatenate their sizes to calculate crc32
$files = glob($basePath . $dirName . '/*', GLOB_BRACE);
$longString = '';
foreach ($files as $file) {
$longString .= filesize($file);
}
$longStringHash = crc32($longString);
# do changed check
if (isset ($data[$dirName]) && $data[$dirName] == $longStringHash)
die('Directory did not change.');
# save hash do DB
$data[$dirName] = $longStringHash;
file_put_contents($dataFile, serialize($data));
die('0');

Categories