16 сентября 2011 г.

Класс обновления приложения AIR

Часто встречаю вопрос типа "Как сделать обновление приложения AIR". Не проверял, есть ли в сети что-то подобное (наверняка есть), а просто решил выложить свой класс для этой процедуры. Он далеко не отшлифован и функционал предполагалось значительно расширить. Но сразу не сделал, а времени на это уже нет. Поэтому, кому понравится, может расширять и модернизировать этот класс без всяческих оговорок.

Итак, при запуске (не обязательно) AIR-приложения определяем версию приложения следующим кодом:

var descriptor:XML = nativeApplication.applicationDescriptor;
var air:Namespace = descriptor.namespaceDeclarations()[0];
var version:String = String(descriptor.air::version);

Версия же приложения устанавливается в файле application.xml до сборки приложения. Например.

<version>1.0</version> 

Затем подключаете мой класс для обновления приложения, который сделает всю работу.

WebUpdate.instance.start(version);

Для функционирования обновления необходимо выложить в сеть для доступа само приложение (файл формата AIR) и текстовый файл с единственной записью, например:

1.0.1

Класс распарсит строку и сделает всё необходимое.

Вот пример кода корневого компонента для тестов:

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml"
                        width="320"
                        height="240"
                        windowComplete="onWindowsComplete(event)"
                        title="AIR Web Update Tutorial"
                        status="AIR Web Update Tutorial"
                        showStatusBar="true">
<mx:Script>
    <![CDATA[
    
    import flash.events.Event;
    import ru.pixeltyumen.air.utils.WebUpdate;
    
    
    private function onWindowsComplete (e:Event):void {
        var descriptor:XML = nativeApplication.applicationDescriptor;
        var air:Namespace = descriptor.namespaceDeclarations()[0];
        var version:String = String(descriptor.air::version);
        WebUpdate.instance.start(version);
    }
    
    ]]>
</mx:Script>
<mx:TextArea width="100%" height="100%" editable="false" selectable="false"
    text="This application is demonstrating the process of WebUpdate class job." />
</mx:WindowedApplication>

А это код самого класса:

