Skip to content

Instantly share code, notes, and snippets.

@7interactivestudio
Created January 11, 2014 02:27
Show Gist options
  • Save 7interactivestudio/8366228 to your computer and use it in GitHub Desktop.
Save 7interactivestudio/8366228 to your computer and use it in GitHub Desktop.
ProgressiveDownload allows to download a file by chunks of data (useful for mobile devices, and memory friendly) and has the ability to pause/resume downloads saving download information for later usage. You can close the app ,and resume the download whenever you want. The API needs to be polish a lot... this is an "alpha version".
/**
* Code by rodrigolopezpeker (aka 7interactive™) on 1/9/14 12:30 PM.
*/
package rodrigolopezpeker.net {
import flash.events.Event;
import flash.events.ProgressEvent;
import flash.filesystem.File;
import flash.filesystem.FileMode;
import flash.filesystem.FileStream;
import flash.net.SharedObject;
import flash.net.URLRequest;
import flash.net.URLRequestHeader;
import flash.net.URLStream;
import flash.utils.ByteArray;
/**
*
* Basic usage:
* ------------------------------------------------
*
* var remoteFileURL:String = "http://airdownload.adobe.com/air/mac/download/latest/AIRSDK_Compiler.tbz2" ;
* var pd:ProgressiveDownload = new ProgressiveDownload() ;
* pd.targetFile = File.desktop.resolvePath('airsdk.tbz2');
* pd.onComplete = onDownloadComplete ;
* pd.onProgress = onDownloadProgress ;
* pd.load( remoteFileURL ) ;
*
* function onDownloadProgress(loadedBytes:uint, totalBytes:uint):void {
* trace("download progress: " + Math.round( loadedBytes/totalBytes * 100 ) "%" ) ;
* }
* function onDownloadComplete():void {
* trace("file downloaded complete");
* }
*
* // somewhere in the code.
*
* pd.pause() ; // pause the download
* pd.resume() ; // resume the download
* pd.reset() ; // reset the instances, stops download, and delete the File references (not the actual file).
*
* pd.saveToSO() ; // saves in cache the file paths, url and bytes positions for later usage (useful for persistent reference)
* pd.usePendingFile() ;// sets the instance to the SharedObject data stored by saveToSO(), just call pd.load() to resume.
* pd.clearSO() ; // clear the SharedObject data (if any).
*
*/
public class ProgressiveDownload {
public static const SHARED_OBJECT_ID:String = '_resumableDownloads' ;
public static var verbose:Boolean = true ;
private var rangeHeader:URLRequestHeader;
private var fromBytePos:int;
public var totalBytes:int;
public var loadedBytes:uint;
public var request:URLRequest;
public var urlStream:URLStream;
public var fileStream:FileStream;
private var _currentBytesChunk:ByteArray ;
// current file downloaded.
private var _targetFile:File;
public var complete:Boolean;
public var paused:Boolean;
public var onComplete:Function;
public var onProgress:Function;
// used to resume downloads
private var _so:SharedObject;
private var usingPending:Boolean;
public function ProgressiveDownload() {
_so = SharedObject.getLocal( SHARED_OBJECT_ID ) ;
complete = false ;
rangeHeader = new URLRequestHeader("range");
request = new URLRequest();
request.requestHeaders.push(rangeHeader);
urlStream = new URLStream();
fileStream = new FileStream();
usingPending = false ;
_currentBytesChunk = new ByteArray();
}
public function load(pURL:String=null):void {
if(pURL)
request.url = pURL ;
complete = false ;
paused = usingPending ;
if( !usingPending )
loadedBytes = totalBytes = 0 ;
updateRangeHeader() ;
listeners(true);
urlStream.load(request);
}
private function progressHandler( e:ProgressEvent ):void {
// first time only...
if( loadedBytes == 0 ){
totalBytes = e.bytesTotal ;
}
// loadedBytes = e.bytesLoaded ;
writeDataToDisk();
if(onProgress!=null)
onProgress( loadedBytes, totalBytes );
}
private function completeHandler( e:Event ):void {
writeDataToDisk();
// clear the cache.
clearSO();
usingPending = false ;
complete = true ;
paused = false ;
if(onComplete!=null) onComplete();
listeners(false);
}
private function listeners(b:Boolean):void {
if( b ){
urlStream.addEventListener( ProgressEvent.PROGRESS, progressHandler );
urlStream.addEventListener( Event.COMPLETE, completeHandler );
} else {
urlStream.removeEventListener( ProgressEvent.PROGRESS, progressHandler );
urlStream.removeEventListener( Event.COMPLETE, completeHandler );
}
}
private function writeDataToDisk():void {
_currentBytesChunk.clear();
urlStream.readBytes(_currentBytesChunk);
loadedBytes += _currentBytesChunk.length ;
fileStream.open( _targetFile, FileMode.APPEND );
fileStream.writeBytes(_currentBytesChunk);
fileStream.close();
}
private function updateRangeHeader():void {
rangeHeader.value = "bytes="+fromBytePos+"-" ;
if( totalBytes > 0 ) rangeHeader.value += totalBytes ;
}
public function get running():Boolean {
return urlStream.connected ;
}
public function pause():void {
if(complete){
if( verbose )
trace('ProgressiveDownload::pause() > Download already complete.');
return ;
}
fromBytePos = loadedBytes ;
paused = true ;
listeners(false);
if( urlStream.connected ) urlStream.close() ;
}
public function resume():void {
if(complete || urlStream.connected ){
if( verbose )
trace('ProgressiveDownload::resume() > File already downloaded');
return ;
}
paused = false ;
updateRangeHeader() ;
listeners(true);
urlStream.load(request);
}
public function get targetFile():File {return _targetFile;}
public function set targetFile(value:File):void {
_targetFile = value;
}
public function reset():void {
_targetFile = null ;
_currentBytesChunk.clear();
listeners(false);
totalBytes = 0 ;
fromBytePos = 0 ;
loadedBytes = 0 ;
if( urlStream.connected ) urlStream.close() ;
complete = false ;
paused = false ;
}
public function clearSO():void {
_so.clear();
}
/*public function hasPendingFile(): Boolean {
return _so.data.hasOwnProperty('requestURL');
}*/
public function usePendingFile(): Boolean {
if( !_so.data.hasOwnProperty('requestURL') || running ) return false ;
var file:File = new File( _so.data.localFileURL ) ;
if(!file.exists) {
if( verbose )
trace('ProgressiveDownload::usePendingFile() > the local file doesn\'t exists.');
return false ;
}
request.url = _so.data.requestURL ;
_targetFile = file ;
fromBytePos = loadedBytes = _so.data.bytesLoaded ;
totalBytes = _so.data.bytesTotal ;
usingPending = true ;
// paused state
return true ;
}
public function getPendingFileInfo():Object {
return _so.data ;
}
public function saveToSO():void {
// useful to resume the download in the future.
if( !request.url ){
if( verbose )
trace('ProgressiveDownload::saveDownloadProgress() > needs a target url to download.');
return ;
}
if( !_targetFile ){
if( verbose )
trace('ProgressiveDownload::saveDownloadProgress() > needs an output file to save the path reference.');
return ;
}
if( totalBytes == 0 ){
if( verbose )
trace('ProgressiveDownload::saveDownloadProgress() > no data has been saved yet.');
return ;
}
_so.data.requestURL = request.url ;
_so.data.localFileURL = _targetFile.nativePath ;
_so.data.bytesLoaded = loadedBytes ;
_so.data.bytesTotal = totalBytes ;
_so.flush();
}
public function setURL(pText:String):void {
request.url = pText ;
}
public function get currentBytesChunk():ByteArray {
return _currentBytesChunk;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment