Vely logo Empower C
install  tutorials  examples
documentation  license  about

12.1.0 released on Sep 19, 2022

Tutorial-file manager



DESCRIPTION:



What you'll learn


You'll learn how to:

File manager example


This example application will let you create a file collection by uploading files (such as images or documents) to the server. You can browse files, view, delete or download them. So, all in all, pretty capable file manager, and all in less than 150 lines of code!

Setup prerequisites


Install Vely - you can use standard packaging tools such as apt, dnf, pacman or zypper.  After installing Vely, turn on syntax highlighting in vim if you're using it:
vv -m

You will also need to install Nginx web server and PostgreSQL database. You can run your application with another web server and/or database, and it would be similar.

It is a good idea to create a separate source code directory for each application (and you can name it whatever you like):
mkdir file_manager
cd file_manager


Setup database table(s)


Before any coding for the file manager application, you need some place to store information about files. For that purpose, create a table in PostgreSQL database. First, to hold this table, create database "dbfiles" owned by user "vely" with password "your_password". You can change any of these names, but remember to change them everywhere here.

Login to PostgreSQL database as root (using psql utility):
sudo -u postgres psql

and execute this to create the aforementioned database objects if not existing already:
create user vely login password 'your_password';
create database dbfiles with owner=vely;
exit

Then login to PostgreSQL again as the user created (with password 'your_password') while setting the current database to "dbfiles":
psql -h localhost -d dbfiles -U vely -p 5432

and create the database object(s) needed for the application, in this case just a "files" table. This table will have information about files, such as file names, paths, sizes etc.:
create table if not exists files (fileName varchar(100), localPath varchar(300), extension varchar(10), description varchar(200), fileSize int, fileID bigserial primary key);
exit

In order to let Vely know where your database is and how to log into it, create database configuration file named "db". Any reason why "db"? You haven't gotten to this point yet, but if you look at "upload.v" source code file, for example in code like this, where file information is inserted into the table:
...
run-query @db="insert into files (fileName, localPath, ..."
...

the database name is specified with "@" sign, in this case "@db", and so the database configuration file name should be "db". In your code, you can choose any name you like, and you would use that name instead. For this application, let's create file "db":
vi db

and save the following in it, reflecting the database you created earlier:

user=vely password=your_password dbname=dbfiles port=5432 host=localhost

The above is a standard PostgreSQL connection string that describes the login to the database you created. Since Vely uses native PostgreSQL connectivity, you can specify any connection string that your database lets you.

Now that you have a database table to store information about files (such as name, size etc.), where will the files themselves be stored?

Well, you could store them in the database, but that's not the most optimal way. You could store them anywhere on file system, but Vely has a very simple (and generally transparent and automatic) way of storing files, which is Vely's file_storage. When a file is uploaded, it's automatically stored there; and you can also create files there as well.

Application landing page


This is the page where application user always goes first. Here, your application will display a web link that shows all files kept in the database so far, as well as HTML form to upload a new file where you can enter its description and choose a file from your computer or device. Here's a snapshot of what the page will look like - this is a basic layout and you can change the code to style it however you wish:


Vely


To that end, start by creating file "start.v":
vi start.v

and copy this code:
// SPDX-License-Identifier: Apache-2.0
// Copyright 2018 DaSoftver LLC.


#include "vely.h" // must always be here


// Upload and list/download files
void start ()
{

   out-header default

   @<h2>File Manager</h2>

   @To manage the uploaded files, <a href="?req=list">click here.</a><br/>
   @<br/>

   // Form to upload a file

   @<form action="" method="POST" enctype="multipart/form-data">
   @    <input type="hidden" name="req" value="upload">

   @    <label for="file_description">File description:</label><br>
   @    <textarea name="filedesc" rows="3" columns="50"></textarea><br/>
   @    <br/>
   @    <label for="filename">File:</label>
   @    <input type="file" name="file" value=""><br><br>

   @    <input type="submit" value="Submit">
   @</form>

}

One thing you'll notice often in Vely code is the use of "@" to output text (in this case HTML page text) - "@" is an output_statement, and it makes it really easy and readable. In general, Vely does not use traditional API for its functionality, rather it uses statement_APIs.

Uploading files


The next source code file is "upload.v", which will upload a file when a user selects a file and clicks "Submit" button, (see "start.v"):
vi upload.v

and copy this code:
// SPDX-License-Identifier: Apache-2.0
// Copyright 2018 DaSoftver LLC.
#include "vely.h" // must always be here

