Monthly Archives: October 2013

Taking photos with a phonegap/cordova application and uploading them to the server.

Last days I’ve working in a phonegap/cordova project. The main purpose of the project was taking photos with the device’s camera and sending them to the server. It’s a simple apache cordova project using the camera plugin. According to the documentation we can upload pictures with the following javascript code:

navigator.camera.getPicture(onSuccess, onFail, { 
    quality: 100,
    destinationType: Camera.DestinationType.DATA_URL
});

function onSuccess(imageData) {
    // here we can upload imageData to the server
}

function onFail(message) {
    alert('Failed because: ' + message);
}

As we can see we our plugin retrieves a base64 encoded version of our image and we can send it to the server, using jQuery for example

function onSuccess(imageData) {
  $.post( "upload.php", {data: imageData}, function(data) {
    alert("Image uploaded!");
  });
}

Our server side is trivial. We only need to read the request variable ‘data’, perform a base64 decode and we have our binary picture ready to be saved.

I test it with an old android smartphone (with a not very good camera) and it’s works, but when I tried to use it with a better android phone (with 8mpx camera) it hangs on and it didn’t work. After property reading the documentation I realized that it isn’t the better way to upload files to the server. It only works if the image file is small. Base64 increases the size of the image and our device can have problems handling the memory. This way Is also slow as hell.

The best way (the way that works, indeed) is, instead of sending the base64 files to the server, to save them into a device’s temporary folder and send them using the file transfer plugin.

var pictureSource;   // picture source
var destinationType; // sets the format of returned value

document.addEventListener("deviceready", onDeviceReady, false);

function onDeviceReady() {
    pictureSource = navigator.camera.PictureSourceType;
    destinationType = navigator.camera.DestinationType;
}

function clearCache() {
    navigator.camera.cleanup();
}

var retries = 0;
function onCapturePhoto(fileURI) {
    var win = function (r) {
        clearCache();
        retries = 0;
        alert('Done!');
    }

    var fail = function (error) {
        if (retries == 0) {
            retries ++
            setTimeout(function() {
                onCapturePhoto(fileURI)
            }, 1000)
        } else {
            retries = 0;
            clearCache();
            alert('Ups. Something wrong happens!');
        }
    }

    var options = new FileUploadOptions();
    options.fileKey = "file";
    options.fileName = fileURI.substr(fileURI.lastIndexOf('/') + 1);
    options.mimeType = "image/jpeg";
    options.params = {}; // if we need to send parameters to the server request
    var ft = new FileTransfer();
    ft.upload(fileURI, encodeURI("http://host/upload"), win, fail, options);
}

function capturePhoto() {
    navigator.camera.getPicture(onCapturePhoto, onFail, {
        quality: 100,
        destinationType: destinationType.FILE_URI
    });
}

function onFail(message) {
    alert('Failed because: ' + message);
}

I just realized that sometimes it fails. It looks like a bug of cordova plugin (I cannot assert it), because of that, if you read my code, you can see that if fp.upload fails I retry it (only once). With this little hack it works like a charm (and it’s also fast).

The server part is pretty straightforward. We only need to handle uploaded files. Here a minimalistic example with php

<?php
move_uploaded_file($_FILES["file"]["tmp_name"], '/path/to/file');

And that’s all. We can easily upload our photos from our smartphone using phonegap/cordova.

You can read more articles about cordova here.

Playing with event dispatcher and Silex. Sending logs to a remote server.

Today I continue playing with event dispatcher and Silex. Now I want to send a detailed log of our Kernel events to a remote server. We can do it something similar with Monolog, but I want to implement one working example hacking a little bit the event dispatcher. Basically we’re going to create one Logger class (implementing PSR-3 of course)

namespace G;

use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;

class Logger implements LoggerInterface
{
    private $socket;

    public function __construct($socket)
    {
        $this->socket = $socket;
    }

    function __destruct()
    {
        @fclose($this->socket);
    }

