8.14 Tutorials: Image Manager

The source of this example is found under the Examples/AS2/22_pro_fileManager folder.

» Introduction

In this tutorial we'll show how simple is to create a multi user image file manager using the new embedded HTTP server coming with SmartFoxServer 1.5.0 and higher.
The sample application that we're going to build (see picture below) will allow users to upload images from their local folders and share them in real time with the other clients connected. In the example we will also add a delete button to remove uploaded images.

imgManager

» Application setup

The application runs in its own Zone as defined in the main configuration file:

<Zone name="imgMan" emptyNames="true">
	<AutoReloadExtensions>true</AutoReloadExtensions>
	<Rooms>
		<Room name="MainLobby" maxUsers="50" isPrivate="false" isTemp="false" autoJoin="true" />
		</Rooms>

	<Extensions>
		<extension name="imgman" className="imageManager.as" type="script"></extension>
	</Extensions>
</Zone>  

For the sake of simplicity we just use one main room, set as autojoin, and we define one extension called "imgman" that points to the imageManager.as file inside the default extension folder ( sfsExtensions/ ). The <AutoReloadExtensions> option allows us to quickly restart the extension each time we modify it.

» Uploading files

Before we start analyzing the example code it is useful to take a look at the following diagram showing how the file upload is handled and integrated in SmartFoxServer.

upDiag

1 - The Flash client first connects to the server and joins the "Main Lobby" room inside the example Zone.

2 - The user chooses one file to upload (an image, mp3 file, video file etc...) and hits the "Upload button". The file is transferred to the embedded web server.

3 - SmartFoxServer handles the file transfer and notifies the Zone extension with an event. From here it's up to the custom extension to handle the event and implement the necessary application logic (for example send an updated file list to all clients inside the "Main Lobby")

» The Server side extension

Let's start by analyzing the code in the server extension, located inside the sfsExtensions/ folder.

var imageList = null
var imagePath = "./webserver/webapps/default/uploads/"
var zone = null

function init()
{
	zone = _server.getCurrentZone()
	imageList = []

	// Get the list of images in the folder
	var dir = _server.getFileList(imagePath)
	
	// Cycle through all files
	for (var i in dir)
	{
		var file = dir[i]
		
		// Check if the current file is a jpg or png image 
		if (file.indexOf(".jpg") > -1 || file.indexOf(".png") > -1)
		{
			// Add it to the local list
			imageList.push(String(dir[i]))
		}
		
	}
	
	trace("Image Manager extension starts...")
}
 

We start by defining three global variables: imageList stores the current list of images available, imagePath points to the default path of the uploaded files, and zone is a reference to the current Zone object. The init() method is called as soon as the server starts and it populates the imageList array with all files available having a ".jpg" or ".png" extension.
You may be probably wondering why we store the list of files instead of reading it every time it's requested by the client. The reason is that usually disk accesses are slower especially with many files, so we prefer to keep a local list of files and avoid to continuously read the directory structure.

If you now scroll the code a few lines down you will see that the handleRequest() method essentially handles two different requests:

One more "task" is handled inside the handleInternalEvent() method when a new file is uploaded: the file name is added to the local list and a message is dispatched to the clients so that they update their local view.

function handleInternalEvent(evt)
{
	if (evt.name == "fileUpload")
	{
		var files = evt.files
		var user = evt.user
		var room = zone.getRoom(user.getRoom())
		var fList = []
		
		for (var i in files)
		{
			trace("Upload file: " + files[i].fileName + ", " + files[i].originalName)
			imageList.push(files[i].fileName)
			fList.push(files[i].fileName)
		}
		
		var resp = {}
		resp._cmd = "fileAdd"
		resp.files = fList
		
		_server.sendResponse(resp, -1, null, room.getAllUsers(), _server.PROTOCOL_JSON)
	}
} 

The files parameter received in the event handler is an array of files that were uploaded. In this specific example we didn't allow the client to select and upload multiple files, however since this is technically possible, we prefer to use a more generic version of the code that can handle multiple uploads.

» The client side

You can now open the source FLA file and position the playhead on the "chat" frame label.
In order to avoid problems with the flash player security policy we first call the System.allowDomain() method specifying the current IP address and current http port.

System.security.allowDomain("http://" + ip + ":" + smartfox.httpPort)

By default SmartFoxServer runs the embedded webserver on port 8080, so the result of the above string concatenation should be http://127.0.0.1:8080 ( the ip variable is defined under the "connect" frame label ). The few next lines of code assign the event handlers to the files list component and to the three buttons available on stage.

lb_files.addEventListener("change", loadImage)
bt_choose.addEventListener("click", chooseFile)
bt_upload.addEventListener("click", uploadFile)
bt_delete.addEventListener("click", deleteFile) 

