Category Archives: Linux

CPU Information

I have spent the past few weeks improving major parts of this app and I am finally at the point of releasing the new and shiny interface.

My ultimate goal for this app is to pull in all of the views into 3D and outshine other similar apps in beauty and create the best app for the job ( as usual ). This is a new beginning for the app and I will likely spend the next few months improving it.

HUD

I love iron Man like HUD interfaces. As such I am building a library with multiple interfaces for different visualization purposes. The interface above is a switch-board with a connected gyroscope and the Earth centered in it.

After the initial release I want to create a terminal emulation which I can scale at will in 3D space, followed by an audio interface which coverts input from the microphone into a nice 3D visual.

There are many more possibilities and I am having fun creating these playful widgets.

Shader

Another area which I want to invest some time in is to work with some custom shaders to bring out some glowing special effects and other goodies.

OpenGL ES 2.0 is a strange beast and not necessarily the easiest environment to learn but it offers some great flexibility in what you can accomplish. The ES, which stands for Embedded Systems, introduces a few changes as compared to the desktop OpenGL implementation.

OpenGL ES 2.0 Programming guide
OpenGL ES 2.0 Programming guide

I am currently going through the “OpenGL ES 2.0 programming guide”, which is a real good book to learn OpenGL. It will help me to realize the remaining widgets and animations to convert the complete app into the 3D realm.

Please give it a try and let me know what you think of it. Also I would love to hear if you have any ideas for a widget which I could implement.

Get it on Google Play

Login Form with php and QooxDoo

Sooner or later all web developer will have to create a Login form for one of their projects. You can find a lot of sample code and frameworks out there. In fact this is one of those times where you end up in information overload. There are millions of options but which one to pick ?

QooxDoo is a great framework to write complex web applications which acts and feels like a standard windows client. These so called SPAs ( Single Page Applications ) are a great way to create a valuable presence online and to offer your customers a very special treat.

But is QooxDoo flexible enough to build a stunning login form with it ?

I found that QooxDoo and php go together like peanut butter and jelly. And yes you can also use a RESTFul API to achieve the same however using PHP will offer you one advantage over E.g. a NodeJS based back-end. With most shared hosting services you will not be able to use NodeJS.

Form and Function:

So what are we looking for in a login form ? We want a nice and clean looking interface and we want to have the ability to

  1. Login
  2. Sign Up
  3. Recover password

So we are actually looking at three different forms and not just a single one.

three Login forms
three Login forms

That serves me well

For the PHP back-end I chose a single user class which I found on Github because it keeps things simple enough while at the same time offering some basic type of security. There are plenty of other options for the back-end available and you can shoe-horn any back-end into this solution really.

The PHP code was adjusted a bit and I added the code in here for your convenience. You can also download the PHP code from the following link Secure-User-Login-And-Authentication-Class

Here is the code which I added. Obviously in a real environment you want to separate this code out into its own file ( or files ).

// Simple input handling.
// Please note that no user input validation is done
$auth = new userAuth;
$type = $_GET['type'];
$name = $_GET['username'];
$pass = $_GET['password'];
$email= $_GET['email'];
$remember = $GET['remember'];
switch ( $type )  {
  case "login":  echo $auth->user_login  ( $name, $pass, $remember ) ? "true" : "false"; break;
  case "logout": echo $auth->user_logout ( ) ? "true" : "false"; break;
  case "forgot": echo $auth->forgot_password ( $email ) ? "true" : "false"; break;
  case "signup": echo $auth->create_user ( $name, $pass, $email, $remember ) ? "true" : "false"; break;
  case "check":  echo $auth->is_logged_in ( ) ? "true" : "false"; break;
  case "newDB":  echo $auth->create_user_table ( ) ? "true" : "false"; break;
  default: echo "Bad type"; break;
}

Now since we are going to use Ajax calls to the backend please keep in mind that any additional request to the backend ( E.g. like to display user data ) must be secured on the server side. You cannot rely on any security checks on the client side.

Login Form

In this post I want to mostly focus on the front-end part of the Login Form. Using QooxDoo to build the UI will require a few tweaks to the look and feel of the widgets in here.

I could have gone and created new styles following the qooxdoo way and I could have also created the LoginForm using the standard generate.py server side script. However I chose to use a functional complete, minified, qooxdoo library instead to bootstrap this application.

<!DOCTYPE html>
<html>
<head>
    <title>login</title>
    <meta charset="utf-8">
    <meta name="Author" CONTENT="SoftwareSamurai">
    <meta name="Description" CONTENT="login" />
    <meta name="Keywords" CONTENT="some keywords" />
    <meta name="Classification" CONTENT="Klassifizierung">
    <script type="text/javascript" src="script/qx-6.0.0a.min.js"></script>
    <script type="text/javascript" src="LoginWindow.js"></script>
    <script type="text/javascript" src="SignupWindow.js"></script>
    <script type="text/javascript" src="ForgotWindow.js"></script>
    <script type="text/javascript" src="ApplicationWindow.js"></script>
</head>
<body>
  <script type="text/javascript">
    qx.Class.define ( "LoginWindow.Application", {
      extend : qx.application.Standalone,
      members: {
        main : function ( )  {
          this.base ( arguments );
          if ( qx.core.Environment.get ( "qx.debug" ) )  {
            qx.log.appender.Native;
            qx.log.appender.Console;
          }
          var root = this.getRoot ( );
          var decorator = new qx.ui.decoration.Decorator ( );
          decorator.setBackgroundImage ( "background.jpg" );
          decorator.setBackgroundRepeat( "scale" );
          this.getRoot ( ).setDecorator( decorator );
          this.rpc ( "type=check", function ( success )  {
            var win = null;
            if ( success )
              win = new ao.apps.users.SoftwareSamurai.ApplicationWindow ( );
            else
              win = new ao.apps.users.SoftwareSamurai.LoginWindow ( );
            win.show ( );
          },this );
        },
        rpc : function ( str, cb, ctx )  {
          var rpc = new qx.io.request.Xhr ( "user.class.php?"+str );
          rpc.addListener ( "success", function ( e, x, y ) {
             var req = e.getTarget ( );
             var rsp = req.getResponse ( );
             cb.call ( ctx, ( rsp === "true" ) );
          }, this );
          rpc.send ( );
        }
      }
    } );
    qx.core.Environment.add ( "qx.application", "LoginWindow.Application" );
  </script>
</body>
</html>

Using this approach has the advantage of being able to rapidly develop your application and then use the standard QooxDoo tools to compile and minify once you are sure you have all you want.
For a standard QooxDoo application you have the Application.js file, which I have folded into this html file.

So lets go though this code above:
Line 10 : Here we load the complete qooxdoo library in a minified version.
Linew 11 – 14 : These are the classes we are going to create today.
Line 18 : Every QooxDoo application has to be derived off of qx.application.Standalone.
Line 21 : This is the main entry function which is called once the application is completely loadedinto the browser and the widgets are initialized.
Line 28 – 31 : In order to set the background image for the complete window we have to set the applications decorator.
Line 32 – 39: These lines of code do call the backend and if the user is already logged in, then we display the main application window, otherwise we display the login form.
Line 41 – 49 : This function will call the backend form ( “user.class.php ), and call the supplied callback function back in the supplied context ( object ).
Line 52 : In order to create a qooxdoo application you have to set this variable to allow the framework to instantiate the appropriate class as the main application.

Log me in Scotty

If you are not currently already logged in you will create a new instance of LoginWindow. So lets take a look at this class.

One word upfront though first. All three classes are very similar in structure, so I will only talk about the LoginWindow here. You can find the code of the other classes following the link below this blog.

The first thing we have to take care of is to create the actual widgets.