    public function emergency($message, array $context = array())
    {
        $this->sendLog($message, $context, LogLevel::EMERGENCY);
    }

    public function alert($message, array $context = array())
    {
        $this->sendLog($message, $context, LogLevel::ALERT);
    }

    public function critical($message, array $context = array())
    {
        $this->sendLog($message, $context, LogLevel::CRITICAL);
    }

    public function error($message, array $context = array())
    {
        $this->sendLog($message, $context, LogLevel::ERROR);
    }

    public function warning($message, array $context = array())
    {
        $this->sendLog($message, $context, LogLevel::WARNING);
    }

    public function notice($message, array $context = array())
    {
        $this->sendLog($message, $context, LogLevel::NOTICE);
    }

    public function info($message, array $context = array())
    {
        $this->sendLog($message, $context, LogLevel::INFO);
    }

    public function debug($message, array $context = array())
    {
        $this->sendLog($message, $context, LogLevel::DEBUG);
    }

    public function log($level, $message, array $context = array())
    {
        $this->sendLog($message, $context, $level);
    }

    private function sendLog($message, array $context = array(), $level = LogLevel::INFO)
    {
        $data = serialize([$message, $context, $level]);
        @fwrite($this->socket, "{$data}\n");
    }
}

As you can see our Logger class send logs to a remote server, with a socket passed within the constructor.
We also need one Service Provider called LoggerServiceProvider to integrate our Logger instance into our Silex application.

namespace G;

use Silex\Application;
use Silex\ServiceProviderInterface;

class LoggerServiceProvider implements ServiceProviderInterface
{
    private $socket;

    public function __construct($socket)
    {
        $this->socket = $socket;
    }

    public function register(Application $app)
    {
        $app['remoteLogger'] = $app->share(
            function () use ($app) {
                return new Logger($this->socket);
            }
        );
    }

    public function boot(Application $app)
    {
    }
}

And now the last part is our Silex application:

use G\LoggerServiceProvider;
use G\Silex\Application;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel;
use Symfony\Component\HttpKernel\Event;

$app = new Application();
$app->register(new LoggerServiceProvider(stream_socket_client('tcp://localhost:4000')));

$app->on(HttpKernel\KernelEvents::REQUEST, function (Event\GetResponseEvent $event) use ($app) {
        $app->getLogger()->info($event->getName());
    }
);

$app->on(HttpKernel\KernelEvents::CONTROLLER, function (Event\FilterControllerEvent $event) use ($app) {
        $app->getLogger()->info($event->getName());
    }
);

$app->on(HttpKernel\KernelEvents::TERMINATE, function (Event\PostResponseEvent $event) use ($app) {
        $app->getLogger()->info($event->getName());
    }
);

$app->on(HttpKernel\KernelEvents::EXCEPTION, function (Event\GetResponseForExceptionEvent $event) use ($app) {
        $app->getLogger()->critical($event->getException()->getMessage());
    }
);

$app->get('/', function () {
    return 'Hello';
});

$app->run();

