I am using a Downloader class to get large files from a IIS server on WS2012 and handle download progress.
It works fine, however when the client's bandwidth is too saturated, Progress events aren't fired anymore and after a certain amount of time the download just stops (the Complete event seems to be triggered?), despite the download being unfinished, leaving the client with a corrupt file.
I couldn't find out how to solve this, or even what strategy I should take on this problem (finish the download and display an error? Wait for bandwidth availability to get my next piece of bytes?)
Here's the Downloader.as class
public class Downloader extends EventDispatcher
{
[Event(name="DownloadComplete", type="DownloadEvent")]
public static var spd:int = 0;
private var file:File;
private var fileStream:FileStream;
private var url:String;
private var urlStream:URLStream;
var mc_background:MovieClip;
var howManyTimes:Number = 3; //How many times per second the download speed will be traced
var bytesLoaded:Number = 0; //don't change, necessary for calculation
var lastTime:int = 0; //don't change, necessary for calculation
private var waitingForDataToWrite:Boolean = false;
public function Downloader(s:MovieClip)
{
mc_background = s;
lastTime = getTimer();
urlStream = new URLStream();
urlStream.addEventListener(Event.OPEN, onOpenEvent);
urlStream.addEventListener(ProgressEvent.PROGRESS, onProgressEvent);
urlStream.addEventListener(Event.COMPLETE, onCompleteEvent);
fileStream = new FileStream();
fileStream.addEventListener(OutputProgressEvent.OUTPUT_PROGRESS, writeProgressHandler)
}
public function download(formUrl:String, toFile:File):void {
this.url = formUrl;
this.file = toFile;
mc_background.pb.file_txt.text = file.name;
fileStream.openAsync(file, FileMode.WRITE);
urlStream.load(new URLRequest(url));
}
private function onOpenEvent(event:Event):void {
waitingForDataToWrite = true;
dispatchEvent(event.clone());
}
private function onProgressEvent(event:ProgressEvent):void {
var time:int = getTimer();
if(time - lastTime >= (1000/howManyTimes))
{
var kiloBytes:Number = (event.bytesLoaded - bytesLoaded)/1000;
var timeInSecs:Number = (time - lastTime)/1000;
var kbsPerSecVal:Number = Math.floor(kiloBytes/timeInSecs);
trace(kbsPerSecVal + " kbs/s");
mc_background.pb.speed_txt.text = kbsPerSecVal + " kbs/s";
bytesLoaded = event.bytesLoaded;
lastTime = getTimer();
}
if(waitingForDataToWrite){
writeToDisk();
dispatchEvent(event.clone());
}
}
private function writeToDisk():void {
var fileData:ByteArray = new ByteArray();
urlStream.readBytes(fileData, 0, urlStream.bytesAvailable);
fileStream.writeBytes(fileData,0,fileData.length);
waitingForDataToWrite = false;
dispatchEvent(new DataEvent(DataEvent.DATA));
}
private function writeProgressHandler(evt:OutputProgressEvent):void{
waitingForDataToWrite = true;
}
private function onCompleteEvent(event:Event):void {
if(urlStream.bytesAvailable>0)
writeToDisk();
fileStream.close();
fileStream.removeEventListener(OutputProgressEvent.OUTPUT_PROGRESS, writeProgressHandler);
dispatchEvent(event.clone());
// dispatch additional DownloadEvent
dispatchEvent(new DownloadEvent(DownloadEvent.DOWNLOAD_COMPLETE, url, file));
}
}
..finish the download and display an error? or wait for bandwidth availability to get my next piece of bytes?
Well why not do both? If there is a problem bring up notice saying "A network error caused an incomplete download" at this point show 2 buttons: "Resume download" or "Keep what I got".. If you want to resume you can read this and this also... – Gingerlyint
variable that stores expected filesize.. When the Event Complete fires now check if your [download]fileData.length
is equal toexpected_FileSize
.. If less then retry until equal or whatever is good enough. To get from last bytes amount you use a "range request" in the request header. It's also shown in this link If that helps you. Something likeURLRequestHeader("range","bytes="+startPOS+"-"+endPOS);
where startPOS isfiledata.length+1
and endPos isexpected_FileSize
amount. – Gingerly