Done
<?php
define('FILE_NAME', 'list.dat');
define('MAX_BREAK', 30);
function write($file, $ip, $time)
{
fwrite($file, $ip . '|' . $time . "\n");
}
$new_ip = /*$REMOTE_ADDR*/ $_SERVER['REMOTE_ADDR'];
$file = fopen(FILE_NAME, 'w+');
flock($file, LOCK_EX | LOCK_SH);
$array = file(FILE_NAME, FILE_IGNORE_NEW_LINES);
$contains = false;
foreach ($array as $record)
{
$values = explode('|', $record);
$ip = $values[0];
$time = $values[1];
if ($ip == $new_ip)
{
$time = time();
$contains = true;
}
if (time() - $time < MAX_BREAK)
write($file, $ip, $time);
}
if (!$contains)
write($file, $new_ip, time());
flock($file, LOCK_UN);
fclose($file);
?>
$array is empty, but it shouldn't because file contains one line.
Any ideas why?
Because list.dat is empty.
fopen with w+
'w+' Open for reading and writing; place the file pointer at the beginning of the file and truncate the file to zero length. If the file does not exist, attempt to create it.
If the file command is returning false, it means file() failed. I think it might be failing because you already have it locked with your flock() call. The file() function does not need to be preceded by an fopen().
Using flock() you aquire an exclusive lock on the file and after that you want to read it. That doesn't work. A shared lock will probably be enough (no one can alter the file while it's locked).
flock($file, LOCK_EX | LOCK_SH);
becomes
flock($file, LOCK_SH);
Related
I developed a very simple counter in PHP. It works as expected but occasionally it resets to zero. No idea why. I suspect it could be related to concurrent visitors but I have no idea how to prevent that in case I am correct. Here is the code:
function updateCounter($logfile) {
$count = (int)file_get_contents($logfile);
$file = fopen($logfile, 'w');
if (flock($file, LOCK_EX)) {
$count++ ;
fwrite($file, $count);
flock($file, LOCK_UN);
}
fclose($file);
return number_format((float)$count, 0, ',', '.') ;
}
Thank you in advance.
file_get_contents on a locked file will probably get a "false" (== 0) and the logfile is probably unlocked again, when it comes to writing.
A classic race condition...
As file_get_contents() can return false accessing a previously locked file, the consequent fwrite() may write a zero or 1, resetting our counter to zero.
So we try to read the counter file after the locking has been succeeded for us.
function updateCounter($logfile) {
//$count = (int)file_get_contents($logfile);
if(file_exists($logfile)) {
$mode = 'r+';
} else {
$mode = 'w+';
}
//
$file = fopen($logfile, $mode);
//
if (flock($file, LOCK_EX)) {
//
// read counter file:
//
$count = (int) fgets($file);
$count++ ;
//
// point to the beginning of the file:
//
rewind($file);
fwrite($file, $count);
flock($file, LOCK_UN);
}
fclose($file);
return number_format((float)$count, 0, ',', '.') ;
}
//
$logfile = "counter.log";
echo updateCounter($logfile);
Please see usernotes on https://www.php.net/manual/en/function.flock.php .
I would append a character into the file and use strlen on the file contents to get the hits. Please note that your file will get big overtime but this can be easily solved with a cronjob that sums it up and cache it into another readonly file.
You can also use !is_writeable and check if its locked and if so you can miss the hit or wait with a while loop until its writable. Tricky but it works. It depends how valuable each hit will be and how much effort you would like to invest in this counter.
i have this script :
<?php
date_default_timezone_set('Europe/Berlin');
$date = date('d/m/Y h:i', time());
$ip = getenv("REMOTE_ADDR");
echo read_and_delete_first_line('data.txt');
function read_and_delete_first_line($filename) {
$file = file($filename);
$output = $file[0];
unset($file[0]);
file_put_contents($filename, $file);
$file1 = fopen($filename, "a");
fputs ($file1, $output);
fclose ($file1);
return $output;
}
?>
this get the first line of file and move to last file, the problem is, if receve more request in same time, php remove all data from data.txt and save file empty .. how can resolve this problem ? is possible whit this method or need use mysql ?
A S0-logger comes with a csv file on a monthly base. The file is updated every 5 minutes and can be retrieved any moment. At the end of the month the file counts over 8500 rows. When a new month starts a new file is made.
Fileformat is like this:
Datum / Uhrzeit (UTC);Main meter - Sales office (kWh);Meter 9;Temperature - Server room;Supply conductor air conditioner
01.06.12 00:00:00;438.220;0.001;274;155
01.06.12 00:05:00;438.240;0.001;274;203
01.06.12 00:10:00;438.259;0.001;275;134
01.06.12 00:15:00;438.283;0.001;274;176
01.06.12 00:20:00;438.303;0.001;274;206
dd.mm.yy (This is european dateformat)
I want to split the monthly file into a daily file with filename yymmdd.csv and store these files for further use and processing. There is no use for the column names.
During the day, its data is updated every five minutes, but after a day is finished there is no need to reprocess this data, because nothing changes. I found out fgetcsv is the most appropriate method. But how to prevent the reprocessing of the data which is rather time consuming and unnecessary?
Assuming the monthly file is always appended to.
You could keep a small file named e.g. 2012-january.csv.ptr. This file keeps the last position in the file; if it's non-existent you start at the beginning.
At every successful read, you determine the file pointer using ftell(). When you reached the end, you write the last position inside the .ptr file.
When the .ptr file exists you seek back into the file using fseek() and then start processing as per normal.
Can you use any database? Simple table would do for data storage and later procesing.
Here is the code:
if ($fp = fopen('log.csv', 'r')) {
$line_number = 0;
while ($line = fgetcsv($fp, 0, ';')) {
if ($line_number++ == 0) {
continue;
}
$date = explode(' ', $line[0]);
$file = $date[0] .'.log';
file_put_contents(
'monthly/'. $file,
implode(';', $line) ."\n",
FILE_APPEND
);
}
fclose($fp);
}
It reads CSV file line by line, extracts date part from the first column and creates new file and appends data to it.
P.S.
folder "monthly" must be writable
Combining the answers I figured this out:
It works, but sometimes when running the script again I miss one line in the split files and I can't figure out why.
<?php
$fh = fopen('2012_06.csv.ptr', 'r');
$pos1 = fread( $fh, 8192 );
//echo $pos1 , 'a',"<BR>";
fclose ($fh);
if ($fp = fopen('2012_06.csv', 'r'))
fseek ($fp , $pos1 );
{
$line_number = 0;
while ($line = fgetcsv($fp, 0, ';')) {
if ($line_number++ == 0) {
continue;
}
$date = explode(' ', $line[0]);
$file = $date[0] .'.log';
file_put_contents(
'Monthly/'. $file,
implode(';', $line) ."\n",
FILE_APPEND
);
}
$pos2 = ftell ($fp);
//echo $pos2;
fclose($fp);
}
$fh = fopen('2012_06.csv.ptr', 'w');
fwrite ($fh, $pos2);
fclose ($fh);
?>
Is there a quick way to load every line of a file into an array from a file once it has already been opened?
For example:
$handle = fopen("file", "r+");
flock($handle, LOCK_EX);
$array = load_lines($handle); <- need this
// compute on the array
fwrite($handle, $array);
flock($handle, LOCK_UN):
fclose($handle);
The reason I need this is because I currently use the file() function to grab the contents of a file and put them into an array. However, I need to incorporate file locking into my design and I'm hoping to not have to change it too much (it is current array-based). Is there an easy way to do this?
On php <5.3, or if you choose to with LOCK_NB, file locks in php are advisory. That is, you have to test the lock yourself .. they don't actually prevent you from updating the file. This will do:
$fh = fopen(__FILE__, 'r+');
if (flock($fh, LOCK_EX)) {
$array = file(__FILE__);
fwrite($fh, implode($array));
flock($fh, LOCK_UN);
flcose($fh);
}
else {
echo "Could not acquire the lock!"
}
I also tested this out in php 5.3. It seems that file() ignores locking.
Try this:
function load_lines($handle)
{
$array = array();
while(!feof($handle)
{
$array[] = fgets($handle);
}
return $array;
}
I had asked a question earlier( How to keep this counter from reseting at 100,000? ), and now have a follow-up question.
I have another version of the counter in question that can be told to reset at a certain number, and I would like to make sure that this second version does not have the same problem as the first.
What I have coded now is:
$reset = '10';
$filename4 = "$some_variable/$filename3.txt";
// Open our file in append-or-create mode.
$fh = fopen($filename4, "a+");
if (!$fh)
die("unable to create file");
if ($reset == 'default'){
// Before doing anything else, get an exclusive lock on the file.
// This will prevent anybody else from reading or writing to it.
flock($fh, LOCK_EX);
// Place the pointer at the start of the file.
fseek($fh, 0);
// Read one line from the file, then increment the number.
// There should only ever be one line.
$current = 1 + intval(trim(fgets($fh)));
// Now we can reset the pointer again, and truncate the file to zero length.
fseek($fh, 0);
ftruncate($fh, 0);
// Now we can write out our line.
fwrite($fh, $current . "\n");
// And we're done. Closing the file will also release the lock.
fclose($fh);
}
else {
$current = trim(file_get_contents($filename4)) + 1;
if($current >= $reset) {
$new = '0';
fwrite(fopen($filename4, 'w'), $new);
}
else {
fwrite(fopen($filec, 'w'), $current);
}
}
echo $current;
I did not want to assume I know what changes to make to this code, so I post another question. EDIT- What changes should I make here to avoid not getting an exclusive lock on the file if $reset is not equal to default? What is the correct way to code this? Would this work?:
$filename4 = "$some_variable/$filename3.txt";
// Open our file in append-or-create mode.
$fh = fopen($filename4, "a+");
if (!$fh)
die("unable to create file");
// Before doing anything else, get an exclusive lock on the file.
// This will prevent anybody else from reading or writing to it.
flock($fh, LOCK_EX);
// Place the pointer at the start of the file.
fseek($fh, 0);
if ($reset == 'default'){
// Read one line from the file, then increment the number.
// There should only ever be one line.
$current = 1 + intval(trim(fgets($fh)));
} else {
// Read one line from the file, then increment the number.
// There should only ever be one line.
$current = 1 + intval(trim(fgets($fh)));
if($current >= $reset) {
$current = '0';
}
else {
// Read one line from the file, then increment the number.
// There should only ever be one line.
$current = 1 + intval(trim(fgets($fh)));
}
}
// Now we can reset the pointer again, and truncate the file to zero length.
fseek($fh, 0);
ftruncate($fh, 0);
// Now we can write out our line.
fwrite($fh, $current . "\n");
// And we're done. Closing the file will also release the lock.
fclose($fh);
echo $current;
EDIT - This seems to be working for me:
$reset = "default";
$filename4 = "counter.txt";
// Open our file in append-or-create mode.
$fh = fopen($filename4, "a+");
if (!$fh)
die("unable to create file");
// Before doing anything else, get an exclusive lock on the file.
// This will prevent anybody else from reading or writing to it.
flock($fh, LOCK_EX);
// Place the pointer at the start of the file.
fseek($fh, 0);
// Read one line from the file, then increment the number.
// There should only ever be one line.
$current = 1 + intval(trim(fgets($fh)));
if ($reset == 'default'){
$new = $current;
} else {
if($current >= ($reset + '1')) {
$new = '1';
}
else {
$new = $current;
}
}
// Now we can reset the pointer again, and truncate the file to zero length.
fseek($fh, 0);
ftruncate($fh, 0);
// Now we can write out our line.
fwrite($fh, $new . "\n");
// And we're done. Closing the file will also release the lock.
fclose($fh);
echo $new;
Does this look right?
if($current >= $reset) {
// here is where you are setting the counter back to zero. comment out
// these lines.
//$new = '0';
//fwrite(fopen($filename4, 'w'), $new);
}
If you simply want a counter that doesn't get reset, try:
$filename4 = "counter.txt";
// Open our file in append-or-create mode.
$fh = fopen($filename4, "a+");
if (!$fh)
die("unable to create file");
// Before doing anything else, get an exclusive lock on the file.
// This will prevent anybody else from reading or writing to it.
flock($fh, LOCK_EX);
// Place the pointer at the start of the file.
fseek($fh, 0);
// Read one line from the file to get current count.
// There should only ever be one line.
$current = intval(trim(fgets($fh)));
// Increment
$new = $current++;
// Now we can reset the pointer again, and truncate the file to zero length.
fseek($fh, 0);
ftruncate($fh, 0);
// Now we can write out our line.
fwrite($fh, $new . "\n");
// And we're done. Closing the file will also release the lock.
fclose($fh);
echo $new;
The best way I can see to do this would be to open the file for reading with a lock other than exclusive. you can then perform your required checks and if the the count exceeds the $reset value, you can close the the file, open it again but this time with the exclusive lock for writing.
Another way would simply not to use an exclusive lock.
You could look into very good flatfile classes out there which have tested locking mechanisms.
file_put_contents is already atomic. There is no need for ten lines of file locking code.
<?php
$fn = "$filename3.txt";
$reset = 0; // 0 is equivalent to "default"
//$reset = 10000000;
$count = file_get_contents($fn);
$count = ($reset && ($count >= $reset)) ? (0) : ($count + 1);
file_put_contents($fn, $count, LOCK_EX);
echo $count;
No idea if this is any help, since your question is still opaque. I will not answer comments.