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.
» 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.
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 |