0

Gestor de descargas con AngularJS y servicios REST – 2º Creando el servicio REST y el backend

angularjs_upload_1280x520

En el último capítulo nos quedamos en el desarrollo de la interfaz de nuestro gestor de descargas con AngularJS. En este capítulo nos enfocaremos en la parte del backend, que será la responsable de la subida de los archivos.

Ya que los desarrolladores de plupload nos dan una plantilla en PHP para manejar las subidas, he optado por reutilizarla haciendo las modificaciones pertinentes. Estas modificaciones irán relacionados a crear un servicio REST que se ocupe de toda la lógica de peticiones que se harán. Estas peticiones pueden ser la subida del archivo mediante POST, actualizar algún dato del mismo mediante UPDATE, borrar alguna entrada con DELETE, así como mostrar un listado de los archivos mediante GET. Con todo esto dicho, pongámonos manos a la obra.

Usaremos MongoDB para la lógica de datos. Puesto que estaremos usando PHP, necesitaremos instalar el driver para conectarnos a nuestra instancia mongodb. Para ello nos dirigiremos a esta página y seguiremos el tutorial de instalación.

Una vez instalado, crearemos nuestro archivo PHP que funcionará como servicio REST. Puesto que PHP es un lenguaje destinado principalmente a la programación web, será necesario que este archivo se ejecute en un entorno servidor web. Yo usaré para ello Apache ya que considero que es el más versátil para ello y ya viene con los módulos necesarios para interpretar los scripts de PHP. El archivo que creemos llevará por nombre rest_upload.php que ubicaremos dentro de la carpeta uploads que crearemos en el directorio que apache sirve nuestras páginas web.

Creando el script REST y nuestro clase conexión

 

Puesto que usaremos MongoDB, vamos a crear aparte una clase que funcione a modo de conector usando el controlador que instalamos y que tendrá por nombre mongo-connection.php. Con esta clase se definirá todas las operaciones que realizaremos con el servidor mongo:

<?php
/**
 * Created by PhpStorm.
 * User: Tone
 * Date: 5/3/16
 * Time: 13:07
 */


class MongoConnection {

    private $manager;
    private $bulk;
    private $collection;


    function initConnection($collection){

        $this->manager = new MongoDB\Driver\Manager("mongodb://localhost:27017");
        $this->collection = $collection;

    }

    function getManager(){
        return $this->manager;
    }

    function insertOperation($datas){

        $this->bulk = new MongoDB\Driver\BulkWrite(['ordered' => true]);

        foreach($datas as $data) {
           $this->bulk->insert($data);
        }

        $writeConcern = new MongoDB\Driver\WriteConcern(MongoDB\Driver\WriteConcern::MAJORITY, 1000);

        try {
            $result = $this->manager->executeBulkWrite($this->collection, $this->bulk, $writeConcern);
        } catch (MongoDB\Driver\Exception\BulkWriteException $e) {
            $result = $e->getWriteResult();

            // Check if the write concern could not be fulfilled
            if ($writeConcernError = $result->getWriteConcernError()) {
                printf("%s (%d): %s\n",
                    $writeConcernError->getMessage(),
                    $writeConcernError->getCode(),
                    var_export($writeConcernError->getInfo(), true)
                );
            }

            // Check if any write operations did not complete at all
            foreach ($result->getWriteErrors() as $writeError) {
                printf("Operation#%d: %s (%d)\n",
                    $writeError->getIndex(),
                    $writeError->getMessage(),
                    $writeError->getCode()
                );
            }
        } catch (MongoDB\Driver\Exception\Exception $e) {
            printf("Other error: %s\n", $e->getMessage());
            exit;
        }

        return $result->getInsertedCount();
    }