// Upload the file
void upload ()
{
   out-header default

   // file description from the upload form
   input-param filedesc
   // file name
   input-param file_filename
   // the path to uploaded file
   input-param file_location
   // size in bytes
   input-param file_size
   // the file extension
   input-param file_ext
   VV_UNUSED (file_ext);

   @<h2>Uploading file</h2>

   // insert the information about the file into the database
   run-query @db="insert into files (fileName, localPath, extension, description, fileSize) values ('%s', '%s', '%s', '%s', '%s')": file_filename, file_location, file_ext, filedesc, file_size
   end-query

   @File <<p-web file_filename>> of size <<p-web file_size>> is stored on server at <<p-web file_location>>. File description is <<p-web filedesc>>.<hr/>
}

When you upload a file into Vely application, it's just an input-parameter, just like it would be with any GET or POST request parameter. In addition, Vely will automatically store the uploaded file into file directory (see file_storage) which is optimized for speed of access, and you can either keep it there, move it elsewhere or do whatever you want. All of this make is easy to work with files.

The above code will get the information about the file uploaded (such as file description entered in "start.v", as well as file name, location, size, extension - which is provided by Vely as input parameters. Then, use run-query statement to insert such information into the database table you created. From now on, the information about a file is stored in the database (and that includes the location where it was uploaded), and the actual file is in the file system. This allows fast access to files and easier management.

And finally, the message will be sent back to web client (such as a browser) that you've saved the file.

Listing files


For files that have been saved, now you will write code that shows them in a list. Next to each file name will be its size, and links to show the file (display it or download it if can't be displayed), and a link to delete the file. So, create "list.v" source code file:
vi list.v

and copy this code:
// SPDX-License-Identifier: Apache-2.0
// Copyright 2018 DaSoftver LLC.
#include "vely.h" // must always be here

// List files
void list ()
{
   // List current files in the database
   out-header default
   @<h2>List of files</h2>
   @To add a file, <a href="?req=start">click here</a><br/><br/>
   @<table border="1">
   @<tr>
   @    <td>File</td><td>Description</td><td>Size</td><td>Show</td><td>Delete</td>
   @</tr>

   // get the list of files from the database
   run-query @db="select fileName, description, fileSize, fileID from files order by fileSize desc" output fileName, description, fileSize, fileID
       query-result fileName to define file_name
       query-result fileID to define file_ID
       query-result description to define description
       query-result fileSize to define file_size
       // construct table output with links to Show and Delete files
       @<tr>
       @    <td><<p-web file_name>></td><td><<p-web description>><td><<p-web file_size>></td>
       @    <td><a href="?req=download&amp;file_id=<<p-url file_ID>>">Show</a></td>
       @    <td><a href="?req=delete&amp;action=confirm&amp;file_id=<<p-url file_ID>>">Delete</a></td>
       @</tr>
   end-query
   @</table>
}

To produce the list of files, the table "files" is queried, and the list of files displayed as an HTML table. Here is the snapshot of this list:


Vely



Downloading files


When a link to download a file is clicked in the list of files, the following code is called - save it to "download.v" file:
vi download.v

and copy this code:
// Copyright 2018 DaSoftver LLC.
#include "vely.h" // must always be here

// Download a file
void download ()
{
   // Show or download a file (its ID is in the database)
   input-param file_id
   char *local_path=NULL;
   char *ext = NULL;

   // get the local path and extension of the file
   run-query @db="select localPath,extension from files where fileID='%s'" output localPath, extension : file_id row-count define num_files
       query-result  localPath to local_path
       query-result  extension to ext
   end-query

   // check we can find the file
   if (num_files != 1) {
       out-header default
       @Cannot find this file!<hr/>
       return;
   }

   // display JPG or PDF files in the browser, or download any other kind
   if (!strcmp (ext, ".jpg")) {
       send-file local_path headers content-type "image/jpg"
   } else if (!strcmp (ext, ".pdf")) {
       send-file local_path headers content-type "application/pdf"
   } else {
       send-file local_path headers content-type "application/octet-stream" download
   }
}

The ID of a file requested is queried in table "files", the file path on server is found, and the file is send to the client using send-file statement.

Deleting files


Finally, deleting a file (by clicking on delete link in the list of files) is handled in "delete.v" source file:
vi delete.v

and copy this code:
// SPDX-License-Identifier: Apache-2.0
// Copyright 2018 DaSoftver LLC.
#include "vely.h" // must always be here

// Delete the file
void delete ()
{
   out-header default
   @<h2>Delete a file</h2>
   input-param action
   input-param file_id
   char *file_name = NULL;
   char *desc = NULL;
   char *local_path = NULL;

   // Get file information from the database
   run-query @db="select fileName, localPath, description  from files where fileID='%s'" output fileName, localPath, description : file_id
       query-result fileName to file_name
       query-result description to desc
       query-result localPath to local_path
   end-query

   if (!strcmp (action, "confirm")) { // get file information to confirm what will be deleted
       @Are you sure you want to delete file <<p-web file_name>> (<<p-web desc>>)? Click <a href="?req=delete&amp;action=delete&amp;file_id=<<p-url file_id>>">Delete</a> or click the browser's Back button to go back.<br/>

   } else if (!strcmp (action, "delete")) { // actual delete file, once confirmed
       begin-transaction @db
       run-query @db= "delete from files where fileID='%s'" : file_id error define err no-loop
       if (atol(err) != 0) {
           @Could not delete the file (error <<p-web err>>)
           rollback-transaction @db
       } else {
           delete-file local_path status define st
           if (st == VV_OKAY) {
               commit-transaction @db
               @File deleted. Go back to <a href="?req=start">start page</a>
           } else {
               rollback-transaction @db
               @File could not be deleted, error <<pf-web "%lld", st>>
           }
       }
   } else {
       @Unrecognized action <<p-web action>>
   }
}

When a file is about to be deleted, a confirmation is asked of user - input parameter "action" is "confirm" in this case, as in:


Vely


Once confirmed, deletion of a file is carried out; input parameter "action" is "delete" then (you can name parameter "action" anything you want). You will see:


Vely


Note that deletion is done as a transaction, only if both the record in table "files" is deleted and the actual file deleted. If the file cannot be deleted, transaction is rollback-ed.

Build application


First you need to create a Vely application. The application will be named "file_manager", but you can name it anything. It's simple to do with vf:
sudo vf -i -u $(whoami) file_manager

This will create application home under Vely directory (which is "/var/lib/vv") and do application setup for you.

To build your application use vv command line utility:
vv -q --db=postgres:db file_manager

-q option means you are building the application, and you are specifying that PostgreSQL database is used under the name "db" (which is --db option), and that your application name is "file_manager". You can use any number of databases, and of different kinds. This example is simple, with just one database.

Setup web access


To access your application via web browser, you need to setup a web server. It can be any web server that supports FastCGI proxying (most of them do). Here, you will setup Nginx, but you can use any other web server.

Edit the Nginx configuration file:
#the location of Nginx config file may vary, for instance:
sudo vi /etc/nginx/sites-enabled/default
#or:
sudo vi /etc/nginx/nginx.conf

Add the following location that will connect Nginx to your application. The "fastcgi_pass" directive will tell Nginx to use your application (running locally on port 2310) whenever there is a URL query string starting with "/file_manager". In addition, you'll increase the file size that can be uploaded to 25MB, since the default is pretty small these days and without it you may see a "request entity too large" error. This may be placed within "server {}" block after "server_name" directive:

location /file_manager {
   include /etc/nginx/fastcgi_params; fastcgi_pass 127.0.0.1:2310;
   client_max_body_size 25m;
}

Save the file and restart Nginx:
sudo systemctl restart nginx

In this case, you will be connecting to your application via TCP port 2310 (you can pick the number as long as it's greater than 1000), but you can use a socket as well. TCP ports are typically used when your application runs on another computer or in a docker container.

If you have SELinux enabled (typically for Fedora systems such as RedHat or similar), you must add the port 2310 to it:
sudo semanage port -a -t vvport_t -p tcp 2310


Run application


To start the application server for your application:
vf -p 2310 file_manager

You specified port 2310 here, which is the same used to setup Nginx reverse proxy. This will by default run anywhere between 0 and 20 server processes for your application, depending on the load. It means when the user load is low, your application will use virtually no memory at all. Check out vf for more options.

That was it! Now you can try it. Assuming you're on a local machine (change the base URL if not), and assuming Nginx listens on port 80:

http://127.0.0.1/file_manager?req=start

You will now be able to add files, browse and delete them, and download them. Congratulations, you've created a file manager application with Vely! Your application is now reverse proxied behind a web server.

Note: if you're not using localhost (i.e. 127.0.0.1), port 80 may be blocked and you may need to open it via firewall. Use "firewall-cmd" (for Fedora based systems) or "ufw" (for Debian systems) to open access to port 80.

SEE ALSO:


Tutorials ( tutorial-file_manager   tutorial-hello_world   tutorial-multitenant_cloud   tutorials   tutorial-stock_app   tutorial-utility  )  SEE ALL (documentation)



Copyright (c) 2022 DaSoftver LLC. Vely is a trademark of Dasoftver LLC. The software and information herein are provided "AS IS" and without any warranties or guarantees of any kind. Icons copyright PaweĊ‚ Kuna licensed under MIT. This web page is licensed under CC-BY-SA-4.0.