AIR AS3 file download is corrupted when bandwidth is low
Asked Answered
L

1

9

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));
    }

}
Lavonnelaw answered 3/8, 2015 at 9:47 Comment(4)
I cant explain the behaviour only some suggestions to handle it.. ..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...Gingerly
Resuming the download seems good to me. When the complete event is fired before the file finished downloading, I could try resuming the download several times, and then stop if it fails for too long. I'd appreciate if someone could detail that in an answer.Lavonnelaw
Have an int variable that stores expected filesize.. When the Event Complete fires now check if your [download] fileData.length is equal to expected_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 like URLRequestHeader("range","bytes="+startPOS+"-"+endPOS); where startPOS is filedata.length+1 and endPos is expected_FileSize amount.Gingerly
@Gingerly It works. You should post that as an answer so I can accept it.Lavonnelaw
G
1

Try an int variable that stores expected filesize..

When the Event Complete fires now check if your [download] fileData.length is equal to expected_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 like URLRequestHeader("range","bytes="+startPOS+"-"+endPOS); where startPOS is filedata.length+1 and endPos is expected_FileSize amount.

Gingerly answered 14/11, 2015 at 10:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.