The aim of this post is to get an AIR application to take a collection of files dragged n dropped onto it, zip them up on the client, and send them off to the server (ASP.NET).
There are a few things to note here:
- Flash officially supports files of up to 100MB, trying to ZIP anything larger and you WILL run into server memory issues; Flash will crash. You can use File.upload() for files > 100MB, but trying to ZIP them will cause the crash.
- ZIPping on the client takes time, 50MB could take from 15s to a minute. The code supplied performs it sync (not async).
- client software FZip, server zip : ISharpZipLib
- uploading manually (using URLLoader instead of File.upload means in you cannot track the Upload’s progress – as of AIR 1.5).
Step 1. The Drag & Drop in AIR
This is quite trivial. Allowing the user to drag and drop as many files/folders onto your application.
Client (AIR) code:
<?xml version="1.0" encoding="utf-8"?> <mx:WindowedApplication nativeDragEnter="onDragEnter(event)" nativeDragDrop="onDragDrop(event)" layout="absolute" xmlns:mx="http://www.adobe.com/2006/mxml" > <mx:Script> <![CDATA[ import mx.managers.DragManager; import mx.events.DragEvent; private function onDragEnter(evt:NativeDragEvent):void { //ensure clipboard contains files if (evt.clipboard.hasFormat(ClipboardFormats.FILE_LIST_FORMAT)) { NativeDragManager.acceptDragDrop(this); } } private var _filesToUpload:Array; private function onDragDrop(evt:NativeDragEvent):void { _filesToUpload = evt.clipboard.getData(ClipboardFormats.FILE_LIST_FORMAT) as Array; } ]]> </mx:Script> </mx:WindowedApplication>
Step 2. Zipping on the client
The nicest solution I could find for compressing files in AIR to ZIP is FZip.
I tried another library, but ISharpLib didn’t like it, it was giving me EOF file header issues in the ZIP decompression.
So the solution is to take the ZIP byteArray and to send it via the lower level URLLoader class.
NOTE: this uses the FileStream.open is sync mode. You could use FileStream.openAsAsync though it would obviously require listening and handling the corresponding events.
Client (AIR) code [URLLoader.load() solution]:
var request:URLRequest = new URLRequest("http://example.com/Handlers/UploadHandler.ashx"); request.method = URLRequestMethod.POST; request.contentType = "application/octet-stream"; var loader:URLLoader = new URLLoader(); loader.dataFormat = URLLoaderDataFormat.BINARY; loader.addEventListener(Event.OPEN, onFileUploadStart,false,0,true); //no point listening for progress, because it doesn't work for URLLoader uploads, only downloads //loader.addEventListener(ProgressEvent.PROGRESS, onFileUploadProgress,false,0,true); loader.addEventListener(Event.COMPLETE,onFileUploadComplete,false,0,true); var fzip:FZip = new FZip(); for each (var file:File in model.filesToUpload) { var fs:FileStream = new FileStream(); fs.open(file, FileMode.READ); var data:ByteArray = new ByteArray(); fs.readBytes(data); fs.close(); fzip.addFile(file.name, data); } var bytes:ByteArray = new ByteArray(); fzip.serialize(bytes); request.data = bytes; loader.load(request);
What about sending extra parameters?
var request:URLRequest = new URLRequest("http://example.com/Handlers/UploadHandler.ashx?someVariable=" + someVariable);
What about the upload progress bar?
If you need the progress bar, then you’re best option is writing this zip file to the client and uploading that.
This also means that if you have optional parameters, you don’t have to append them to the URL but can send them in the data.
This changes the client code to use File.upload().
Client (AIR) code – [File.upload() solution]:
var params:URLVariables = new URLVariables(); params.someVariable = someVariable; //add other variables here var request:URLRequest = new URLRequest(model.UPLOAD_URL); request.method = URLRequestMethod.POST; request.data = params; var fzip:FZip = new FZip(); for each (var file:File in model.filesToUpload) { var fs:FileStream = new FileStream(); fs.open(file, FileMode.READ); var data:ByteArray = new ByteArray(); fs.readBytes(data); fs.close(); fzip.addFile(file.name, data); } //get the zip as a byte array var bytes:ByteArray = new ByteArray(); fzip.serialize(bytes); //write the zip to a local file var fsW:FileStream = new FileStream(); //change "tmp.zip" to a better name :) var wFile:File = File.applicationStorageDirectory.resolvePath("tmp.zip"); fsW.open(wFile, FileMode.WRITE); fsW.writeBytes(bytes); fsW.close(); //listen to upload events wFile.addEventListener(Event.OPEN, onFileUploadStart,false,0,true); wFile.addEventListener(ProgressEvent.PROGRESS, onFileUploadProgress,false,0,true); wFile.addEventListener(Event.COMPLETE,onFileUploadComplete,false,0,true); //start the upload wFile.upload(request);
Step 3. Unzipping on the server
Now, I use SharpZipLib in .NET because it’s Open Source, and because it’s also bundled with WebORB .NET, the remoting gateway that I often use.
If you use the URLLoader.load() option above (from Step 2) – then you must use Request.InputStream to access the parameters
Server (ASP.NET C# Handler – ashx) code: (SharpZipLib extraction)
using ICSharpCodeInternal.SharpZipLib.Zip; public class UploadHandler : IHttpHandler { public void ProcessRequest (HttpContext context) { String someVariable = context.Request["someVariable"]; ZipInputStream zipStream = new ZipInputStream(context.Request.InputStream); ZipEntry zipEntry; while (true) { zipEntry = zipStream.GetNextEntry(); if (zipEntry == null) { break; } //choose location to extract files to string serverFolder = context.Server.MapPath("~/Uploads/"); FileStream streamWriter = File.Create(( serverFolder + zipEntry.Name)); int size = 2048; byte[] data = new byte[2048]; while (true) { size = zipStream.Read(data, 0, data.Length); if (size > 0) { streamWriter.Write(data, 0, size); } else { break; } } streamWriter.Close(); } zipStream.Close(); } }
If you are using File.upload() on the client
You’re Handler in ASP.NET now will be looking in the context.Request.Files array for the attached ZIP.
Modified Server (ASP.NET C# Handler – ashx) code:
ZipInputStream zipStream = new ZipInputStream(context.Request.Files[0].InputStream);
If you need folder structures
To do ZIP folders, you simply add the folder name before the file when you ZIP:
Modified Client (AIR) code:
fzip.addFile(folderName + "/" + file.name, data);
And then in the server code, you need to create directories if required:
Modified Server (ASP.NET C# Handler – ashx) code:
//existing code ... string serverFolder = context.Server.MapPath("~/Uploads/"); string dir = Path.GetDirectoryName(serverFolder + zipEntry.Name); if (!Directory.Exists(dir)) { Directory.CreateDirectory(dir); } //existing code FileStream streamWriter = File.Create(( serverFolder + ze.Name)); ...
Recent Comments