    function updateOperation($data){

        $this->bulk = new MongoDB\Driver\BulkWrite();
        $this->bulk->update(
                ['_id' => $data->id],
                ['$set'=> ['name' => $data->name, 'url' => $data->url]],
                ['multi' => false, 'upsert' => false]
        );

        $writeConcern = new MongoDB\Driver\WriteConcern(MongoDB\Driver\WriteConcern::MAJORITY, 1000);

        try {
            $result = $this->manager->executeBulkWrite($this->collection, $this->bulk, $writeConcern);
        } catch (MongoDB\Driver\Exception\BulkWriteException $e) {
            $result = $e->getWriteResult();

            // Check if the write concern could not be fulfilled
            if ($writeConcernError = $result->getWriteConcernError()) {
                printf("%s (%d): %s\n",
                    $writeConcernError->getMessage(),
                    $writeConcernError->getCode(),
                    var_export($writeConcernError->getInfo(), true)
                );
            }

            // Check if any write operations did not complete at all
            foreach ($result->getWriteErrors() as $writeError) {
                printf("Operation#%d: %s (%d)\n",
                    $writeError->getIndex(),
                    $writeError->getMessage(),
                    $writeError->getCode()
                );
            }
        } catch (MongoDB\Driver\Exception\Exception $e) {
            printf("Other error: %s\n", $e->getMessage());
            exit;
        }

        return $result->getModifiedCount();
    }

    function deleteOperation($datas){

        $this->bulk = new MongoDB\Driver\BulkWrite(['ordered' => true]);

        foreach($datas as $id){
            $this->bulk->delete(["_id" => $id], ['limit' => 1]);
        }

        $writeConcern = new MongoDB\Driver\WriteConcern(MongoDB\Driver\WriteConcern::MAJORITY, 1000);

        try {
            $result = $this->manager->executeBulkWrite($this->collection, $this->bulk, $writeConcern);
        } catch (MongoDB\Driver\Exception\BulkWriteException $e) {
            $result = $e->getWriteResult();

            // Check if the write concern could not be fulfilled
            if ($writeConcernError = $result->getWriteConcernError()) {
                printf("%s (%d): %s\n",
                    $writeConcernError->getMessage(),
                    $writeConcernError->getCode(),
                    var_export($writeConcernError->getInfo(), true)
                );
            }

            // Check if any write operations did not complete at all
            foreach ($result->getWriteErrors() as $writeError) {
                printf("Operation#%d: %s (%d)\n",
                    $writeError->getIndex(),
                    $writeError->getMessage(),
                    $writeError->getCode()
                );
            }
        } catch (MongoDB\Driver\Exception\Exception $e) {
            printf("Other error: %s\n", $e->getMessage());
            exit;
        }

        return $result->getDeletedCount();
    }

    function queryOperationById($id){
        $filter = ['_id' => $id];
        $query = new MongoDB\Driver\Query($filter);
        $cursor = $this->manager->executeQuery($this->collection, $query);
        $row = $cursor->toArray();

        if(sizeof($row) > 0){
            return $row[0];
        }
        else{
            return null;
        }

    }

    function queryOperation(){
        $query = new MongoDB\Driver\Query([]);
        $cursor = $this->manager->executeQuery($this->collection, $query);
        $rows =  $cursor->toArray();

        if(sizeof($rows) > 0){
            return $rows;
        }
        else{
            return null;
        }
    }
}


Tenemos al comienzo una función initConnection que mediante la clase MongoDB\Driver\Manager recibirá la instancia mongo que estemos ejecutando en nuestra máquina. Por defecto se ejecutará en el puerto 27017.

Ten en cuenta que para arrancar el servidor debes abrir una consola y ejecutarlo mediante el comando mongod. A esta función se le pasará también un collection que será el nombre de la colección que crearemos para almacenar los registros. La función getManager nos devolverá la instancia creada de la conexión.

A continuación tenemos la función insertOperation el cual recibirá un array de registros a introducir, donde cada uno de estos registros también será un array con los campos definidos a insertar. Se crea una instancia MongoDB\Driver\BulkWrite que colecciona una o más operaciones de escritura que se enviarán al servidor y otra instancia MongoDB\Driver\WriteConcern los cuales se les pasará finalmente a la función executeBulkWrite para ejecutar las operaciones en el servidor. Cualquier error generado será capturado por MongoDB\Driver\Exception\BulkWriteException y MongoDB\Driver\Exception\Exception y será notificado, en otro caso, se devolverá el número de registros insertados. Este mismo conjunto de operaciones se realizará tanto en la función deleteOperation como updateOperation, donde se borrará el archivo por su id y se actualizará el nombre del mismo respectivamente. MongoDB\Driver\WriteConcern define el nivel de prioridad en la petición que MongoDB destinará para las operaciones de escritura.