As we can see the event dispacher send each event to a remote server (in this example: tcp://localhost:4000). Now we only need a tcp server to handle those sockets. We can use different tools and libraries to do that. In this example we’re going to use React.

use React\EventLoop\Factory;
use React\Socket\Server;

$loop   = Factory::create();
$socket = new Server($loop);

$socket->on('connection', function (\React\Socket\Connection $conn){
    $unique = uniqid();
    $conn->on('data', function ($data) use ($unique) {
            list($message, $context, $level) = \unserialize($data);
            echo date("d/m/Y H:i:s")."::{$level}::{$unique}::{$message}" . PHP_EOL;
        });
});

echo "Socket server listening on port 4000." .PHP_EOL;
echo "You can connect to it by running: telnet localhost 4000" . PHP_EOL;

$socket->listen(4000);
$loop->run();

Now we only need to start our servers:
our silex one

php -S 0.0.0.0:8080 -t www

and the tcp server

php app/server.php

One screencast showing the prototype in action:

You can see the full code in my github account.

Using the event dispatcher in a Silex application

Symfony has one component called The Event Dispatcher. This component is one implementation of Mediator pattern and it’s widely used in modern frameworks, such as Symfony. Silex, as a part of Symfony, also uses this component and we can easily use it in our projects. Let me show you one little example. Imagine one simple route in Silex to create one png file containing one text:

$app->get("/gd/{text}", function($text) {
    $path = "/tmp/qr.png." . uniqid();
    $im = imagecreate(90, 30);
    $background = imagecolorallocate($im, 255, 255, 255);
    $color = imagecolorallocate($im, 0, 0, 0);
    imagestring($im, 5, 5, 5,  $text, $color);
    imagepng($im, $path);
    imagedestroy($im);
    return $app->sendFile($path);
});

It works, but there’s one mistake. We need to unlink our temporally file $path, but where? We need do if after “return $app->sendFile($path);” but it’s not possible.

$app->get("/gd/{text}", function($text) {
    $path = "/tmp/qr.png." . uniqid();
    $im = imagecreate(90, 30);
    $background = imagecolorallocate($im, 255, 255, 255);
    $color = imagecolorallocate($im, 0, 0, 0);
    imagestring($im, 5, 5, 5,  $text, $color);
    imagepng($im, $path);
    imagedestroy($im);
    return $app->sendFile($path, 200, ['Content-Type' => 'image/png']);;
    unlink($path); // unreachable code
});

We can use BinaryFileResponse instead of the helper function “sendFile”, but there’s one smarter solution: The event dispatcher.

use Symfony\Component\HttpKernel\KernelEvents;

$app->get("/gd/{text}", function($text) use (app) {
    $im = imagecreate(90, 30);
    $path = "/tmp/qr.png." . uniqid();
    $background = imagecolorallocate($im, 255, 255, 255);
    $color = imagecolorallocate($im, 0, 0, 0);
    imagestring($im, 5, 5, 5,  $text, $color);
    imagepng($im, $path);
    imagedestroy($im);
    
    $app['dispatcher']->addListener(KernelEvents::TERMINATE, function() use ($path) {
        unlink($path);
    });

    return $app->sendFile($path, 200, ['Content-Type' => 'image/png']);
});

(Updated! thanks to Hakin’s recommendation)
Or even better using Silex’s Filters. In this case we after or finish. In fact those filters are nothing more than an elegant way to speak to the event dispatcher.


$app->get("/gd/{text}", function($text) use (app) {
    $im = imagecreate(90, 30);
    $path = "/tmp/qr.png." . uniqid();
    $background = imagecolorallocate($im, 255, 255, 255);
    $color = imagecolorallocate($im, 0, 0, 0);
    imagestring($im, 5, 5, 5,  $text, $color);
    imagepng($im, $path);
    imagedestroy($im);
    
    $app->after(function() use ($path) {
        unlink($path);
    });

    return $app->sendFile($path, 200, ['Content-Type' => 'image/png']);
});

We also can use the generic function to add events to the event listener:

use Symfony\Component\HttpKernel\KernelEvents;

$app->get("/gd/{text}", function($text) use (app) {
    $im = imagecreate(90, 30);
    $path = "/tmp/qr.png." . uniqid();
    $background = imagecolorallocate($im, 255, 255, 255);
    $color = imagecolorallocate($im, 0, 0, 0);
    imagestring($im, 5, 5, 5,  $text, $color);
    imagepng($im, $path);
    imagedestroy($im);
    
    $app->on(KernelEvents::TERMINATE, function() use ($path) {
        unlink($path);
    });

    return $app->sendFile($path, 200, ['Content-Type' => 'image/png']);
});

Now our temporally file will be deleted once a response is sent. Life is simpler with event dispatcher 🙂