After we have successfully auto-joined the "Main Lobby" room we can request the list of images to the server extension. The call is performed at the end of the onJoinRoom() handler:

// Request list of images
smartfox.sendXtMessage(xtName, "getImgList", {}, SmartFoxClient.PROTOCOL_JSON)

We don't need to pass any parameter to the extension so an empty object is used in the 3rd argument. Also notice that we're using the JSON format as serialization / deserialization protocol.
The server will send a response with the same command name ( getImgList ) that we will handle in the onExtensionResponse() function:

smartfox.onExtensionResponse = function(resObj:Object):Void
{
	var cmd:String = resObj._cmd
	
	if (cmd == "getImgList")
	{
		for (var i:String in resObj.imgList)
		{
			lb_files.addItem( {label:resObj.imgList[i], data:resObj.imgList[i]} )
		}
		
		lb_files.sortItemsBy("label", "ASC")
	}
	

Here we simply handle the list of images by adding them to the List component on stage. When one of the names in the list is clicked the following code is executed:

//----------------------------------------------------------
//	Handle selection in the image listbox
//	Load the image that is currently selected in the image list
//----------------------------------------------------------
function loadImage()
{
	var imgName = lb_files.selectedItem.data
	imgLoader.visible = false
	imgLoader.load(smartfox.getUploadPath() + imgName)
}

First we obtain the name of the selected file from the selectedItem.data property of the List component. In order to build the complete url of the image we call the SmartFoxClient getUploadPath() method and concatenate it with the image name. Finally we pass the full url to the load() method of the Loader component on stage which will load and display the image.

» Uploading images

In order to transfer files to the webserver we first need to create a FileReference object.
The FileReference class was introduced in Flash since version 8: it allows to select one or more files from the local folders and upload them to a webserver.
( You can read more about the flash.net.FileReference class here )

var fileRef:FileReference = new FileReference()
fileRef.addListener(this)

By passing this as the even listener object we maintain the scope in the main timeline, so the event listener methods can be defined in our main code:

//----------------------------------------------------------
//	Handles file selection
//----------------------------------------------------------
function onSelect(fr:FileReference)
{
	filename_txt.text = fr.name
}


//----------------------------------------------------------
//	Handles file upload complete
//----------------------------------------------------------
function onComplete(fr:FileReference)
{
	bt_upload.enabled = true
	bt_upload.label = "Upload"
}


//----------------------------------------------------------
//	Show upload progress in the button label
//----------------------------------------------------------
function onProgress(fr:FileReference, bl:Number, bt:Number)
{
	bt_upload.label = Math.floor((bl * 100) / bt) + "%"
}

The onSelect event will show the name of the selected file in the textfield near the button.
When the "Upload" button is pressed the button itself is disabled and it's label will show the percentage of the upload. (see onProgress handler)
In the onComplete() handler the button label is restored and the button re-enabled.

After a successful file transfer the extension will send a "fileAdd" event to all the clients in the room. The event is handled, as usual, in the onExtensionResponse() method:

	else if (cmd == "fileAdd")
	{
		var files:Array = resObj.files
		for (var i:String in files)
		{
			var file:String = files[i]
			lb_files.addItem( {label:file, data:file} )
		}
	}

The extension response sends an array of files that were uploaded, so we just iterate over it and add the file names to the List component

» Deleting images

When an image is selected in the image list it can be removed by simply clicking the "Delete Image" button. This is the request sent to the server side extension:

//----------------------------------------------------------
//	Send a delete-file request
//----------------------------------------------------------
function deleteFile()
{
	var fName:String = lb_files.selectedItem.data
	
	if (fName != undefined)
		smartfox.sendXtMessage(xtName, "delImg", {fName:fName}, SmartFoxClient.PROTOCOL_JSON)
}

We check that a file is selected and send its name to the extension. If no error occurs during the file deletion a "delImg" response will be received and handled in the onExtensionResponse() method:

	else if (cmd == "delImg")
	{
		var currFile = lb_files.selectedItem.data
		
		for (var i:Number = 0; i < lb_files.length; i++)
		{
			var file:String = lb_files.getItemAt(i).data
			if (file == resObj.fName)
			{
				lb_files.removeItemAt(i)
				
				// Hide current image if it was deleted
				if (file == currFile)
					imgLoader.visible = false
					
				break
			}
		}
	}

The server response contains the name of the removed file in a property called fName. In order to remove it from our List component we have to iterate over its items, find that name and remove it. Also we check if the image we're currently watching is the one that was deleted and set its visibility property to false.

» Conclusions

The example application shows how simple is adding realtime file exchange features with the embedded webserver. This is not just limited to image files but it can also be extended to any other type of document, especially audio files and video files. In fact one of the great features of the flash player is the ability to stream audio and video through the HTTP protocol.

 


doc index