Para la función updateOperation enviaremos el id y el nombre del archivo a editar y para deleteOperation el id del registro a borrar.

Ahora creamos el script REST:


<?php
/**
 * Created by PhpStorm.
 * User: Tone
 * Date: 13/2/16
 * Time: 16:42
 */

require_once('mongo-connection.php');

// Turning OFF php report, comment below to debug purposes.
error_reporting(E_ALL);
@ini_set('display_errors', 0);

if (isset($_SERVER['HTTP_ORIGIN']) && $_SERVER['HTTP_ORIGIN'] == 'http://localhost:9000') {
    header("Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}");
    header('Access-Control-Allow-Credentials: true');
    header('Access-Control-Max-Age: 86400');
}

else{
    return;
}

if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {

    if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']))
        header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS");

    if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']))
        header("Access-Control-Allow-Headers: {$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}");
}


// Set content type headers to json
header("Content-Type: application/json");


// Set target dir
$target_dir = $_SERVER[DOCUMENT_ROOT]."/uploads/files/";

//Set connection
$connection = new MongoConnection();
$connection->initConnection('db.uploads');

switch($_SERVER['REQUEST_METHOD']){
    case "GET" :
        getRequest();
        break;
    case "POST":
        postRequest();
        break;
    case "PUT":
        updateRequest();
        break;
    case "DELETE":
        deleteRequest();
        break;
    default:
        break;

}

function getRequest(){

    // Set connection
    global $connection;

    // For individual file
    if(isset($_GET['id'])){
        $row = $connection->queryOperationById($_GET['id']);
        if(isset($row)){
            $response['row'] = $row;
            return send_response(200,$response,"Get file ".$row->name." OK");
        }
        else{
            return send_response(404,$response,"Get file with id ".$_GET['id']." NOT FOUND");
        }

    }
    // For query all files
    else{
        $rows = $connection->queryOperation();
        if(isset($rows)){
            $response['rows'] = $rows;
            return send_response(200,$response," GET rows files OK");
        }
        else{
            return send_response(404,$response," GET rows files NOT FOUND");
        }
    }
}

function updateRequest(){

    // Set connection
    global $connection;
    global $target_dir;

    $row = json_decode(file_get_contents("php://input"));

    if(isset($row)){
        $rowOld = $connection->queryOperationById($row->id);
        $url = ($_SERVER['SERVER_NAME']==='localhost') ? 'http://localhost/uploads/files/' : $_SERVER['SERVER_NAME'];
        $row->url = $url . $row->name;
        if($connection->updateOperation($row) > 0){
            rename($target_dir.$rowOld->name, $target_dir.$row->name);
            return send_response(200, $response, " UPDATE file ".$row->name." success!");
        }
        else{
            return send_response(500, $response, "There was an error updating file ".$row->name);
        }
    }
    else{
        return send_response(401, $response, "Bad request.");
    }

}

function deleteRequest(){
    // Set connection
    global $connection;

    // For individual file
    if(isset($_GET['id'])){
        $row = $connection->queryOperationById($_GET['id']);
        if(isset($row)){
            if($connection->deleteOperation($row) > 0){
                // Set target dir
                global $target_dir;
                $target_file = $target_dir . $row->name;
                if (file_exists($target_file)) {
                    unlink($target_file);
                }
                $response['row'] = $row;
                return send_response(200, $response, "File id: ".$row->_id." deleted OK");
            }
            else{
                return send_response(500, $response, "There was an error in server on DELETE file ".$row->name);
            }
        }
        else{
            return send_response(404, $response, "Cannot DELETE file with id ".$_GET['id']." NOT FOUND");
        }

    }
    else{
        return send_response(401, $response, "Bad request.");
    }

}

function postRequest(){

    // Get a file name
    if (isset($_REQUEST["name"])) {
        $fileName = $_REQUEST["name"];
    } elseif (!empty($_FILES)) {
        $fileName = $_FILES["file"]["name"];
    } else {
        $fileName = uniqid("file_");
    }

    // Set target dir
    global $target_dir;
    $target_file = $target_dir . $fileName;
    $uploadOk = 1;
    $errorUploadFile = "";

    if ($errorUploadFile = move_uploaded_file($_FILES['file']['tmp_name'], $target_file)) {
        $url = ($_SERVER['SERVER_NAME']==='localhost') ? 'http://localhost/uploads/files/' : $_SERVER['SERVER_NAME'];
        $url = $url . $fileName;
        $datas = array(
            array("_id" => bin2hex(random_bytes(20)), "name" => $fileName, "type" => $_FILES['file']['type'],
                "size" => $_FILES['file']['size'], "url" => $url)
        );

        // Set connection
        global $connection;

        // We have records inserted

        if($connection->insertOperation($datas) > 0){

            // Search for the record
            $row = $connection->queryOperationById($datas[0]['_id']);
            $response['row'] = $row;
            return send_response(200, $response, "Insert file ".$fileName." OK");
        }
        // There was an error inserting the records.
        else{
            return send_response(500, $response, "There was an error inserting the file ".$fileName);
        }

    } else {
        return send_response(500, $response, "There was an error uploading the file.");
    }


}

function send_response($status, $response, $message){
    $response['status'] = $status;
    $response['message'] = $message;
    header("HTTP/1.1 ". $response['status'].": ". $response['message']."");
    echo json_encode($response);
    return;
}

 

En las primeras líneas establecemos el CORS domain para que únicamente acepte conexiones de ningún otro dominio a menos que provengan de localhost:9000 que es donde se desplegará la aplicación web. También se permitirá el acceso al recurso mediante los diferentes métodos con Access-Control-Allow-Methods y las cabeceras establecidas por la petición. También se establecerá el tipo de contenido como respuesta en formato json mediante Content-Type: applicaction/json.

A continuación, establecemos una ruta global a la carpeta files dentro de la carpeta uploads en el servidor web, donde se alojarán los archivos, y crearemos una nueva conexión MongoConnection. Se le pasa el nombre de la colección, en este caso, la llamaremos db.uploads.

Mediante un switch controlaremos los diferentes métodos en la petición que recibamos en el script:

  • Para las peticiones GET, miramos si $_GET[‘id’] está definido. De esa manera se devolverá únicamente la información de dicho archivo asociado a esa id y devolveremos la respuesta con estado 200 junto a la información. En otro caso se devolverá todos los registros de la colección.

 

  • Para las peticiones PUT mediante el flujo de entrada php://input se leerá los campos a modificar para el registro y el archivo en la carpeta files. En este caso sólo se pasará el id y el nombre a modificar del archivo.

 

  • Para las peticiones DELETE, sólo se pasará el id y se comprobará si existe en la colección. De ser así, se borrará el registro y el archivo.

 

  • Por último, en las peticiones POST se realizará una serie de operaciones previas a la inserción. Se comprobará si tenemos el nombre del archivo establecido, de no ser así, se creará uno al azar. Se procederá a guardar el archivo entonces y una vez acabado se llamará a la función move_uploaded_file el cual moverá el archivo desde la carpeta temporal a la carpeta files y se guardará la url a la que apunte el archivo; así como los campos nombre, tipo de archivo y tamaño. Se insertará entonces el registro y se devolverá como respuesta.

Con esto ya estaría definido el script. El resto de excepciones 404500 y 401 irán relacionados con problemas a la hora de encontrar un registro, errores de ejecución a la hora de realizar las operaciones contra la base de datos o un fallo en la construcción de la petición.

Para que todo esto funcione correctamente, todas las peticiones que hagamos tendrán que seguir un patrón, por lo tanto tendremos que redefinir las reglas url de acceso al script rest_upload.php:


Options +FollowSymLinks
RewriteEngine on
RewriteRule ^upload/?$ rest_upload.php [L]
RewriteRule ^upload/([^/.]+)/?$ rest_upload.php?id=$1 [L] # GET/UPDATE/DELETE certain file

Cada vez que pasemos la ruta /upload, se llamará al método GET para traernos todos los registros o POST para crear un nuevo. Por otro lado cuando se llame a upload/:id se realizará el método GET, UPDATE o DELETE para el id introducido.

Al final en el backend tendremos la estructura como sigue:

Captura de pantalla 2016-03-27 a las 14.01.25.png

Volviendo a nuestra aplicación web, usaremos la dependencia ngResources para realizar las distintas operaciones contra el script que hemos creado. Para ello crearemos en nuestra carpeta scripts una carpeta services y a su vez un archivo con el nombre uploadFactory.js. Este archivo contendrá lo siguiente:


'use strict';

angular.module('puploadAngularApp')
        .constant("baseURL","http://localhost/uploads/")
        .service('uploadFactory', ['$resource', 'baseURL',
         function($resource, baseURL) {

           this.getFiles = function(){
             return $resource(baseURL+"upload/:id",{id:'@id'},{
               'update': {method:'PUT'}, // method for issue a update request
               'query': {
                    method: 'GET',
                    transformResponse: function (data) {
                      data = $.parseJSON(data);
                      if(data.status===200){
                          return data.rows;
                      }
                      else{
                        console.log(data.status+" -> "+data.message);
                        return undefined;
                      }

                    },
                    isArray: true //since your list property is an array
                }
             },
             {
                stripTrailingSlashes: false
             });
           }

        }])
;


Inyectando la dependencia $resource, le pasamos una constante donde definiremos la url base a la cual angular se conectará para realizar las operaciones REST. Si apreciamos de nuevo el método $resource, le estamos pasando una url con la estructura: upload/:id que es el patrón definido anteriormente en el htaccess. Mención especial al método query que es el que hará la consulta. Por defecto nos devuelve un array con los datos, pero necesitamos redefinir la respuesta y que nos devuelva los datos o no en función de si existen al realizar la consulta a la base da datos.

Para el método UPDATE, nos enfocaremos  en la edición del nombre del archivo. Usaremos el plugin angular-xeditable con el que podremos hacer click en el nombre y editarlo fácilmente. Abriremos un terminal, nos dirigiremos a la carpeta del proyecto y ejecutaremos el siguiente comando:

bower install angular-xeditable

Incluímos el script y el css en el index.html así como inyectar la dependencia en la definición de la aplicación en app.js:

<link href="bower_components/angular-xeditable/dist/css/xeditable.css" rel="stylesheet">
<script src="bower_components/angular-xeditable/dist/js/xeditable.js"></script>
angular
.module('puploadAngularApp', [
            'ngResource',
            'ngRoute',
            'xeditable'
]);

Volviendo a los archivos filesupload.js y filesupload.html tenemos que realizar unos cambios sustanciales. En primer lugar, se creará una nueva fila donde se mostrará todos los archivos que hayamos ido subiendo, donde cada uno de ellos contendrá la lógica de actualización y borrado.

Para la actualización, creamos un método $scope.updateNameFile que recibe el id y el nuevo nombre a cambiar. Se llama al método update del servicio uploadFactory.getFiles() que definimos y esperaremos mediante un $promise a que acabe la llamada asíncrona. En caso de éxito, establecemos una nueva variable $scope.successUpdated a true y mostraremos un mensaje en el DOM notificando el cambio.


$scope.updateNameFile = function(id, data){
     uploadFactory.getFiles().update({id: id, name: data})
      .$promise.then(
        function(response){
            console.log("update success!");
            console.log(response);
            $scope.succesUpdated = true;
            var index = $scope.filesUploaded.indexOfObject("_id",id);
            $scope.filesUploaded[index].succesUpdated = true;
            $timeout(function(){
              $scope.filesUploaded[index].succesUpdated = false;
            }, 3000);
        },
        function(error){
          console.log("update error! "+error);
        });

   };

Y en el html quedaría de la siguiente manera:


<div ng-show="f.succesUpdated" class="alert alert-success fade in">
   <a class="close" data-dismiss="alert" aria-label="close">&times;</a>
   <strong>Success file name updated!</strong><br>
</div>

Para el borrado, crearemos el método $scope.deleteFile que recibirá el id del archivo, y como ocurriría con el método de actualización, se pasará un $promise y en caso de éxito se borrará del DOM dicho archivo quitando el elemento a través de su índice en $scope.filesUploaded.

Definiremos una variable $scope.notFilesUploaded que funcionará a modo de información, mostrando un mensaje cuando se inicie la aplicación web y se llame a la operación query. Si no recibe nada del servidor, se pondrá a true y mostrará el mensaje de que existen archivos subidos. En otro caso, será falso y se mostrará los archivos. Esta lógica se contralará con la directiva ng-if:


<div ng-if="notFilesUploaded" class="alert alert-warning fade in">
   <a class="close" data-dismiss="alert" aria-label="close">&times;</a>
   <strong>There's no files yet, just upload some files!</strong><br>
</div>

...

Por último y para ir acabando, en el método FileUploaded de la librería plupload la respuesta que recibamos en response hay que formatearla en JSON, y de esa manera, accedemos al registro para introducirlo en $scope.fileUploaded. También definiremos la url a la que apuntará plupload para subir los archivos, que será http://localhost/uploads/upload/

Al final la estructura tanto para filesupload.html y filesupload.js quedaría así:


<style media="screen">
@-webkit-keyframes spin2 {
    from { -webkit-transform: rotate(0deg);}
    to { -webkit-transform: rotate(360deg);}
}

</style>
<div class="container">

  <div class="col-xs-12">
    <div class="row marketing">
      <div id="container">
        <div class="row">
          <div class="col-xs-10">
            <div class="panel panel-default">
              <div class="panel-heading"><h4>Form file upload</h4></div>
              <div class="panel-body">
                <p>Browse for files you want upload to. Only jpg and png files will show a preview, otherwise a glyphicon file will show instead.</p>
                <div id="upload-form" class="row">
                    <button type="button" id="pickfiles" class="btn btn-success">
                      Browse File
                    </button>
                    <button type="button" ng-click="uploadFiles()" id="uploadfiles" class="btn btn-warning">
                      Start Upload
                    </button>

                    <ul class="filelist list-unstyled">
                      <li ng-repeat="f in filesUploads">
                        <div class="media">
                         <div class="media-left">
                           <div class="media-object" id="preview-{{f.id}}"></div>
                         </div>
                         <div class="media-body">
                           <h4 class="media-heading info-file">{{ f.name }}</h4>
                           <h5>File size: <b>{{ f.formatSize }}</b></h5>
                           <h5>File type: <b>{{ f.type }}</b></h5>

                           <div ng-show="f.showError" class="alert alert-danger fade in">
                            <a class="close" data-dismiss="alert" aria-label="close">&times;</a>
                            <strong>Error!</strong> {{f.error}}<br>
                           </div>
                             <div class="row">
                               <div class="col-xs-10">
                                 <div ng-show="f.showProgress && !f.showError" class="progress progress-striped active">
                                    <div class="progress-bar progress-bar-custom" role="progressbar" style="width: {{f.progressFile}}%;">
                                        <span class="sr-only">{{f.progressFile}}%</span>
                                    </div>
                                 </div>
                               </div>
                               <div class="col-xs-2">
                                 <span ng-show="f.processingFile" class="glyphicon glyphicon-refresh glyphicon-refresh-animate"></span>
                                 <span ng-show="f.complete" class="glyphicon glyphicon glyphicon-ok"></span>
                               </div>
                             </div>
                         </div>
                       </div>
                      </li>
                    </ul>
                </div>
              </div>
            </div>
          </div>
        </div>
        <div class="row">
           <div class="col-xs-10">
             <div class="panel panel-default">
               <div class="panel-heading"><h4>Form files uploaded</h4></div>
               <div class="panel-body">
                 <p>Files uploaded will be shown below.</p>
                 <div ng-if="notFilesUploaded" class="alert alert-warning fade in">
                  <a class="close" data-dismiss="alert" aria-label="close">&times;</a>
                  <strong>There's no files yet, just upload some files!</strong><br>
                 </div>
                     <ul ng-if="!notFilesUploaded" class="filelist list-unstyled">
                       <li ng-repeat="f in filesUploaded track by f._id">
                         <div class="media">
                          <div class="media-left">
                            <a href="{{f.url}}">
                              <img ng-if="f.type == 'image/png' || f.type == 'image/jpg' || f.type == 'image/jpeg'" 
                                class="media-object" width="100" height="100" src="{{f.url}}" alt="{{f.name}}">
                              <video ng-if="f.type == 'video/mp4' || f.type == 'video/ogg' || f.type == 'video/avi' || f.type == 'video/mpg' || f.type == 'video/x-matroska'"
                               width="100" height="100" src="{{f.url}}" controls>
                                <source ng-src="{{f.url}}" type="{{f.type}}"/>
                              </video>
                            </a>
                          </div>
                          <div class="media-body">
                            <h4 class="media-heading info-file" onbeforesave="updateNameFile(f._id, $data)" editable-text="f.name">
                              {{ f.name }}
                            </h4>
                            <h5>File size: <b>{{ formatSize(f.size) }}</b></h5>
                            <h5>File type: <b>{{ f.type }}</b></h5>

                            <div ng-show="f.succesUpdated" class="alert alert-success fade in">
                             <a class="close" data-dismiss="alert" aria-label="close">&times;</a>
                             <strong>Success file name updated!</strong><br>
                            </div>
                            <button ng-click="deleteFile(f._id)" type="button" class="btn btn-danger" name="button">Delete</button>
                          </div>
                        </div>
                       </li>
                     </ul>
               </div>
             </div>
           </div>
        </div>
      </div>
     <br>

  </div>

