I was needing a way to generate thumbnails (using PHP5) for an image management script and had a problem where my host has multiple versions of PHP installed (4 and 5), with PHP4 set as default. This meant that any calls to php from the CLI would run PHP4. I've come up with the following as what I hope to be a cross platform solution. I'm posting it here primarily as I had a lot of trouble finding any help using Google, so this might help someone in the future, I also have the following questions.
Do you see anything obviously wrong with it?
Are there any other paths to the php5 binary that you know of or know of a better order to have the array for optimisation?
If a host has exec or shell_exec disabled, will the EGalleryProcessQueue.php script be able to be run as a standalone cron job? I don't have access to cron to be able to test this yet. I'm not too worried about this question, as I'll get around to testing it eventually anyway.
Does anyone have any suggestions as to a way in which I can get some feedback as to how far through the images the processing is? See the TODO section of EGalleryProcessQueue.php I'd like to display a progress bar when it's in the admin section.
Main script
/**
* Writes the array to a text file in /path/to/gallery/needsThumbs.txt for batch processing.
* Runs the thumbnail generator script in the background.
*
* #param array $_needsThumbs the array of images needing thumbnails
*/
private function generateThumbnails($_needsThumbs)
{
file_put_contents($this->_realpath.DIRECTORY_SEPARATOR.'needsThumbs.txt',serialize($_needsThumbs));
$Command = realpath(dirname(__FILE__)).DIRECTORY_SEPARATOR.'EGalleryProcessQueue.php '.$this->_realpath.' '.$this->thumbnailWidth.' '.$this->thumbnailHeight;
if(PHP_SHLIB_SUFFIX == 'so')// *nix (aka NOT windows)
{
/*
* We need to make sure we are using the right PHP version
* (problems with shared hosts that have PHP4 and PHP5 installed,
* but PHP4 set as default).
*/
$phpPaths = array('php', '/usr/local/bin/php', '/usr/local/php5/bin/php', '/usr/bin/php', '/usr/bin/php5');
foreach($phpPaths as $path)
{
exec("echo '<?php echo version_compare(PHP_VERSION, \"5.0.0\", \">=\"); ?>' | $path", $result);
if($result)
{
shell_exec("nohup $path $Command 2> /dev/null > /dev/null &");
break;
}
}
}
else // Windows
{
$WshShell = new COM("WScript.Shell");
$WshShell->Run("php.exe $Command", 0, false);
}
}
EGalleryProcessQueue.php
#!/usr/bin/php
<?php
if ($argc === 4 && strstr($argv[0], basename(__FILE__))) {
// File is being called by the CLI and has not been included by another script
if(!file_exists($argv[1].DIRECTORY_SEPARATOR.'needsThumbs.txt'))
{
// Either no thumbnails need to be created or a wrong directory has been supplied
exit;
}
include(realpath(dirname(__FILE__)).DIRECTORY_SEPARATOR.'EGalleryThumbGenerator.php');
$generator = new EGalleryThumbGenerator;
$generator->directory = $argv[1];
$generator->thumbnailWidth = is_int($argv[2]) ? $argv[2] : 128;
$generator->thumbnailHeight = is_int($argv[3]) ? $argv[3] : 128;
// $generator->processImages() returns the number of images left to process (it does them in blocks of 10)
while (($i = $generator->processImages()) > 0)
{
/*
* TODO Can we get some sort of feedback to the user here?
* Possibly so that we can display a progress bar in the management section.
* Probably have to write $i to a file to be read by the main script.
*/
}
exit;
}
?>
Do you see anything obviously wrong with it?
Nope, the code looks good.
Are there any other paths to the php5 binary that you know of or know of a better order to have the array for optimization?
This is a hard question to answer, as PHP could be installed anywhere on a server. The paths you have seem to be very logical to me, but there could be any number of other places it could be installed.
Rather than providing a bunch of directories where PHP5 might be installed, what about having a parameter the user has to set to provide the path to the PHP5 executable if it's not in their $PATH?
If a host has exec or shell_exec disabled, will the EGalleryProcessQueue.php script be able to be run via a cron job?
I haven't tested it, but I would assume that would prevent the script from running.
Does anyone have any suggestions as to a way in which I can get some feedback as to how far through the images the processing is? See the TODO section of EGalleryProcessQueue.php I'd like to display a progress bar when it's in the admin section.
Store the number of images completed somewhere (file, db, maybe even session var) and have an AJAX call fire every second or so to a function that provides done vs total. Then use something like http://docs.jquery.com/UI/Progressbar
Related
I have a server running on Linux that execute commands to 12 nodes (12 computers with Linux running in them). I recently downloaded PHP on the server to create web pages that can execute commands by opening a specific PHP file.
I used exec(), passthru(), shell_​exec(), and system(). system() is the only one that returns a part of my code. I would like PHP to act like open termainal command in linux and I cannot figure out how to do it!
Here is an example of what is happening now (Linux directly vs PHP):
When using linux open terminal command directly:
user#wizard:/home/hyperwall/Desktop> /usr/local/bin/chbg -mt
I get an output:
The following settings will be used:
option = mtsu COLOR = IMAGE = imagehereyouknow!
NODES = LOCAL
and additional code to send it to 12 nodes.
Now with PHP:
switch($_REQUEST['do'])
{ case 'test':
echo system('/usr/local/bin/chbg -mt');
break;
}
Output:
The following settings will be used:
option = mtsu COLOR = IMAGE = imagehereyouknow!
NODES = LOCAL
And stops! Anyone has an explanation of what is happening? And how to fix it? Only system displays part of the code the other functions display nothing!
My First thought is it can be something about std and output error. Some softwares dump some informations on std out and some in std error. When you are not redirecting std error to std out, most of the system calls only returns the stdout part. It sounds thats why you see the whole output in terminal and can't in the system calls.
So try with
/usr/local/bin/chbg -mt 2>&1
Edit:
Also for a temporary work through, you can try some other things. For example redirect the output to file next to the script and read its contents after executing the command, This way you can use the exec:
exec("usr/local/bin/chbg -mt 2>&1 > chbg_out");
//Then start reading chbg_out and see is it work
Edit2
Also it does not make sense why others not working for you.
For example this piece of code written in c, dumps a string in stderr and there is other in stdout.
#include <stdio.h>
#include<stdlib.h>
int main()
{
fputs("\nerr\nrro\nrrr\n",stderr);
fputs("\nou\nuu\nuttt\n",stdout);
return 0;
}
and this php script, tries to run that via exec:
<?php
exec("/tmp/ctest",&$result);
foreach ( $result as $v )
{
echo $v;
}
#output ouuuuttt
?>
See it still dumps out the stdout. But it did not receive the stderr.
Now consider this:
<?php
exec("/tmp/ctest 2>&1",&$result);
foreach ( $result as $v )
{
echo $v;
}
//output: errrrorrrouuuuttt
?>
See, this time we got the whole outputs.
This time the system:
<?php
echo system("/tmp/ctest 2>&1");
//output: err rro rrr ou uu uttt uttt
?>
and so on ...
Maybe your chbg -mt writes additional code to stderr instead of stdout? Try to execute your script inside php like this:
/usr/local/bin/chbg -mt 2>&1
The other responses are good for generic advice. But in this specific case, it appears you are trying to change your background on your desktop. This requires many special considerations because of 'user context':
First, your web server is probably running as a different user, and therefore would not have permissions to change your desktop.
Second, the program probably requires some environmental variables from your user context. For example, X programs need a DISPLAY variable, ssh-agent needs SSH_AGENT_PID and SSH_AUTH_SOCK, etc. I don't know much about changing backgrounds, but I'm guessing it involves D-Bus, which probably requires things like DBUS_SESSION_BUS_ADDRESS, KONSOLE_DBUS_SERVICE, KONSOLE_DBUS_SESSION, and KONSOLE_DBUS_WINDOW. There may be many others. Note that some of these vars change every time you log in, so you can't hard-code them on the PHP side.
For testing, it might be simpler to start your own webserver right from your user session. (i.e. Don't use the system one, it has to run as you. You will need to run it on an alternate port, like 8080). The web server you start manually will have all the 'context' it needs. I'll mention websocketd because it just came out and looks neat.
For "production", you may need to run a daemon in your user context all the time, and have the web server talk to that daemon to 'get stuff done' inside your user context.
PHP's system only returns the last line of execution:
Return Value: Returns the last line of the command output on success, and FALSE on failure.
You will most likely want to use either exec or passthru. exec has an optional parameter to put the output into an array. You could implode the output and use that to echo it.
switch($_REQUEST['do'])
{ case 'test':
exec('/usr/local/bin/chbg -mt', $output);
echo implode('\n', $output); // Could use <br /> if HTML output is desired
break;
}
I think that the result of execution, can changes between users.
First, try to run your PHP script directly into your terminal php yourScript.php
If it runs as expected, go to your Apache service and update it to run with your own credentials
You are trying to change the backgrounds for currently logged in users... While they are using the desktop. Like while I'm typing this message. I minimize my browser and 'ooh my desktop background is different'. Hopefully this is for something important like it turns red when the reactor or overheating.
Anyway to my answer:
Instead of trying to remotely connect and run items as the individual users. Setup each user to run a bash script (in their own account, in their own shell) on a repeating timer. Say every 10 minutes. Have it select the SAME file.. from a network location
/somenetworkshare/backgrounds/images/current.png
Then you can update ALL nodes (1 to a million) just by changing the image itself in /somenetworkshare/backgrounds/images/current.png
I wrote something a while ago that does just this -- you can run a command interpreter (/bin/sh), send it commands, read back responses, send more commands, etc. It uses proc_open() to open a child process and talk to it.
It's at http://github.com/andrasq/quicklib, Quick/Proc/Process.php
Using it would look something like (easier if you have a flexible autoloader; I wrote one of those too in Quicklib):
include 'lib/Quick/Proc/Exception.php';
include 'lib/Quick/Proc/Exists.php';
include 'lib/Quick/Proc/Process.php';
$proc = new Quick_Proc_Process("/bin/sh");
$proc->putInput("pwd\n");
$lines = $proc->getOutputLines($nlines = 10, $timeoutSec = 0.2);
echo $lines[0];
$proc->putInput("date\n");
$lines = $proc->getOutputLines(1, 0.2);
echo $lines[0];
Outputs
/home/andras/quicklib
Sat Feb 21 01:50:39 EST 2015
The unit of communication between php and the process is newline terminated lines. All commands must be newline terminated, and all responses are retrieved in units of lines. Don't forget the newlines, they're hard to identify afterward.
I am working on a project that uses Terminal A on machine A to output to Terminal B on Machine B, both using linux for now. I didnt see it mentioned, but perhaps you can use redirection, something like this in your webserver:
switch($_REQUEST['do'])
{ case 'test':
#process ID on the target (12345, 12346 etc)
echo system('/usr/local/bin/chbg -mt > /proc/<processID>/fd/1');
#OR
#device file on the target (pts/0,tty0, etc)
echo system('/usr/local/bin/chbg -mt > /dev/<TTY-TYPE>/<TTYNUM>');
break;
}
Definitely the permissions need to be set correctly for this to work. The command "mesg y" in a terminal may also assist...Hope that helps.
Hi i'm trying to execute a LONG RUNNING request (action) in background.
function actionRequest($id){
//execute very long process here in background but continue redirect
Yii::app()->user->setFlash('success', "Currently processing your request you may check it from time to time.");
$this->redirect(array('index', 'id'=>$id));
}
What i'm trying to achieve is to NOT have the user waiting for the request to be processed since it generally takes 5-10min, and the request usually goes to a timeout, and even if I set the timeout longer, waiting for 5-10 min. isn't a good user experience.
So I want to return to the page immediately notifying the user that his/her request is being processed, while he can still browse, and do other stuff in the application, he/she can then go back to the page and see that his/her request was processed.
I've looked into Yii extensions backjob, It works, the redirect is executed immediately (somehow a background request), but when doing other things, like navigating in the site, it doesn't load, and it seems that the request is still there, and i cannot continue using the application until the request is finished.
A similar extension runactions promises the same thing, but I could not even get it to work, it says it 'touches a url', like a fire and forget job but doesn't work.
I've also tried to look into message queuing services like Gearman, RabbitMQ, but is really highly technical, I couldn't even install Gearman in my windows machine so "farming" services won't work for me. Some answers to background processing includes CRON and AJAX but that doesn't sound too good, plus a lot of issues.
Is there any other workaround to having asynchronous background processing? I've really sought hard for this, and i'm really not looking for advanced/sophisticated solutions like "farming out work to several machines" and the likes. Thank You very much!
If you want to be able to run asynchronous jobs via Yii, you may not have a choice but to dabble with some AJAX in order to retrieve the status of the job asynchronously. Here are high-level guidelines that worked for me. Hopefully this will assist you in some way!
Setting up a console action
To run background jobs, you will need to use Yii's console component. Under /protected/commands, create a copy of your web controller that has your actionRequest() (e.g. /protected/commands/BulkCommand.php).
This should allow you to go in your /protected folder and run yiic bulk request.
Keep in mind that if you have not created a console application before, you will need to set up its configuration similar to how you've done it for the web application. A straight copy of /protected/config/main.php into /protected/config/console.php should do 90% of the job.
Customizing an extension for running asynchronous console jobs
What has worked for me is using a combination of two extensions: CConsole and TConsoleRunner. TConsoleRunner uses popen to run shell scripts, which worked for me on Windows and Ubuntu. I simply merged its run() code into CConsole as follows:
public function popen($shell, $redirectOutput = '')
{
$shell = $this->resolveCommandLine($shell, false, $redirectOutput);
$ret = self::RETURN_CODE_SUCCESS;
if (!$this->displayCommands) {
ob_start();
}
if ($this->isWindows()) {
pclose(popen('start /b '.$shell, 'r'));
}
else {
pclose(popen($shell.' > /dev/null &', 'r'));
}
if (!$this->displayCommands) {
ob_end_clean();
}
return $ret;
}
protected function isWindows()
{
if(PHP_OS == 'WINNT' || PHP_OS == 'WIN32')
return true;
else
return false;
}
Afterwards, I changed CConsole's runCommand() to the following:
public function runCommand($command, $args, $async = false, &$outputLines = null, $executor = 'popen')
{
...
switch ($executor) {
...
case 'popen':
return $this->popen($shell);
...
}
}
Running the asynchronous job
With the above set up, you can now use the following snippet of code to call yiic bulk request we created earlier.
$console = new CConsole();
$console->runCommand('bulk request', array(
'--arg1="argument"',
'--arg2="argument"',
'--arg3="argument"',
));
You would insert this in your original actionRequest().
Checking up on the status
Unfortunately, I'm not sure what kind of work your bulk request is doing. For myself, I was gathering a whole bunch of files and putting them in a folder. I knew going in how many files I expected, so I could easily create a controller action that verifies how many files have been created so far and give a % of the status as a simple division.
This is the code I'm using as I work my way to a solution.
public function indexAction()
{
//id3 options
$options = array("version" => 3.0, "encoding" => Zend_Media_Id3_Encoding::ISO88591, "compat" => true);
//path to collection
$path = APPLICATION_PATH . '/../public/Media/Music/';//Currently Approx 2000 files
//inner iterator
$dir = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS);
//iterator
$iterator = new RecursiveIteratorIterator($dir, RecursiveIteratorIterator::SELF_FIRST);
foreach ($iterator as $file) {
if (!$file->isDir() && $file->getExtension() === 'mp3') {
//real path to mp3 file
$filePath = $file->getRealPath();
Zend_Debug::dump($filePath);//current results: accepted path no errors
$id3 = new Zend_Media_Id3v2($filePath, $options);
foreach ($id3->getFramesByIdentifier("T*") as $frame) {
$data[$frame->identifier] = $frame->text;
}
Zend_Debug::dump($data);//currently can scan the whole collection without timing out, but APIC data not being processed.
}
}
}
The problem: Process a file system of mp3 files in multiple directories. Extract id3 tag data to a database (3 tables) and extract the cover image from the tag to a separate file.
I can handle the actual extraction and data handling. My issue is with output.
With the way that Zend Framework 1.x handles output buffering, outputting an indicator that the files are being processed is difficult. In an old style PHP script, without output buffering, you could print out a bit of html with every iteration of the loop and have some indication of progress.
I would like to be able to process each album's directory, output the results and then continue on to the next album's directory. Only requiring user intervention on certain errors.
Any help would be appreciated.
Javascript is not the solution I'm looking for. I feel that this should be possible within the constructs of PHP and a ZF 1 MVC.
I'm doing this mostly for my own enlightenment, it seems a very good way to learn some important concepts.
[EDIT]
Ok, how about some ideas on how to break this down into smaller chunks. Process one chunk, commit, process next chunk, kind of thing. In or out of ZF.
[EDIT]
I'm beginning to see the problem with what I'm trying to accomplish. It seems that output buffering is not just happening in ZF, it's happening everywhere from ZF all the way to the browser. Hmmmmm...
Introduction
This is a typical example of what you should not do because
You are trying to parse ID3 tag with PHP which is slow and trying to have multiple parse files at once would definitely make it even slower
RecursiveDirectoryIterator would load all the files in a folder and sub folder from what i see there is no limit .. it can be 2,000 today the 100,000 the next day ? Total processing time is unpredictable and this can definitely take some hours in some cases
High dependence on single file system, with your current architecture the files are stored in local system so it would be difficult to split the files and do proper load balancing
You are not checking if the file information has been extracted before and this results Loop and extraction Duplication
No locking system .. this means that this process can be initiated simultaneously resulting to general slow performance on the server
Solution 1 : With Current Architecture
My advice is not to use loop or RecursiveDirectoryIterator to process the files in bulk.
Target the file as soon as they are uploaded or transferred to the server. That way you are only working with one file at a time this way to can spread the processing time.
Solution 2: Job Queue (Proposed Solution)
Your problem is exactly what Job Queue are designed to do you are also not limited to implementing the parsing with PHP .. you take advantage of C or C++ for performance
Advantage
Transfer Jobs to other machines or processes that are better suited to do the work
It allows you to do work in parallel, to load balance processing
Reduce the latency of page views in high-volume web applications by running time-consuming tasks asynchronously
Multiple Languages client in PHP sever in C
Examples have tested
ZemoMQ
Gearman
Beanstalkd
Expected Process Client
Connect To Job Queue eg German
Connect to Database eg MongoDB or Redis
Loop with folder path
Check File extension
If file is mp3 , generate file hash eg. sha1_file
Check if file has been sent for processing
send hash, file to Job Server
Expected Process Server
Connect To Job Queue eg German
Connect to Database eg MongoDB or Redis
Receive hash / file
Extract ID3 tag ;
Update DB with ID3 Tag Information
Finally this processing can be done on multiple servers in parallel
One solution would be to use a Job Queue, such a Gearman. Gearman is an excellent solution for this kind of problem, and easily integrated with Zend Framework (http://blog.digitalstruct.com/2010/10/17/integrating-gearman-into-zend-framework/)
It will allow you to create a worker to process each "chuck", allowing your process to continue unblocked while the job is processed, very handy for long running proceeses such as music/image processing etc http://gearman.org/index.php?id=getting_started
I'm not familiar with how Zend Framework work. I will give you a general advice. When working with process that is doing so many iterative and possibly in long time, it is generally advised that the long process be moved into background process. Or, in web related, moved into cron job.
If the process you want to use is for single site, you can implement something like this, in your cronjob (note: rough pseudo-code):
<?php
$targetdir = "/path/to/mp3";
$logdir = "/path/to/log/";
//check if current state is exists. If it does, then previous cronjob is still running
//we should stop this process so that it doesn't do duplicated process which might have introduced random bugs
if(file_exists($logdir."current-state")){
exit;
}
//start process, write state to logdir
file_put_contents($logdir."current-log", "process started at ".date("Y-m-d H:i:s"));
file_put_contents($logdir."current-state", "started\t".date("Y-m-d H:i:s"));
$dirh = opendir($targetdir);
while($file = readdir($dirh)){
//lets ignore current and parent dir
if(in_array($file, array('.', '..'))) continue;
//do whatever process you want to do here:
//you might want to write another log, too:
file_put_contents($logdir."current-log", "processing file {$file}", FILE_APPEND);
}
closedir($dirh);
file_put_contents($logdir."current-log", "process finished at ".date("Y-m-d H:i:s"));
//process is finished, delete current-state:
unlink($logdir."current-state");
Next, in your php file for web, you can add snippet to, says admin page, or footer, or whatever page you want, to see the progress:
<?php
if(file_exists($logdir."current-state")){
echo "<strong>there are background process running.</strong>";
} else {
echo "<strong>no background process running.</strong>";
}
I should suggest using plugin.
class Postpone extends Zend_Controller_Plugin_Abstract
{
private $tail;
private $callback;
function __construct ($callback = array())
{
$this->callback = $callback;
}
public function setRequest (Zend_Controller_Request_Abstract $request)
{
/*
* We use layout, which essentially contains some html and a placeholder for action output.
* We put the marker into this placeholder in order to figure out "the tail" -- the part of layout that goes after placeholder.
*/
$mark = '---cut-here--';
$layout = $this->getLayout ();
$layout->content = $mark;
/*
* Now we have it.
*/
$this->tail = preg_replace ("/.*$mark/s", '', $layout->render ());
}
public function postDispatch (Zend_Controller_Request_Abstract $request)
{
$response = $this->getResponse ();
$response->sendHeaders ();
/*
* The layout generates its output to the default section of the response.
* This output inludes "the tail".
* We don't need this tail shown right now, because we have callback to do.
* So we remove it here for a while, but we'll show it later.
*/
echo substr ($this->getResponse ()
->getBody ('default'), 0, - strlen ($this->tail));
/*
* Since we have just echoed the result, we don't need it in the response. Do we?
*/
Zend_Controller_Front::getInstance ()->returnResponse(true);
$response->clearBody ();
/*
* Now to business.
* We execute that calculation intensive callback.
*/
if (! empty ($this->callback) && is_callable ($this->callback))
{
call_user_func ($this->callback);
}
/*
* We sure don't want to leave behind the tail.
* Output it so html looks consistent.
*/
echo $this->tail;
}
/**
* Returns layout object
*/
function getLayout ()
{
$layout_plugin = Zend_Controller_Front::getInstance ()->getPlugin ('Zend_Layout_Controller_Plugin_Layout');
return $layout = $layout_plugin->getLayout ();
}
}
class IndexController extends Zend_Controller_Action
{
/*
* This is a calculation intensive action
*/
public function indexAction ()
{
/*
* Zend_Layout in its current implementation accumulates whole action output inside itself.
* This fact hampers out intention to gradually output the result.
* What we do here is we defer execution of our intensive calculation in form of callback into the Postpone plugin.
* The scenario is:
* 1. Application started
* 2. Layout is started
* 3. Action gets executed (except callback) and its output is collected by layout.
* 4. Layout output goes to response.
* 5. Postpone::postDispatch outputs first part of the response (without the tail).
* 6. Postpone::postDispatch calls the callback. Its output goes stright to browser.
* 7. Postpone::postDispatch prints the tail.
*/
$this->getFrontController ()
->registerPlugin (new Postpone (function ()
{
/*
* A calculation immigration
* Put your actual calculations here.
*/
echo str_repeat(" ", 5000);
foreach (range (1, 500) as $x)
{
echo "<p>$x</p><br />\n";
usleep(61500);
flush();
}
}), 1000);
}
}
There is a vbscript that we must run to consolidate information gathered in a custom web application into our management software. The .vbs is in the same folder as the web application which is built in CodeIgniter 2.
Here is the controller code:
public function saveToPM( $budgetType ){
// run it
$obj = new COM( 'WScript.Shell' );
if ( is_object ( $obj ) ) {
$obj->Run( 'cmd /C wscript.exe D:\pamtest\myload.vbs', 0, true );
var_dump($obj->Run);
} else {
echo 'can not create wshell object';
} // end if
$obj = null;
//$this->load->view('goodPush');
} // end saveToPM function
We have enabled DCon in the php.ini file and used dcomcnfg to enable permissions for the user.
I borrowed the code from http://www.sitepoint.com/forums/showthread.php?505709-run-a-vbs-from-php.
The screen echos "Code executed" but the vbscript does not run.
We have been fighting with this for a while so any help is GREATLY appreciated.
It's a bit messy. PHP calls WScript.Shell.Run which will call cmd (with /c - i.e terminate cmd.exe when it's done its thing) which will call cscript.exe to run and interpret a .vbs. As you can see quite a few things that have to go right! :)
What if you 'wait' for the WScript.Shell.Run call to end (your $wait variable) before continuing execution of the wsh script which will in turn allow PHP to continue execution etc?
Since you're not waiting for the call to finish, PHP thinks its all good and continues onto the next line (interpreted language).
Also, maybe have the .vbs create an empty text file? Just so you have an indication that it has actually run.
Just take a step back, have a beer and it'll come to you! Gogo troubleshoot!
And - http://ss64.com/vb/run.html
If bWaitOnReturn is set to TRUE, the Run method returns any error code returned by the application.
I've tested your code with a stand-alone PHP script (without Codeigniter) on a Windows XP machine, with the PHP 5.4.4 built-in web server, and I've noticed that the vbscript gets executed, but PHP dies (the event viewer shows a generic "Application Error" with ID 1000).
However I've also discovered that removing the "cmd /C" from the command string solves the problem.
Here is the simple script that I've used for my test:
<?php
$obj = new COM('WScript.Shell');
if (is_object($obj)) {
//$obj->Run('cmd /C wscript.exe test.vbs', 0, true); // This does'nt work
$obj->Run('wscript.exe test.vbs', 0, true); // This works
var_dump($obj->Run);
} else {
echo 'can not create wshell object';
}
$obj = null;
?>
And this is my simple "test.vbs" script:
WScript.Echo "vbscript is running"
Another solution that seems to be working (at least on my platform) is the "system" call:
system('wscript.exe test.vbs');
Unfortunately I don't have a 64-bit IIS system to test with, so I can't really say if there are specific problems on this platform, but I hope this helps.
I am trying to run a R script from PHP, and in R script, I will create test.jpg image, and in PHP, I will display this image on web.
The R is 2.11.1 and OS is Ubuntu 10.10.
The problem is: this .jpg is created successfully if I run from terminal, but no image created if I run from WebUI. I run the script from terminal and WebUI in the same directory. /opt/lampp/htdocs/name/. (If somebody can tell me a good tool to debug this WebUI, it would be great. I put some echo in the .php file, I see the functions being called, but still do not know how to solve this bug).
The .jpg is created when I run from terminal:
php r_caller.php
In this r_caller.php, I have simple function as:
<?php php_call_r(){
$cmd = "echo 'argv <- \"r_command.r\"; source(argv)' | " .
"/usr/bin/R --vanilla --slave";
$ret = system($cmd);
echo $ret;}
?>
and this php_call_r function is called in the same file as r_caller.php:
<?php
//some irrelavant codes above
php_call_r();
print("<img src=test.jpg>");
?}
and in the r_command.r script, I have simple commands as:
jpeg("test.jpg")
plot(50, 50)
dev.off()
I really appreciate your help!
You did not specify your platform and R version, but on unix the jpeg() device may require X11 to render the image (which you may have in your interactive session but not in apache). You may be better off using the Cairo package or other means that don't require X11 session (recent R allows you to use alternative types in the jpeg call which you can also try - see ?jpeg).
(As a side note there is a PHP client to Rserve which makes web requests much faster - running R itself is pretty much the slowest way to use R from PHP. If you don't want to install any packages then you may want to use at least Rscript)
Edit: Now that you have added the R version - that's a really ancient one, you should seriously consider upgrading it. You can try installing Cairo with that old R version, but you may possibly need to go back there as well.
One more thing to consider, check you file privileges - make sure www-data has write-permissions wherever you will be creating the file (e.g., see echo system("pwd"); for the current directory R will be run in).
Check the Apache error logs to see if there are any errors. Try adding the following to the beginning of your PHP code:
error_reporting(E_ALL);
ini_set('display_errors','On');
This might be a copy/paste error, but your php_call_r function is not properly defined as a function. I suggest the following:
<?php function php_call_r() {
$cmd = "echo 'argv <- \"r_command.r\"; source(argv)' | " .
"/usr/bin/R --vanilla --slave";
$ret = system($cmd);
echo $ret;
}
?>
Executing R from PHP for each request is a very bad idea -- PHP piping is usually not reliable and R's output is optimized for interactive work rather than transmitting results. Moreover R starts for ages, so you waste lots of time and CPU power.
The better idea is to use R worker daemon, created by either Rserve or triggr -- Rserve has PHP client, for triggr you need to cook one on your own, but it is trivial; it may look like this:
R part (r.R)
require(triggr);
serve(function(data_from_php){
cat(sprintf("Called with: %s\n",data_from_php));
#<<Picture creation code>>
#Break connection notifying PHP that picture is done
return(endConnection("Done\r\n"));
},9090);
# ^- Port you want to use for internal communication
PHP part
<?php
$s=socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
echo "Connecting...\n";
if(socket_connect($s,"localhost",9090)){
echo "Connected!\n"; //v double \r\n is crucial
$d="some data for R\r\n\r\n";
socket_write($s,$d,strlen($d));
//This blocks until picture is done
$r=socket_read($s,6);
//Here we can emit the page featuring <img>
echo "Response was $r\n";
}
?>
Now you just fire r.R in background or under some auto-resurrection daemon and you are done.