I have cleanup script, which move the XLS files from one place to another. for this file moving process, I have used the rename function. This script is working fine. but when the XLS file is open, when I try to move that xls, I am getting error which simply say Can not rename sample.xls. But I would like to add the functionality like, Check the XLS is open before initiate rename function.
I believe this is function call flock but this is applicable for TXT file alone.
How to check XLS file is opened before call the rename function.
One simple thing you could try is to use flock to acquire a Exclusive Lock on the file and if it fails you will know the file is being used:
<?php
$fp = fopen('c:/your_file.xlsx', 'r+');
if(!flock($fp, LOCK_EX))
{
echo 'File is being used...';
exit(-1);
}
else
{
fclose($fp);
// rename(...);
}
An alternative would be to check the existence of the locking file excel usually creates when a file is being used:
<?php
$file = 'c:/testfile.xlsx';
$lock = 'c:/~$testfile.xlsx';
if (file_exists($lock))
{
echo "Excel $file is locked.";
}
else
{
echo "Excel $file is free.";
}
The hidden file is usually name with the prefix ~$ as for old excel files I believe 2003 and older the lock files are saved on the temp folder with a random name like ~DF7B32A4D388B5854C.TMP so it would be pretty hard to find out.
You should use flock(). This puts a flag on the file so that other scripts are informed that the file is in use. The flag is turned off either intentionally using fclose or implicitly by the end of the script.
Use file lock like:
flock($file,LOCK_EX);
see this
Related
I have a little problem inside my application.
I have many process launched automatically, from the server with crontab, written in php to read file inside a folder.
Sometimes different process read the same file and create a problem inside the application.
Is there a way to manage this problem?
Actually I read all files inside a folder, read each of them and delete immediately, but sometimes another process read the same file before I delete It.
This is my script written with cakephp3 (so some classes like File is only for cakephp3 but isn't the point of the question) to read and delete:
$xml_files = glob(TMP . 'xml/*.xml');
foreach($xml_files as $fileXml)
{
//read the file and put into a string or array or object
$explStr = explode('/', $fileXml);
$filename = $explStr[count($explStr) - 1];
$path = TMP . '/xml/' . $filename;
$file = new File($path, false);
if($file->exists()){
$string = $file->read();
$file->close();
$file->delete();
}
}
Use flock() to obtain (or attempt to obtain) a file lock and act accordingly.
It's called a race condition, and when working with files you could lock the file when process A uses it, it locks it, then other processes would check if it's locked and if it is then do nothing. Then unlock the file when process A has finished with it.
I am testing my code using little database in txt files. The most important problem that I have found is: when users write at the same time into one file. To solve this I am using flock.
OS of my computer is windows with xampp installed (comment this because i understand flocks works fine over linux no windows) However I need to do this test over linux server.
Actually I have tested my code by loading the same script in 20 windows at the same time. The firsts results works fine, but after test database file appears empty.
My Code :
$file_db=file("test.db");
$fd=fopen("".$db_name."","w");
if (flock($fd, LOCK_EX))
{
ftruncate($fd,0);
for ($i=0;$i<sizeof($file_db);$i++)
{
fputs($fd,"$file_db[$i]"."\n");
}
fflush($fd);
flock($fd, LOCK_UN);
fclose($fd);
}
else
{
print "Db Busy";
}
How it's possible that the script deletes database file content. What is proper way: use flock with fixing of existing code or use some other alternative technique of flock?
I have re-wrote the script using #lolka_bolka's answer and it works. So in answer to your question, the file $db_name could be empty if the file test.db is empty.
ftruncate after fopen with "w" is useless.
file function
Returns the file in an array. Each element of the array corresponds to a line in the file, with the newline still attached. Upon failure, file() returns FALSE.
You do not have to add additional end of line symbol.
flock function
PHP supports a portable way of locking complete files in an advisory way (which means all accessing programs have to use the same way of locking or it will not work).
It means that file function not affected by the lock. It means that $file_db=file("test.db"); could read file while other process somewhere between ftruncate($fd,0); and fflush($fd);. So, you need read file content inside lock.
$db_name = "file.db";
$fd = fopen($db_name, "r+"); // w changed to r+ for getting file resource but not truncate it
if (flock($fd, LOCK_EX))
{
$file_db = file($db_name); // read file contents while lock obtained
ftruncate($fd, 0);
for ($i = 0; $i < sizeof($file_db); $i++)
{
fputs($fd, "$file_db[$i]");
}
fflush($fd);
flock($fd, LOCK_UN);
}
else
{
print "Db Busy";
}
fclose($fd); // fclose should be called anyway
P.S. you could test this script using console
$ for i in {1..20}; do php 'file.php' >> file.log 2>&1 & done
I have my users uploading a text file which then gets processed by my application. Once the processing is done, I would like to save a copy of this text file somewhere on my server for future reference. Currently, the uploaded text file stays in the PHP temp folder until it is closed by my app.
What's a simple way to accomplish this?
BTW, I'll need to know how to do this on my web server along with localhost (for testing).
You can use the fwrite function (this is probably not a very good idea in this particular example though.
$fp = fopen('data.txt', 'w');
fwrite($fp, $yourContents);
fclose($fp);
But, if you already have the file simply copy it using the copy command (if you want to keep it in the temp folder that is, if not move it with the rename function instead).
To copy, do something like this
$tempfile = 'tempfile.txt';
$newfile = 'newfile.txt';
if (copy($tempfile, $newfile)) {
echo "success!";
} else {
echo "misery :(";
}
To move with rename
// Rename returns a bool, just as in the copy example
rename("/tmp/tempfile.txt", "/home/user/files/newfile.txt");
Added this: To move with move_uploaded_file
Please note, I didn't test this in a development environment. This may not execute perfectly.
$uploads_dir = 'C:\\movefiles\\here\\';
foreach ($_FILES["upload-tracking-file"]["error"] as $key => $error) {
if ($error == UPLOAD_ERR_OK) {
$tmp_name = $_FILES["upload-tracking-file"]["tmp_name"][$key];
$name = $_FILES["upload-tracking-file"]["name"][$key];
move_uploaded_file($tmp_name, "$uploads_dir\\$name");
}
}
References
Copy, http://php.net/manual/en/function.copy.php
Move (rename), http://php.net/manual/en/function.rename.php
move_uploaded_file, http://php.net/manual/en/function.move-uploaded-file.php
fwrite, http://php.net/manual/en/function.fwrite.php
use php's function ob_start(), and file_put_contents() this should help you. this links will help you if not post your reply
php.net/manual/en/function.ob-start.php, php.net/manual/en/function.file-put-contents.php
I know you can create a temporary file with tmpfile and than write to it, and close it when it is not needed anymore. But the problem I have is that I need the absolute path to the file like this:
"/var/www/html/lolo/myfile.xml"
Can I somehow get the path, even with some other function or trick?
EDIT:
I want to be able to download the file from the database, but without
$fh = fopen("/var/www/html/myfile.xml", 'w') or die("no no");
fwrite($fh, $fileData);
fclose($fh);
because if I do it like this, there is a chance of overlapping, if more people try to download the same file at exactly the same time. Or am I wrong?
EDIT2:
Maybe I can just generate unique(uniqID) filenames like that, and than delete them. Or can this be too consuming for the server if many people are downloading?
There are many ways you can achieve this, here is one
<?php
// Create a temp file in the temporary
// files directory using sys_get_temp_dir()
$temp_file = tempnam(sys_get_temp_dir(), 'MyFileName');
echo $temp_file;
?>
The above example will output something similar to:
/var/tmp/MyFileNameX322.tmp
I know you can create a temporary file with tmpfile
That is a good start, something like this will do:
$fileHandleResource = tmpfile();
Can I somehow get the path, even with some other function or trick?
Yes:
$metaData = stream_get_meta_data($fileHandleResource);
$filepath = $metaData['uri'];
This approach has the benefit of leaving it up to PHP to pick a good place and name for this temporary file, which could end up being a good thing or a bad thing depending on your needs. But it is the simplest way to do this if you don't yet have a specific reason to pick your own directory and filename.
References:
http://us.php.net/manual/en/function.stream-get-meta-data.php
Getting filename (or deleting file) using file handle
This will give you the directory. I guess after that you are on your own.
For newer (not very new lol) versions of PHP (requires php 5.2.1 or higher) #whik's answer is better suited:
<?php
// Create a temp file in the temporary
// files directory using sys_get_temp_dir()
$temp_file = tempnam(sys_get_temp_dir(), 'MyFileName');
echo $temp_file;
?>
The above example will output something similar to: /var/tmp/MyFileNameX322.tmp
old answer
Just in case someone encounters exactly the same problem. I ended up doing
$fh = fopen($filepath, 'w') or die("Can't open file $name for writing temporary stuff.");
fwrite($fh, $fileData);
fclose($fh);
and
unlink($filepath);
at the end when file is not needed anymore.
Before that, I generated filename like that:
$r = rand();
$filepath = "/var/www/html/someDirectory/$name.$r.xml";
I just generated a temporary file, deleted it, and created a folder with the same name
$tempFolder = tempnam(sys_get_temp_dir(), 'MyFileName');
unlink($tempFolder);
mkdir($tempFolder);
I need to read a list of CSV files from an FTP and delete them after I successfully read them.
Until now, i opened the csv file using fopen into a resource and then used fgetcsv to read the csv lines from it.
$res = fopen($url);
while ($csv_row = fgetcsv($res, null, self::DELIMITER)) {
.....
}
The problem is that I need to read a list of csv files and delete them too. the ftp_get function save the file into a local file. I rather avoid that. any way I can keep using the fgetcsv function with the ftp_nlist & ftp_connect functions? ?
You can save the csv file to a temporary file stream using ftp_fget(). This allows you to avoid the "create-read-delete" cycle. Once you close the file stream it's like it magically never existed :)
$ftp_handle = ftp_connect($ftp_server);
$remote_path = "/path/to/file.csv";
$tmp_handle = fopen('php://temp', 'r+');
if (ftp_fget($ftp_handle, $tmp_handle, $remote_path, FTP_ASCII)) {
rewind($tmp_handle);
while ($csv_row = fgetcsv($tmp_handle)) {
// do stuff
}
}
fclose($tmp_handle);
If you wanted to loop over a directory of files just get the list of files and then put the above code in a loop.