</div>


'use strict';

/**
 * @ngdoc function
 * @name puploadAngularApp.controller:FilesuploadCtrl
 * @description
 * # FilesuploadCtrl
 * Controller of the puploadAngularApp
 */
angular.module('puploadAngularApp')
  .controller('FilesuploadCtrl', ['$scope', 'uploadFactory','$timeout',
    function ($scope, uploadFactory, $timeout) {
    $scope.filesUploads = [];
    $scope.filesUploaded = [];
    $scope.succesUpdated = false;
    $scope.notFilesUploaded = false;
    $scope.uploader = new plupload.Uploader({
     runtimes : 'html5,flash,silverlight,html4',
     browse_button : 'pickfiles', // you can pass an id...
     container: document.getElementById('container'), // ... or DOM Element itself
     url : 'http://localhost/uploads/upload/',
     flash_swf_url : '../plupload/Moxie.swf',
     silverlight_xap_url : '../plupload/Moxie.xap',

     //chunk_size: '50000kb',

     filters : {
       max_file_size : '10000mb',
       mime_types: [
         {title : "Image files", extensions : "jpg,gif,png"},
         {title : "Zip files", extensions : "zip"},
         {title : "Video files", extensions : "avi,mp4,wmv,mkv"},
         {title : "PDF files", extensions : "pdf"}
       ]
     },
     headers: {
         'Access-Control-Allow-Origin' : 'http://localhost:9000/'
     },

     init: {
       PostInit: function() {
         $scope.uploadFiles = function(){
           $scope.uploader.start();
           return false;
         };
       },

       FilesAdded: function(up, files) {
         plupload.each(files, function(file) {
           file.formatSize = plupload.formatSize(file.size);
           file.progress = 0;
           file.showProgress = false;
           file.showError = false;
           file.processingFile = false;
           file.complete = false;
           $scope.filesUploads.push(file);
           $scope.$apply(); // probably plupload is blocking the scope apply event so we have to aply ourselves.

           // Set preview image
           var img = new mOxie.Image();
        		img.onload = function() {
                this.embed($('#preview-'+file.id).get(0), {
                  width: 100,
                  height: 100,
                  crop: true
                });
        		};
        		img.onembedded = function() {
        			this.destroy();
        		};
        		img.onerror = function() {
        			this.destroy();
        		};
            if(file.type === 'image/jpeg' || file.type === 'image/png'){
                img.load(file.getSource());
            }
            else{
              $('#preview-'+file.id).prepend('<span style="font-size: 100px; color: #fff;"><i class="glyphicon glyphicon-file"></i></span>');
            }
         });
       },

       BeforeUpload: function(up, file) {
         file.showProgress = true;
         file.showError = false;
       },

       UploadProgress: function(up, file) {
         file.progressFile = file.percent;
         file.processingFile = false;
         if(file.percent === 100){
           file.processingFile = true;
         }
         $scope.$apply();
       },

       FileUploaded: function(up, file, response) {
         response.response = $.parseJSON(response.response);
         console.log("File uploaded: "+file.name);
         console.log("Response from the server: "+response.message+ " with status: "+ response.status);
         console.log("Response headers: "+response.responseHeaders);
         file.processingFile = false;
         file.complete = true;
         $scope.filesUploaded.push(response.response.row);
         $scope.notFilesUploaded = false;
         $scope.$apply();
       },

       Error: function(up, err) {
         err.file.error = err.response+" with status: "+err.status;
         err.file.showError = true;
         err.file.showProgress = false;
         err.file.processingFile = false;
         err.file.complete = false;
         $scope.$apply();
         console.log(err.file.name+" showError: "+err.file.showError);
         console.log("Error Plupload code#" + err.code + ": " + err.message);
         console.log("Error with file: "+err.file.name);
         console.log("Response from the server: "+err.response.message+" with status: "+err.status);
         console.log("Response headers: "+err.responseHeaders);
       }
     }
   });

   $scope.formatSize = function(size){
     return plupload.formatSize(size);
   }

   /**
    * @ngdoc function
    * @name deleteFile
    * @description
    *
    * Function to delete a file passing its id.
    */

   $scope.deleteFile = function(id1){

    uploadFactory.getFiles().delete({id: id1})
     .$promise.then(
       function(response){
         var index = $scope.filesUploaded.indexOfObject("_id",response.row._id);
         $scope.filesUploaded.splice(index, 1);
         console.log("delete success!");
       },
       function(error){
         console.log("delete error! "+error);
       }
     );

   };

   Array.prototype.indexOfObject = function arrayObjectIndexOf(property, value) {
    for (var i = 0, len = this.length; i < len; i++) {
        if (this[i][property] === value){
          return i;
        }
     }
     return -1;
   };

   /**
    * @ngdoc function
    * @name updateNameFile
    * @description
    *
    * Function to update a file name passing its id.
    */

   $scope.updateNameFile = function(id, data){
     uploadFactory.getFiles().update({id: id, name: data})
      .$promise.then(
        function(response){
            console.log("update success!");
            console.log(response);
            $scope.succesUpdated = true;
            var index = $scope.filesUploaded.indexOfObject("_id",id);
            $scope.filesUploaded[index].succesUpdated = true;
            $timeout(function(){
              $scope.filesUploaded[index].succesUpdated = false;
            }, 3000);
        },
        function(error){
          console.log("update error! "+error);
        });

   };


   $scope.uploader.init();

   // initial load
   $scope.init = function(){
     uploadFactory.getFiles().query().$promise
     .then(function(response){
            $scope.notFilesUploaded = false;
            $scope.filesUploaded = response;
            console.log(response);
          },
          function(error){
            if(error.status === 404){
              $scope.notFilesUploaded = true;
            }
            console.log(error);
          });
   };

   $scope.init();

  }])
  /*.filter('trusted', ['$sce', function ($sce) {
    return function(url) {
        return $sce.trustAsResourceUrl(url);
    };
  }]);*/;


Y con esto quedaría terminada la aplicación. Dejo abajo un enlace a mi GitHub donde podéis bajar el código o hacer las aportaciones que estiméis oportunas. Dejo a continuación el enlace a GitHub donde podéis bajaros el proyecto y arrancarlo en vuestra máquina: https://github.com/Tonetete/plupload_angular

Saludos y nos vemos en nuevas entradas 🙂

Tone

Ingeniero del Software y procrastinador sin remedio, interesado en todo lo que tenga que ver con el mundo del desarrollo web y la inteligencia artificial, no sé si seré el responsable de la creación de Skynet algún día pero se intenta.

ESCRIBIR UN COMENTARIO
  • (will not be published)

XHTML: Puedes usar estas etiquetas: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>