blob: b405aa79fddad9860c9f8c865b6bf44618100ce8 [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
package com.riaspace.nativeApplicationUpdater
{
import air.update.events.DownloadErrorEvent;
import air.update.events.StatusUpdateErrorEvent;
import air.update.events.StatusUpdateEvent;
import air.update.events.UpdateEvent;
import com.riaspace.nativeApplicationUpdater.utils.HdiutilHelper;
import flash.desktop.NativeApplication;
import flash.desktop.NativeProcess;
import flash.desktop.NativeProcessStartupInfo;
import flash.events.ErrorEvent;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IOErrorEvent;
import flash.events.ProgressEvent;
import flash.filesystem.File;
import flash.filesystem.FileMode;
import flash.filesystem.FileStream;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.net.URLStream;
import flash.system.Capabilities;
import flash.utils.ByteArray;
import flash.utils.setTimeout;
[Event(name="initialized", type="air.update.events.UpdateEvent")]
[Event(name="checkForUpdate", type="air.update.events.UpdateEvent")]
[Event(name="updateStatus",type="air.update.events.StatusUpdateEvent")]
[Event(name="updateError",type="air.update.events.StatusUpdateErrorEvent")]
[Event(name="downloadStart",type="air.update.events.UpdateEvent")]
[Event(name="downloadError",type="air.update.events.DownloadErrorEvent")]
[Event(name="downloadComplete",type="air.update.events.UpdateEvent")]
[Event(name="progress",type="flash.events.ProgressEvent")]
[Event(name="error",type="flash.events.ErrorEvent")]
public class NativeApplicationUpdater extends EventDispatcher
{
namespace UPDATE_XMLNS_1_0 = "http://ns.riaspace.com/air/framework/update/description/1.0";
namespace UPDATE_XMLNS_1_1 = "http://ns.riaspace.com/air/framework/update/description/1.1";
/**
* The updater has not been initialized.
**/
public static const UNINITIALIZED:String = "UNINITIALIZED";
/**
* The updater is initializing.
**/
public static const INITIALIZING:String = "INITIALIZING";
/**
* The updater has been initialized.
**/
public static const READY:String = "READY";
/**
* The updater has not yet checked for the update descriptor file.
**/
public static const BEFORE_CHECKING:String = "BEFORE_CHECKING";
/**
* The updater is checking for an update descriptor file.
**/
public static const CHECKING:String = "CHECKING";
/**
* The update descriptor file is available.
**/
public static const AVAILABLE:String = "AVAILABLE";
/**
* The updater is downloading the AIR file.
**/
public static const DOWNLOADING:String = "DOWNLOADING";
/**
* The updater has downloaded the AIR file.
**/
public static const DOWNLOADED:String = "DOWNLOADED";
/**
* The updater is installing the AIR file.
**/
public static const INSTALLING:String = "INSTALLING";
[Bindable]
public var updateURL:String;
protected var _isNewerVersionFunction:Function;
protected var _updateDescriptor:XML;
protected var _updateVersion:String;
protected var _updatePackageURL:String;
protected var _updateDescription:String;
protected var _currentVersion:String;
protected var _downloadedFile:File;
protected var _installerType:String;
protected var _currentState:String = UNINITIALIZED;
protected var updateDescriptorLoader:URLLoader;
protected var os:String = Capabilities.os.toLowerCase();
protected var urlStream:URLStream;
protected var fileStream:FileStream;
public function NativeApplicationUpdater()
{
}
public function initialize():void
{
if (currentState == UNINITIALIZED)
{
currentState = INITIALIZING;
var applicationDescriptor:XML = NativeApplication.nativeApplication.applicationDescriptor;
var xmlns:Namespace = new Namespace(applicationDescriptor.namespace());
if (xmlns.uri == "http://ns.adobe.com/air/application/2.0")
currentVersion = applicationDescriptor.xmlns::version;
else
currentVersion = applicationDescriptor.xmlns::versionNumber;
if (os.indexOf("win") > -1)
{
installerType = "exe";
}
else if (os.indexOf("mac") > -1)
{
installerType = "dmg";
}
else if (os.indexOf("linux") > -1)
{
if ((new File("/usr/bin/dpkg")).exists)
installerType = "deb";
else
installerType = "rpm";
}
else
{
dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, false, false, "Not supported os type!", UpdaterErrorCodes.ERROR_9000));
}
currentState = READY;
dispatchEvent(new UpdateEvent(UpdateEvent.INITIALIZED));
}
}
public function checkNow():void
{
if (currentState == READY)
{
currentState = BEFORE_CHECKING;
var checkForUpdateEvent:UpdateEvent = new UpdateEvent(UpdateEvent.CHECK_FOR_UPDATE, false, true);
dispatchEvent(checkForUpdateEvent);
if (!checkForUpdateEvent.isDefaultPrevented())
{
addEventListener(StatusUpdateEvent.UPDATE_STATUS,
function(event:StatusUpdateEvent):void
{
if (event.available && !event.isDefaultPrevented())
downloadUpdate();
});
addEventListener(UpdateEvent.DOWNLOAD_COMPLETE,
function(event:UpdateEvent):void
{
if (!event.isDefaultPrevented())
installUpdate();
});
checkForUpdate();
}
}
}
/**
* ------------------------------------ CHECK FOR UPDATE SECTION -------------------------------------
*/
/**
* Checks for update, this can be runned only in situation when UpdateEvent.CHECK_FOR_UPDATE was cancelled.
*/
public function checkForUpdate():void
{
if (currentState == BEFORE_CHECKING)
{
currentState = CHECKING;
updateDescriptorLoader = new URLLoader();
updateDescriptorLoader.addEventListener(Event.COMPLETE, updateDescriptorLoader_completeHandler);
updateDescriptorLoader.addEventListener(IOErrorEvent.IO_ERROR, updateDescriptorLoader_ioErrorHandler);
try
{
updateDescriptorLoader.load(new URLRequest(updateURL));
}
catch(error:Error)
{
dispatchEvent(new StatusUpdateErrorEvent(StatusUpdateErrorEvent.UPDATE_ERROR, false, false,
"Error downloading update descriptor file: " + error.message,
UpdaterErrorCodes.ERROR_9002, error.errorID));
}
}
}
protected function updateDescriptorLoader_completeHandler(event:Event):void
{
updateDescriptorLoader.removeEventListener(Event.COMPLETE, updateDescriptorLoader_completeHandler);
updateDescriptorLoader.removeEventListener(IOErrorEvent.IO_ERROR, updateDescriptorLoader_ioErrorHandler);
updateDescriptorLoader.close();
updateDescriptor = new XML(updateDescriptorLoader.data);
if (updateDescriptor.namespace() == UPDATE_XMLNS_1_0)
{
updateVersion = updateDescriptor.UPDATE_XMLNS_1_0::version;
updateDescription = updateDescriptor.UPDATE_XMLNS_1_0::description;
updatePackageURL = updateDescriptor.UPDATE_XMLNS_1_0::urls.UPDATE_XMLNS_1_1::[installerType];
}
else
{
var typeXml:XMLList = updateDescriptor.UPDATE_XMLNS_1_1::[installerType];
if (typeXml.length() > 0)
{
updateVersion = typeXml.UPDATE_XMLNS_1_1::version;
updateDescription = typeXml.UPDATE_XMLNS_1_1::description;
updatePackageURL = typeXml.UPDATE_XMLNS_1_1::url;
}
}
if (!updateVersion || !updatePackageURL)
{
dispatchEvent(new StatusUpdateErrorEvent(StatusUpdateErrorEvent.UPDATE_ERROR, false, false,
"Update package is not defined for current installerType: " + installerType, UpdaterErrorCodes.ERROR_9001));
return;
}
currentState = AVAILABLE;
dispatchEvent(new StatusUpdateEvent(
StatusUpdateEvent.UPDATE_STATUS, false, true,
isNewerVersionFunction.call(this, currentVersion, updateVersion), updateVersion)); // TODO: handle last event param with details (description)
}
protected function updateDescriptorLoader_ioErrorHandler(event:IOErrorEvent):void
{
updateDescriptorLoader.removeEventListener(Event.COMPLETE, updateDescriptorLoader_completeHandler);
updateDescriptorLoader.removeEventListener(IOErrorEvent.IO_ERROR, updateDescriptorLoader_ioErrorHandler);
updateDescriptorLoader.close();
dispatchEvent(new StatusUpdateErrorEvent(StatusUpdateErrorEvent.UPDATE_ERROR, false, false,
"IO Error downloading update descriptor file: " + event.text,
UpdaterErrorCodes.ERROR_9003, event.errorID));
}
/**
* ------------------------------------ DOWNLOAD UPDATE SECTION -------------------------------------
*/
/**
* Starts downloading update.
*/
public function downloadUpdate():void
{
if (currentState == AVAILABLE)
{
var fileName:String = updatePackageURL.substr(updatePackageURL.lastIndexOf("/") + 1);
downloadedFile = File.createTempDirectory().resolvePath(fileName);
fileStream = new FileStream();
fileStream.addEventListener(IOErrorEvent.IO_ERROR, urlStream_ioErrorHandler);
fileStream.addEventListener(Event.CLOSE, fileStream_closeHandler);
fileStream.openAsync(downloadedFile, FileMode.WRITE);
urlStream = new URLStream();
urlStream.addEventListener(Event.OPEN, urlStream_openHandler);
urlStream.addEventListener(ProgressEvent.PROGRESS, urlStream_progressHandler);
urlStream.addEventListener(Event.COMPLETE, urlStream_completeHandler);
urlStream.addEventListener(IOErrorEvent.IO_ERROR, urlStream_ioErrorHandler);
try
{
urlStream.load(new URLRequest(updatePackageURL));
}
catch(error:Error)
{
dispatchEvent(new DownloadErrorEvent(DownloadErrorEvent.DOWNLOAD_ERROR, false, false,
"Error downloading update file: " + error.message, UpdaterErrorCodes.ERROR_9004, error.message));
}
}
}
protected function urlStream_openHandler(event:Event):void
{
currentState = NativeApplicationUpdater.DOWNLOADING;
dispatchEvent(new UpdateEvent(UpdateEvent.DOWNLOAD_START));
}
protected function fileStream_closeHandler(event:Event):void
{
fileStream.removeEventListener(Event.CLOSE, fileStream_closeHandler);
fileStream.removeEventListener(IOErrorEvent.IO_ERROR, urlStream_ioErrorHandler);
currentState = NativeApplicationUpdater.DOWNLOADED;
dispatchEvent(new UpdateEvent(UpdateEvent.DOWNLOAD_COMPLETE, false, true));
}
protected function urlStream_progressHandler(event:ProgressEvent):void
{
var bytes:ByteArray = new ByteArray();
urlStream.readBytes(bytes);
fileStream.writeBytes(bytes);
dispatchEvent(event);
}
protected function urlStream_completeHandler(event:Event):void
{
urlStream.removeEventListener(Event.OPEN, urlStream_openHandler);
urlStream.removeEventListener(ProgressEvent.PROGRESS, urlStream_progressHandler);
urlStream.removeEventListener(Event.COMPLETE, urlStream_completeHandler);
urlStream.removeEventListener(IOErrorEvent.IO_ERROR, urlStream_ioErrorHandler);
urlStream.close();
fileStream.close();
}
protected function urlStream_ioErrorHandler(event:IOErrorEvent):void
{
fileStream.removeEventListener(Event.CLOSE, fileStream_closeHandler);
fileStream.removeEventListener(IOErrorEvent.IO_ERROR, urlStream_ioErrorHandler);
fileStream.close();
urlStream.removeEventListener(Event.OPEN, urlStream_openHandler);
urlStream.removeEventListener(ProgressEvent.PROGRESS, urlStream_progressHandler);
urlStream.removeEventListener(Event.COMPLETE, urlStream_completeHandler);
urlStream.removeEventListener(IOErrorEvent.IO_ERROR, urlStream_ioErrorHandler);
urlStream.close();
dispatchEvent(new DownloadErrorEvent(DownloadErrorEvent.DOWNLOAD_ERROR, false, false,
"Error downloading update file: " + event.text, UpdaterErrorCodes.ERROR_9005, event.errorID));
}
/**
* ------------------------------------ INSTALL UPDATE SECTION -------------------------------------
*/
/**
* Installs downloaded update
*/
public function installUpdate():void
{
if (currentState == DOWNLOADED)
{
if (os.indexOf("win") > -1)
{
installFromFile(downloadedFile);
}
else if (os.indexOf("mac") > -1)
{
var hdiutilHelper:HdiutilHelper = new HdiutilHelper(downloadedFile);
hdiutilHelper.addEventListener(Event.COMPLETE, hdiutilHelper_completeHandler);
hdiutilHelper.addEventListener(ErrorEvent.ERROR, hdiutilHelper_errorHandler);
hdiutilHelper.attach();
}
else if (os.indexOf("linux") > -1)
{
installFromFile(downloadedFile);
}
}
}
private function hdiutilHelper_errorHandler(event:ErrorEvent):void
{
var hdiutilHelper:HdiutilHelper = event.target as HdiutilHelper;
hdiutilHelper.removeEventListener(Event.COMPLETE, hdiutilHelper_completeHandler);
hdiutilHelper.removeEventListener(ErrorEvent.ERROR, hdiutilHelper_errorHandler);
dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, false, false,
"Error attaching dmg file!", UpdaterErrorCodes.ERROR_9008));
}
private function hdiutilHelper_completeHandler(event:Event):void
{
var hdiutilHelper:HdiutilHelper = event.target as HdiutilHelper;
hdiutilHelper.removeEventListener(Event.COMPLETE, hdiutilHelper_completeHandler);
hdiutilHelper.removeEventListener(ErrorEvent.ERROR, hdiutilHelper_errorHandler);
var attachedDmg:File = new File(hdiutilHelper.mountPoint);
var files:Array = attachedDmg.getDirectoryListing();
if (files.length == 1)
{
var installFileFolder:File = File(files[0]).resolvePath("Contents/MacOS");
var installFiles:Array = installFileFolder.getDirectoryListing();
if (installFiles.length == 1)
installFromFile(installFiles[0]);
else
dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, false, false,
"Contents/MacOS folder should contain only 1 install file!", UpdaterErrorCodes.ERROR_9006));
}
else
{
dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, false, false,
"Mounted volume should contain only 1 install file!", UpdaterErrorCodes.ERROR_9007));
}
}
protected function installFromFile(updateFile:File):void
{
var beforeInstallEvent:UpdateEvent = new UpdateEvent(UpdateEvent.BEFORE_INSTALL, false, true);
dispatchEvent(beforeInstallEvent);
if (!beforeInstallEvent.isDefaultPrevented())
{
currentState = INSTALLING;
if (os.indexOf("linux") > -1)
{
updateFile.openWithDefaultApplication();
}
else
{
var info:NativeProcessStartupInfo = new NativeProcessStartupInfo();
var cmdExe:File = (os.indexOf("win") > -1) ? new File("C:\\Windows\\System32\\cmd.exe") : null;
if (cmdExe && cmdExe.exists)
{
var args:Vector.<String> = new Vector.<String>();
args.push("/c", updateFile.nativePath);
info.executable = cmdExe;
info.arguments = args;
}
else
{
info.executable = updateFile;
}
var installProcess:NativeProcess = new NativeProcess();
installProcess.start(info);
}
setTimeout(NativeApplication.nativeApplication.exit, 200);
}
}
[Bindable]
public function get currentVersion():String
{
return _currentVersion;
}
protected function set currentVersion(value:String):void
{
_currentVersion = value;
}
[Bindable]
public function get updateVersion():String
{
return _updateVersion;
}
protected function set updateVersion(value:String):void
{
_updateVersion = value;
}
[Bindable]
public function get updateDescriptor():XML
{
return _updateDescriptor;
}
protected function set updateDescriptor(value:XML):void
{
_updateDescriptor = value;
}
[Bindable]
public function get currentState():String
{
return _currentState;
}
protected function set currentState(value:String):void
{
_currentState = value;
}
[Bindable]
public function get downloadedFile():File
{
return _downloadedFile;
}
protected function set downloadedFile(value:File):void
{
_downloadedFile = value;
}
[Bindable]
public function get isNewerVersionFunction():Function
{
if (_isNewerVersionFunction != null)
return _isNewerVersionFunction;
else
return function(currentVersion:String, updateVersion:String):Boolean { return currentVersion != updateVersion};
}
public function set isNewerVersionFunction(value:Function):void
{
_isNewerVersionFunction = value;
}
[Bindable]
public function get installerType():String
{
return _installerType;
}
protected function set installerType(value:String):void
{
_installerType = value;
}
[Bindable]
public function get updatePackageURL():String
{
return _updatePackageURL;
}
protected function set updatePackageURL(value:String):void
{
_updatePackageURL = value;
}
[Bindable]
public function get updateDescription():String
{
return _updateDescription;
}
protected function set updateDescription(value:String):void
{
_updateDescription = value;
}
}
}