package ru.pixeltyumen.air.utils
{
    import air.net.URLMonitor;
    import flash.desktop.Updater;
    import flash.events.StatusEvent;
    import flash.filesystem.File;
    import flash.filesystem.FileStream;
    import flash.filesystem.FileMode;
    import flash.events.Event;
    import flash.events.IOErrorEvent;
    import flash.events.ProgressEvent;
    import flash.events.EventDispatcher;
    import flash.utils.ByteArray;
    import mx.events.CloseEvent;
    import flash.net.URLLoader;
    import flash.net.URLRequest;
    import flash.net.URLStream;
    import mx.controls.Alert;
    
    /**
     * ...
     * @author .p.i.x.e.l.
     */
    public class WebUpdate extends EventDispatcher
    {
        
        public static const ONLY_UPDATE_ALERT:String = "ONLY_UPDATE_ALERT";
        public static const UPDATE_AND_EQUAL_ALERT:String = "UPDATE_AND_EQUAL_ALERT";
        public static const DETAILED_ALERT:String = "DETAILED_ALERT";
        
        private static const NEED_UPDATE:String = "NEED_UPDATE";
        private static const EQUAL_VERSIONS:String = "EQUAL_VERSIONS";
        private static const NEWEST_VERSION:String = "NEWEST_VERSION";

        private static var _instance:WebUpdate;

        private var _netStatus:URLMonitor;
        private var _currentVersion:String;
        private var _updateVersion:String;
        private var _state:String = EQUAL_VERSIONS;
        private var _mode:String = NEED_UPDATE;

        private var _versionTextUrl:String =
            "http://url_for_text_file_with_version_number";
        private var _updateAppUrl:String =
            "http://url_for_last_version_of_application";
        
        // Message strings
        private var _equalTitleString:String = "Внимание!";
        private var _equalTextString:String = "У вас последняя версия приложения, не требующая обновления.";
        private var _newestTitleString:String = "Внимание!";
        private var _newestTextString:String = "Странно, но ваша версия новее!";
        private var _olderTitleString:String = "Внимание!";
        private var _olderTextString:String = "Ваша версия более старая и требует обновления. Приступить к обновлению?";
        private var _errorTitleString:String = "Ошибка!";
        private var _errorTextString:String = "Сервер обновлений не доступен! Проверте настройки сети или повторите операцию позже.";
        
        
        
        /**
         * Constructor
         */
        public function WebUpdate () {
            if (_instance != null) {
                throw new Error("This class ru.pixeltyumen.air.utils.WebUpdate is singleton!");
            }
            _netStatus = new URLMonitor(new URLRequest(_updateAppUrl));
            _netStatus.start();
        }
        
        
        
        /**
         * Instance of class
         */
        static public function get instance ():WebUpdate {
            if (_instance == null) {
                _instance = new WebUpdate();
            }
            return _instance;
        }
        
        
        
        /**
         * Setter for URL of application file
         */
        public function set updateAppUrl (s:String):void {
            _updateAppUrl = s;
        }
        
        
        
        /**
         * Setter for URL of version text-file
         */
        public function set updateVersionUrl (s:String):void {
            _versionTextUrl = s;
        }
        
        
        
        /**
         *
         * @param    currentVersion
         * @param    mode
         */
        public function start (currentVersion:String, mode:Boolean = false):void {
            _mode = mode ? UPDATE_AND_EQUAL_ALERT : NEED_UPDATE;
            _currentVersion = currentVersion;
            
            if (!_netStatus.available && mode) {
                Alert.show(_errorTextString, _errorTitleString);
                return;
            }
            
            var loader:URLLoader = new URLLoader();
            loader.addEventListener(Event.COMPLETE, versionSignComplete);
            // add another error handlers here...
            loader.load(new URLRequest(_versionTextUrl));
        }
        
        
        
        /**
         * Complete handler of getting last version
         * @param    e
         */
        private function versionSignComplete (e:Event):void {
            _updateVersion = e.target.data as String;
            compareVersions();
            
            if (_state == EQUAL_VERSIONS && _mode == (UPDATE_AND_EQUAL_ALERT || DETAILED_ALERT)) {
                Alert.show(_equalTextString, _equalTitleString);
            }
            
            if (_state == NEWEST_VERSION && _mode == DETAILED_ALERT) {
                Alert.show(_newestTextString, _newestTitleString);
            }
            
            if (_state == NEED_UPDATE) {
                Alert.show(
                    _olderTextString,
                    _olderTitleString,
                    Alert.YES | Alert.NO,
                    null,
                    confirmUpdate,
                    null
                );
            }
        }
        
        
        
        /**
         * Compare versions
         */
        private function compareVersions ():void {
            _state = EQUAL_VERSIONS;
            var curArray:Array = _currentVersion.split(".");
            var updArray:Array = _updateVersion.split(".");
            try {
                for (var i:uint = 0; i < curArray.length; i++) {
                    if (curArray[i] && updArray[i] && uint(curArray[i]) < uint(updArray[i])) {
                        _state = NEED_UPDATE;
                        return;
                    }
                }
            } catch (e:Error) { errorHandler(null); }
            
        }
        
        
        
        /**
         *
         * @param    e
         */
        private function confirmUpdate (e:CloseEvent):void {
            if (e.detail == Alert.YES) {
                var stream:URLStream = new URLStream();
                //stream.addEventListener(ProgressEvent.PROGRESS, progressHandler);
                stream.addEventListener(Event.COMPLETE, completeHandler);
                stream.addEventListener(IOErrorEvent.IO_ERROR, errorHandler);
                // add other error handler
                stream.load(new URLRequest(_updateAppUrl));
            }
        }
        
        
        
        /**
         *
         * @param    e
         */
        private function progressHandler (e:ProgressEvent):void {
            "Downloading update " + e.bytesLoaded + " of " + e.bytesTotal + " bytes";
        }
        
        
        
        /**
         *
         * @param    e
         */
        private function completeHandler (e:Event):void {
            var urlStream:URLStream = e.target as URLStream;
            var fileStream:FileStream = new FileStream();
            var file:File = File.applicationStorageDirectory.resolvePath("newVersion.air");
            fileStream.open(file, FileMode.WRITE);
            var bytes:ByteArray = new ByteArray();
            urlStream.readBytes(bytes);
            fileStream.writeBytes(bytes);
            fileStream.close();
            var updater:Updater = new Updater();
            updater.update(file, _updateVersion);
        }
        
        
        
        /**
         *
         * @param    e
         */
        private function errorHandler (e:Event):void {
            Alert.show(_errorTextString, _errorTitleString);
        }
    }

}

Только не забудьте поставить нужные URL в переменные _versionTextUrl и _updateAppUrl.

Комментариев нет:

Отправить комментарий