qx.Class.define ( "ao.apps.users.SoftwareSamurai.LoginWindow",
{
  extend : qx.ui.container.Composite,
  construct : function ( ) {
    this.base ( arguments );
    this.setLayout ( new qx.ui.layout.Canvas ( ) );
    this.set ( { width: 300, height: 200 } );
    this._buildLogin ( );
    var top  = parseInt ( ( qx.bom.Document.getHeight ( ) - 200 ) / 2, 10 );
    var left = parseInt ( ( qx.bom.Document.getWidth  ( ) - 300 ) / 2, 10 );
    var app  = qx.core.Init.getApplication ( );
    var root = app.getRoot( );
    root.add ( this, { top: top, left: left } );
  },
  destruct : function ( )  {
  },
  members  :  {
    _buildLogin : function ( )  {
      var semi = new qx.ui.core.Widget ( );
      semi.set ( { opacity: 0.6, backgroundColor: "#404040" } );
      this.add ( semi, { top: 1, left: 1, right: 1, bottom: 1 } );

      var font = new qx.bom.Font ( 24, [ "Arial" ] );
      font.setBold ( true );
      //font.setColor ( "#FFFFFF" );
      var lbl = new qx.ui.basic.Label ( "<center><b style='color: #FFFFFF'>" + this.tr ( "Log in" ) + "</b></center>" );
      lbl.setFont ( font );
      lbl.setRich ( true );
      lbl.setWidth( this.getWidth ( ) - 20  );
      this.add ( lbl, { top: 10, left: 10 } );

      var line = new qx.ui.core.Widget ( );
      line.set ( { height: 2, backgroundColor: '#FFFFFF' } );
      this.add ( line, { top: 50, left: 10, right: 10 } );

      var name = new qx.ui.form.TextField ( );
      name.setPlaceholder ( this.tr ( "Username" ) );
      this.add ( name, { top: 65, left: 10, right: 10 } );
      this._name = name;

      var pass = new qx.ui.form.PasswordField ( );
      pass.setPlaceholder ( this.tr ( "Password" ) );
      this.add ( pass, { top: 100, left: 10, right: 10 } );
      this._pass = pass;

      var chk = new qx.ui.form.CheckBox ( "<b style='color: #FFFFFF'>" + this.tr ( "Remember me ?" ) + "</b>" );
      lbl = chk.getChildControl ( "label" );
      lbl.setRich ( true );
      this.add ( chk, { top: 130, left: 10 } );
      this._remember = chk;

      var strForgot = "<center><i style='color: white'>" + this.tr ( "Forgot Password ?" ) + "</i></center>";
      var btnForgot = new qx.ui.basic.Atom ( strForgot );
      btnForgot.set ( { cursor: 'pointer' } );
      lbl = btnForgot.getChildControl ( "label" );
      lbl.setRich ( true );
      lbl.setAllowGrowY ( true );
      btnForgot.addListener( "mouseover", function ( )  {
        btnForgot.setLabel ( "<u style='color: white'>" + strForgot + "</u>" );
      },this );
      btnForgot.addListener( "mouseout", function ( )  {
        btnForgot.setLabel ( strForgot );
      },this );
      btnForgot.addListener( "click", function ( )  {
        this.forgot ( );
      },this );
      this.add ( btnForgot, { top: 130, right: 10 } );

      var width = parseInt ( ( this.getWidth ( ) - 30 ) / 2, 10 );
      var btnLogin = this._createBtn ( this.tr ( "Log in" ), "#AAAAFF70", width, function ( ) {
         this.login ( );
      }, this );
      this.add ( btnLogin, { bottom: 10, left: 10 } );
      var btnSignup = this._createBtn ( this.tr ( "Sign Up" ), "#AAFFAA70", width, function ( ) {
         this.signup ( );
      }, this );
      this.add ( btnSignup, { bottom: 10, right: 10 } );
    }
  }
} );

Line 1: We define the class name following the qooxdoo naming convention.
Line 3: Instead of creating a Window with a title etc we simply create a Container
Line 4 – 14: The constructor takes care of the initialization. I want to point out qx.bom.Document.getHeight ( ) which will give you the size of the browser window.
Line 11 – 13: Getting the applications root window and adding the LoginWindow to the root.
Line 19 – 21: The semi transparent background. If we would set the opacity of the actual LoginWindow we would also create the buttons and the text with the set opacity which is not wanted.
Line 46 – 50: For the checkbox we want to change the text color to white. In order to achieve this we have to get the actual Label object, and then use plain HTML to change the color.
Line 52 – 67: Here we build a widget which behaves like a HTML link. There are many ways this could be done, so consider this one of the solutions. Also please note that I am using a Atom instead of a Button object here.
Line 69 – 77: Finally we create the Log In and Sign up buttons. This is done using a function which takes in the required attributes and a callback function.

