Mat DeLong

March 24, 2013

SOURCE – DataSnap Photo Album Server & Admin Client

Filed under: DataSnap, Delphi, RAD Studio, XE2 — Tags: , , , , , , , — mathewdelong @ 7:06 AM

Intro

With my previous blog post (https://mathewdelong.wordpress.com/2013/01/24/datasnap-photo-album-server-admin-client/) I shared with you a sample application written in Delphi XE2 using DataSnap to make a client/server application for sharing albums of photos. With this post I want to show you the code and explain parts of it to you.

First off, this is where you can download a zip file of the source: http://matdelong.com/professional/downloads/delphi/AlbumWebsite-v1.0_src.zip

And again, this is where you can download the sample app itself: http://matdelong.com/professional/downloads/delphi/AlbumWebsite-v1.0.zip

Lastly, here is the SourceForge project, in case you want to browse the code that way: http://sourceforge.net/projects/albumwebsite/.

I’m going to assume you’ve read my previous blog posts, and so know about the basics of web clients, authentication management, Invocation Metadata, and remote method invocation with DataSnap. If you need a refresher, feel free to read my older blog posts before continuing with this one.

Server Setup

When setting up the server, I chose to create a new DataSnap REST Application, including server methods class, proxy generation and authentication management. The server methods are actually used to load the page content. This is because we need to do some processing of the content before returning it, and this is one way of doing it. The proxy generation is required so that a Delphi Client version of the server methods is available for the admin client, so that it can remotely invoke the server methods. The authentication manager (actually, I’ve used two of them) is to restrict admin functions (like adding new photos and deleting photos/albums) to an authenticated admin user.

ServerForm

Server container form

Above is a photo of the completed server container form. The components I haven’t yet mentioned are the TCP Transport, used by the admin client for uploading photos using a TCP connection, and the file dispatcher. The file dispatcher allows for sending of files through HTTP to the web client, such as when it requests a specific image resource from the server, or a cs file, JavaScript file, etc.

Authentication & Authorization

There are two different types of users who will be connecting to the server: administrators and regular users who wish to view your photos. For this reason, I’ve created user-role constants which will be assigned in the authentication managers for connecting users:

const
gcAdminRole = 'admin';
gcPublicRole = 'public';

And these are the two authentication methods for the two authentication manager components:

procedure TAWServerContainer.WebClientAuthenticatorUserAuthenticate(Sender: TObject; const Protocol, Context,
                                                                    User, Password: string; var valid: Boolean; UserRoles: TStrings);
begin
  UserRoles.Add(gcPublicRole);
  valid := True;
end;

procedure TAWServerContainer.AdminAuthenticatinatorUserAuthenticate(Sender: TObject; const Protocol, Context, User,
  Password: string; var valid: Boolean; UserRoles: TStrings);
begin
  valid := False;

  if AnsiSameText(fAdminPassword, Password) then
  begin
    UserRoles.Add(gcPublicRole);
    UserRoles.Add(gcAdminRole);
    valid := True;
  end;
end;

You can see here that for regular users we just authenticate them, but for the admin user we first check the password. Also, the admin user is granted the roles of both an admin and a regular user. This will allow him to both modify the album and view it like normal. It is up to the TRoleAuth annotations on the server methods to enforce this.

‘Regular User’ Server Methods

These server methods are used to return web page content to regular users. The type of pages returned are a page of all the available albums, and a page for a specific album, showing all of the album photos. Here are the declarations of each:


    /// <summary>
    ///   Results in the HTTP Response containing HTML of a page showing a list of all albums.
    /// </summary>
    [TRoleAuth('public')]
    procedure Albums;
    /// <summary>
    ///   Results in the HTTP Response containing HTML of a page showing the specified
    ///   album's images.
    /// </summary>
    [TRoleAuth('public')]
    procedure Album(const AlbumName: String);

These procedures use the Invocation Metadata to set the HTML of the response to specific HTML, the content of which is built based on the filesystem of the server. The server has a specific directory set as the “albums” directory, and each directory under that is an album. So the names of the folders are the album names returned on the web page. Here is the implementation:


procedure TAWMethods.Albums;
var
  AlbumNames : TStringList;
  AlbumName: String;
  SB: TStringBuilder;
begin
  AlbumNames := GetSubdirectories(lcAlbumsDir);

  SB := TStringBuilder.Create;
  try
    SB.AppendLine('  <div class="titlediv">' + lcTitleAlbumsList + '</div>');
    SB.AppendLine('  <div class="albumlist">');

    for AlbumName In AlbumNames do
    begin
      SB.AppendLine('    <a href="' + lcAlbumAction + '/' + AlbumName + '">' + AlbumName + '</a><br />');
    end;

    SB.AppendLine('  </div>');

    GetInvocationMetadata().ResponseContent := GetPageText(lcAlbumsAction, ptAlbumList, SB);
  finally
    FreeAndNil(SB);
  end;
end;

This code reads the list of subdirectories from the server’s filesystem, and then builds the body of the HTML to return, where there is a link for each subdirectory under the album folder. The links that get built are calls to the “Album” server method, passing in the subdirectory name as the Album name parameter. The link is relative from the Albums URL, so the full path isn’t needed (going from: http://host/DS/rest/Albums to http://host/DS/rest/Album/SomeName). The GetPageText call just adds the HTML body just created to a pre-existing default HTML page with the required links in the header, such as required JavaScript files. Look at the project files for more information on this.

This is what the implementation looks like for the method that returns the HTML for a specific album:


procedure TAWMethods.Album(const AlbumName: String);
var
  ADirPath: String;
  AThumbsPath: String;
  AImageName: String;
  AImageList: TStringList;
  SB: TStringBuilder;
  ACurrentImgPath, ACurrentImgThumbPath: String;
  AAlbumInfo: TJSONObject;
begin
  ADirPath := lcAlbumsDir + '/' + AlbumName;
  AThumbsPath := ADirPath + '/' + lcThumbsDirName;

  AImageList := GetImageList(ADirPath);
  SB := TStringBuilder.Create;

  AAlbumInfo := GetAlbumInfo(AlbumName);

  try
    SB.AppendLine('  <div class="titlediv">' + AlbumName + '</div>');
    SB.AppendLine('  <div class="backlink hcenter"><img src="' + GetWebPath('images/up.png') + '"/><a href="../' + lcAlbumsAction + '">Back to Album List</a></div><br />');
    SB.AppendLine('  <div class="descriptiondiv">' + GetAlbumDescription(AAlbumInfo) + '</div>');
    SB.AppendLine('  <div class="highslide-gallery hcenter"><br />');

    for AImageName In AImageList do
    begin
      ACurrentImgPath := ADirPath + '/' + AImageName;
      ACurrentImgThumbPath := AThumbsPath + '/' + AImageName;

      SB.AppendLine('    <a href="' + GetWebPath(ACurrentImgPath) + '" class="highslide" onclick="return hs.expand(this, galleryOptions )">');
      SB.AppendLine('      <img src="' + GetWebPath(ACurrentImgThumbPath) + '" alt="Loading Thumbnail..." title="Click to enlarge"/>');
      SB.AppendLine('    </a>');
      SB.AppendLine('    <div class="highslide-caption">' + GetImageDescription(AAlbumInfo, AImageName) +'</div>');
    end;

    SB.AppendLine('  </div>');

    GetInvocationMetadata().ResponseContent := GetPageText(lcAlbumAction, ptAlbum, SB);
  finally
    FreeAndNil(SB);
    FreeAndNil(AImageList);
    FreeAndNil(AAlbumInfo);
  end;
end;

The above code works in a similar way to the previous code for loading the list of albums: It scans the file system (this time under the albums subdirectory with the album name) and finds all of the images and thumbnails there. It then builds the body HTML for the album page based on the images it finds. One additional thing it does is looks for a “settings.json” file in the album directory, which holds the description for the album and each image.

The highslide JS library is used in the HTML for loading a full-sized image from a thumbnail, which allows for only loading the full sized images as a user clicks them, which speeds up page loading.

As with before, the last thing this procedure does is use the Invocation Metadata to set the content of the HTTP response. If these methods weren’t called through an HTTP request, then nothing will happen, because the InvocationMetadata isn’t used for TCP connections. These methods are intended to be called from a web browser, with a GET request.

Admin Server Methods

These are the public methods which can be remotely invoked by an authenticated Admin user:


    /// <summary>Returns a JSON Array containing the names of all Albums.</summary>
    [TRoleAuth('admin')]
    function GetAlbumList: TJSONArray;
    /// <summary>Returns a JSONObject for the album, containing image names and descriptions.</summary>
    /// <remarks>Looks like: {"description":"","images":["IMG001.jpg":""]}</remarks>
    [TRoleAuth('admin')]
    function GetAlbumInfo(const AlbumName: String): TJSONObject;
    /// <summary>Returns the image thumbnail, or nil and False if it can't be loaded.</summary>
    [TRoleAuth('admin')]
    function GetThumbnail(const AlbumName, ImageName: String; out ImgStream: TStream): Boolean;
    /// <summary></summary>Renames the specified album.
    [TRoleAuth('admin')]
    function RenameAlbum(const AlbumName, NewName: String): Boolean;
    /// <summary>Sets the info for the given album.</summary>
    [TRoleAuth('admin')]
    function SetAlbumInfo(const AlbumName: String; const AlbumInfoObj: TJSONObject): Boolean;
    /// <summary>Adds image to the specified album.</summary>
    [TRoleAuth('admin')]
    function AddImage(const AlbumName, ImageName: String; FileStream: TStream): Boolean;
    /// <summary>Removes the specified image from the album.</summary>
    [TRoleAuth('admin')]
    function RemoveImage(const AlbumName, ImageName: String): Boolean;
    /// <summary>Deletes the specified album.</summary>
    [TRoleAuth('admin')]
    function DeleteAlbum(const AlbumName: String): Boolean;
    /// <summary>Creates an empty directory with the given name in the albums directory.</summary>
    /// <remarks>This results in an empty Album being created.</remarks>
    [TRoleAuth('admin')]
    function CreateAlbum(const AlbumName: String): Boolean;

These allow for getting, adding, removing and editing of albums and images. The TRoleAuth attribute/annotation is limiting each of the functions to be called only by the admin user (established by the authentication manager.) These methods are used by the Delphi client application for managing albums. For the most part, the implementation of these are quite simple. They use the file system on the server in much the same way the previously mentioned procedures did. For more information on the inner workings, feel free to check out the source code.

Delphi Client

The Delphi client connects to the server through a TCP connection, authenticates with an administrative password, and if successfully logged in, is able to view and modify the list of albums and their contents. This client boils down to simply using a generated proxy for remote method invocation, so I won’t go into more detail on it.

Web Client

The use case for this application is that the person hosting the ‘album website’ wants it to be available for those who are given the URL. The website itself simply consists of the landing page (the list of albums with links to them) and the individual pages for each album. The web client is, therefore, quite simple. There is no need for a generated JS proxy, because the URLs themselves call the server methods. All there is for client code (other than the highslide library and its required files) is a “main.js” and “main.css” file. These files are included by the “GetPageText” function in the server methods class, and the file dispatcher on the server handles delivering them to web browsers when the pages load.

To see the content being built for the web client, view that GetPageText function, and the Album and Albums procedures that call it.

Conclusion

I could have gone a lot more in detail, but I think that with the provided source code, this should be enough information to get you started. Let me know in the comments if you have any questions.

1 Comment

  1. VERY many thanks for posting this Mat. Any articles, tips or insights on the presentation layer for browsers would be deeply appreciated 🙂
    Rob

    Comment by Rob — March 26, 2013 @ 6:40 AM


RSS feed for comments on this post.

Sorry, the comment form is closed at this time.

Create a free website or blog at WordPress.com.

<span>%d</span> bloggers like this: