SOURCE – DataSnap Photo Album Server & Admin Client

  

Intro
With my previous blog post (http://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.
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.

Comments are closed.