Lets inspect how we create the buttons and their custom looks.

    _createBtn : function ( txt, clr, width, cb, ctx )  {
      var btn = new qx.ui.form.Button ( "<b style='color: white'>" + txt + "</b>" );
      btn.set ( { width: width, cursor: 'pointer' } );
      lbl = btn.getChildControl ( "label" );
      lbl.setRich ( true );
      btn.addListenerOnce ( "appear", function ( )  {
        this.setBGColor ( btn, "#AAAAAA00", "#AAAAAA00" );
      },this );
      btn.addListener ( "mouseover", function ( )  {
        this.setBGColor ( btn, clr, clr );
      },this );
      btn.addListener ( "mouseout", function ( )  {
        this.setBGColor ( btn, "#AAAAAA00", "#AAAAAA00" );
      },this );
      btn.addListener ( "execute", function ( e )  {
         cb.call ( this );
      }, ctx );
      return btn;
    },
    setBGColor : function ( btn, clr1, clr2 ) {
       var elem = btn.getContentElement ( );
       var dom  = elem.getDomElement ( );
       var img  = "linear-gradient(" + clr1 + " 35%, " + clr2 + " 100%)";
       if ( dom.style.setProperty )
            dom.style.setProperty ( "background-image", img, null );
       else
            dom.style.setAttribute ( "backgroundImage", img );
    },

Line 2 – 5 : The first few lines of the _createBtn function we create a button and then change the text color to white.
Line 6 – 8 : The first time the button is rendered we will go ahead and change the background color. If we call the function before the button is visible, then the DOM element would be null.
Line 9 – 14 : These two callback function handle the case when the mouse gets into and out of the widget. In this case we are going to change the background color of the button.
Line 15 – 17 : We handle the execute event and are going to call the registered function back. Please take note of the way I implemented the callback, which will retain the context ( object ) inside the function. In object oriented programming you want to always use and handle the data inside an object so it is important to remain in the appropriate context.
Line 20 – 28 : In order to set the buttons background color we have to modify the DOM element directly. Please remember that this is not the way I would change the style of a button if I wanted to use it throughout the rest of the application. This is a quick way to get to change the gradient background color.

… and action.

The following functions handle the button action.

    forgot : function ( )  {
      var forgot = new ao.apps.users.SoftwareSamurai.ForgotWindow ( );
      forgot.show  ( );
      this.destroy ( );
    },
    login : function ( )  {
      var name = this._name.getValue ( );
      var pass = this._pass.getValue ( );
      var remember = this._remember.getValue ( );
      if ( name.length < 1 || pass.length < 1 )  {
        alert ( this.tr ( "Please provie username and password" ) );
        return;
      }

      var str = "type=login";
      str += "&username="+name;
      str += "&password="+pass;
      str += "&remember="+remember;
      var app = qx.core.Init.getApplication ( );
      app.rpc ( str, function ( success ) {
        var win = null;
        if ( success )  {
          win = new ao.apps.users.SoftwareSamurai.ApplicationWindow ( );
          win.show ( );
          this.destroy ( );
        }
        else  {
          alert ( this.tr ( "Could not log in." ) );
        }
      }, this );
    },
    signup : function ( )  {
      var signup = new ao.apps.users.SoftwareSamurai.SignupWindow ( );
      signup.show  ( );
      this.destroy ( );
    },

Line 1 – 5 : The forgot function will create and display the ForgotWindow form and destroy the Login form.
Line 6 – 13 : we retrieve the values the user entered and do some basic sanity checking.
Line 15 – 18 : Here we build the query string to send to the back-end.
Line 19 – 20 : We get the application object to invoke the rpc function.
Line 21 – 29 : Once the server returns we check if we were successful in logging in. If yes, then we hide the Login form and start the main ApplicationWindow.
Line 32 – 36 : The signup function will create and display the SignupWindow form and destroy the Login form.

Let’s wrap it up

Congratulations, you have done it. You are now able to build your own complex web applications on a shared hosting service, such as Dreamhost with as little as $8,- per month.

DreamHost.

Resources

Live sample

Create a stunning login form using QooxDoo and PHP

Note: try user “test” and password “abc123” or create a new user.

I also created a video on creating a stunning login form using QooxDoo, which you can find here …

Please follow this link here … to play with the code.


Cloud abstraction layer

The plain pain

Imagine that you have written a really good web app, and you have distributed it to many customers these customers in turn acquired a lot of customers.

Now fast forward a few months and all of a sudden you are getting calls to help fix issues with your platform. For some unforsaken reason your cloud storage integration stopped working.

Because you have moved on to the next thing you have only limited time to spend on fixing the issue. What you eventually discover is that a service provide decided to change the API from version X to version Y.

Now you have to sit down and spend a couple of days fixing what is been broken.

Sleep Mode Zero
Sleep Mode Zero

That is something you have to deal with all the time in an actively changing web environment.

APIs change and certain providers may stop offering services or worse go out of business.

How to avoid the pain

Most web based APIs use a RESTFul interface to their services. As such the steps involved in utilizing a online service is usually accomplished through OAuth2 authorization to gain secure access to users data, followed by the utilization of the actual API.

As a developer you are free to develop to a specific API or to abstract the API in a way where you can easily replace one service/API with another.

However every single line of code you write you will have to maintain yourself and make sure that changes over time will not break functionality.

Cloud abstraction layer, the better way

Every now and then you can do one better though. Take for example web storage. There are many providers of web storage, such as box, Dropbox, S3, Google storage etc. If you want to offer a wide selection of possible back-end storage platforms you would be well advised to look into a framework such as Flysystem for PHP.

The PHP League Logo
The PHP League Logo

Flysystem abstracts it the different back-end APIs and provided a unified interface. You can find a multitude of third party connectors, such as Azure, S3, Dropbox, box etc. You can also find some strange adaptations such as GitHub or flicker for it in case you have use for it.

The most important thing to remember though is that if one of the available back-end APIs changes you will be able to replace it with almost no additional work required on your side.

Also if a provider goes out of business, you can quickly switch to another provider. And finally, if a service provider changes the API version and ignores backwards compatibility you can simply replace the old library with a new library with the same API calls.

There are however some shortcomings to adding an cloud abstraction layer

  • It is usually not as comprehensive in its feature set
  • The additional code will slow down the requests a few milliseconds
  • It will increase the projects complexity
  • Not every supported back-end-API may provide the required data. E.g. certain storage back-ends don’t support a file system natively

AstraNOS integration

Since I had to move from Dropbox v1 to Dropbox v2, I switched over to utilize the cloud abstraction layer provided by Flysystem for AstraNOS. Integrating the OAuth2 client from the PHP League us also unifying the signups mechanism for cloud storage back-end ( and more if I ever need to ).

Working Dropbox integration
Working Dropbox integration

With this addition I will now be able to increase the available back-end services with little additional work, though I would guess that it still requires a good day per back-end.

However this is a price worth paying if we can leverage multiple cloud based back-ends at the same time and in the same environment. Working seamlessly between them as it has been intended.

Rpi WebRTC Streamer to Android WebRTC streaming

In this article I will setup Android WebRTC streaming to RPI-WebRTC-Streamer running on my Raspberry PI Zero W, to talk to each other.

Here is the architecture of RPI-WebRTC-Streamer.

Now why do we use WebRTC instead of directly streaming audio/video through a socket from the Raspberry PI to the Android device ?

It is helpful to understand how WebRTC works under the hood and how a connection is established.

The magic in WebRTC is the probing of the connection to allow two clients to talk P2P ( Peer-to-Peer ), even if they are behind a firewall or using NAT. There are plenty of online resources devoted to this complex topic ( E.g. https://www.html5rocks.com/en/tutorials/webrtc/infrastructure/ )

The basic steps in establishing a WebRTC connection are:

  • Step 1: Signaling: both peers connect to a signaling server (using websockets over 80/443, comet, SIP,etc..) and exchange information (about their media capabilities, public IP:port pairs when they become available, etc.)
  • Step 2: Discovery: Devices connected to LAN or mobile networks are not aware of their public IP (and port) where they can be reached at so they use STUN servers located on the public Internet to discover their ip:port pair (ICE candidates). In the process they punch a hole through the NAT/router which is used in step3:
  • Step 3: P2P connection: once the ICE candidates are exchanged through the initial signaling channel each peer is aware of each other’s ip:port (and holes have been punched in NATs/routers) so a peer to peer UDP connection can be established.
  • Step 4: If a P2P connection can’t be established ( maybe through firewall rules or the usage of Symmetric NAT ) then TURN servers can be used, which will relay the data between the peers. Please note that this will require the TURN server to receive and send all video and audio and is the last resort in WebRTC.

Under normal circumstances you would establish the connection between two web-browser. However in this case I need to establish Android WebRTC streaming to the Raspberry PI Zero W. Fortunately we have the required tools and libraries available on both platforms and can take full advantage of this technology stack. This allows us to basically build a video conference similar to skype between the Raspberry PI and Android. As an aside, iOS can also handle WebRTC, which may be a project for later.

Android WebRTC Streaming:

  1. Lets first make sure you have the RPI WebRTC Streamer setup, as explained in my previous post.
  2. Then you have to install ADB onto your computer to be able to transfer the App to your Android phone.
  3. Connect your Android phone to your computer and make sure you have the developer option enabled
  4. Next get the source code for RPI WebRTC Streamer from github and install it onto your phone, like so.
    bash> git clone https://github.com/kclyu/rpi-webrtc-streamer.git
    bash> cd rpi-webrtc-streamer/misc
    bash> adb install AppRTCMobile.apk
    
  5. Next open the app on your Android phone
  6. Change settings of the Video encoder to H264 High
  7. Change the resolution to VGA(640 x 480)
  8. And then simply enter the IP address of your raspberry PI as the room number
Android WebRTC streaming in action
Android WebRTC streaming in action

Finally, here is the article from mpromonet if you ever feel like compiling rpi-webrtc-streamer https://www.raspberrypi.org/forums/viewtopic.php?t=186271.
You can even use docker containers to make things easier: https://hub.docker.com/r/mpromonet/webrtc-streamer/

As an aside, I found multiple Android app templates to use WebRTC

uv4l WebRTC vs rpi-webrtc-streamer

I have spent the past two days working with the uv4l driver to get WebRTC working. I eventually got everything to work with three major issues

  1. I could not get the transmitted quality to anything close to what I needed
  2. I could not get rid of the the watermark which was put over the video
  3. The complete CPU utilization for 640×480 was above 90% and caused issues.

Another slightly annoying issue was that I had to re-install Jessie after I found out that uv4l is currently not available for Raspbian stretch lite. I could only find the full version for Jessie, which requires at least a 8GB microSD card. And off I went to replace my 4GB microSD card.

On the positive side I installed rpi-webrtc-streamer from github and was able to look at the results in realtime right away.

Unlike uv4l, which is based off OpenWebRTC from Ericson, rpi-webrtc-streamer is based off the Native Code from www.WebRTC.org which seems to be a bit more responsive than uv4l.

Here are the steps I had to do to get things to work.

bash>  # First create a home for the code
bash>  mkdir utils && cd utils
bash>  git clone https://github.com/kclyu/rpi-webrtc-streamer.git

You can find the Android App under rpi-webrtc-streamer/misc/AppRTCMobile.apk
But first lets avoid the work and go straight to installing the software.

Go to https://github.com/kclyu/rpi-webrtc-streamer-deb and download the appropriate deb-package.

bash>  dpkg -i rws_xxx_armhf.deb
bash>  sudo systemctl start rws

Then simply point your browser at http://<IP Address>:8889/native-peerconnection/

Raspberry PI camera
Raspberry PI camera

I will be going through the setup and usage of the Android App which is part of rpi-webrtc-sstreamer in one of my next blog entries.

Raspberry PI Zero W as Chromecast

So one of the thing which I tried out was to see if the PI Zero can handle video streaming to our TV.

In order to setup the Raspberry PI Zero W as Chromecast, I followed the series of 7 videos below and plugged it into our large TV. However the result was unusable. The video did only play for a second or two before the whole thing stopped and tried to continue playing.

This is not too surprising considering that the PI Zero has only a single core 1GHz CPU while the instructions assume the PI 2 or 3 which has a quad core CPU to handle the stress.

Step 1 : Raspbian Jessie Lite Install Guide
https://youtu.be/R9UDL1JxP7I

Step 2: Raspbian Jessie Lite wifi config
https://youtu.be/N2ZjrBC2bv0

Step 3 : Raspbian update upgrade omxplayer
https://youtu.be/JV7nbAsQKO0

Step 4 : Install youtube download video and python
https://youtu.be/ukujbwn7XiQ

Step 5 : Raspbian Jessie Lite Golang
https://youtu.be/gVhMYRaKGXU

Step 6 : Raspberry pi as Youtube TV
https://youtu.be/dWjfkwz6MWk

Please feel free to sign up or follow my home made hardware projects here …

Loading