понедельник, 21 февраля 2011 г.

Форматирование даты/времени (ActionScript)

Преамбула:
От так вот неожиданно случилось, что в приложении стало необходимо выводить дату в формате диктуемым языком интерфейса (почти как в случае с настройками локали). Проблема скажем тривиальная. Есть наверняка куча всяких форматтеров, из которых на скоряк попался флексовый (была идея портировать явовский, но там тоже как то все мутно). Но т.к. приложение на чистом ас3, то очень не хотелось таки цеплять флексовые либы и прочее, тем более флексовый форматтер юзает еще какие то классы из либ. Был бы он "сам в себе" взял бы не раздумывая, но увы и ах...

Проблема:
Применять разное форматирование даты/времени в зависимости от языковых настроек интерфейса.

Решение:
По сути требовалось форматирование даты в нашем и буржуйском формате (например: 21.02.2011 или 02/21/2011) и время в 24-часовом формате для всех языков оставить (пока).
Долго не мудрствуя родился ниже приведенный класс с несложной, но готовой к расширению, функциональностью.

public class DateFormatter
{
  private static const MAP_TOKENS:Object = new Object();
  
  private static const DATE:Date = new Date();

  /// метод форматирования №1
  public static function formatDate(date:Date, pattern:String):String
  {
    DATE.setTime(date.getTime());
    return format(DATE, pattern);
  }
  
  /// метод форматирования №2
  public static function formatTimestamp(timestamp:Number, pattern:String):String
  {
    DATE.setTime(timestamp);
    return format(DATE, pattern);
  }
  
  private static function format(date:Date, pattern:String):String
  {
    DATE.setTime(date.getTime());
    
    var tokens:Array = [];
    
    for (var token:String in MAP_TOKENS) 
    {
      if (pattern.indexOf(token) != -1)
         tokens.push(token);
    }
    
    
    var retval:String = pattern;
    for each (token in tokens) 
    {
      retval = retval.replace(token, (MAP_TOKENS[token] as Function).apply());
    }
    
    return retval;
  }
  
  private static function formatSeconds():String
  {
    return leadZero(DATE.getSeconds());
  }
  
  private static function formatMinutes():String
  {
    return leadZero(DATE.getMinutes());
  }
  
  private static function formatHours():String
  {
    return leadZero(DATE.getHours());
  }
  
  private static function formatFullYear():String
  {
    return leadZero(DATE.getFullYear());
  }
  
  private static function formatDay():String
  {
    return leadZero(DATE.getDate());
  }
  
  private static function formatMonth():String
  {
    var month:int = DATE.getMonth() + 1;
    return leadZero(month);
  }
  
  private static function leadZero(value:int):String
  {
    return (value < 10 ? "0" : "") + value.toString();
  }
  
  
  // блок статической инициализации
  {
    MAP_TOKENS["MM"] = formatMonth;
    MAP_TOKENS["DD"] = formatDay;
    MAP_TOKENS["YYYY"] = formatFullYear;
    MAP_TOKENS["hh"] = formatHours;
    MAP_TOKENS["mm"] = formatMinutes;
    MAP_TOKENS["ss"] = formatMinutes; 
  }
}
Пример использования:
var sysdate:Date = new Date();
trace(DateFormatter.formatDate(sysdate, "DD.MM.YYYY"); // 21.02.2011
trace(DateFormatter.formatDate(sysdate, "MM/DD/YYYY"); // 02/21/2011
trace(DateFormatter.formatDate(sysdate, "MM/DD/YYYY hh:mm:ss"); // 02/21/2011 14:26:01
trace(DateFormatter.formatDate("hh:mm:ss"); // 14:26:01
// или так можно, но только осторожно
trace(DateFormatter.formatDate("Today is DD.MM.YYYY"); // Today is 21.02.2011
Необходимые для конкретного языка шаблоны форматирования хранятся в соответствующих файлах. Вытягивать и хранить это все можно способом описанным в этом посте. Например:

   
 

   
    

   
 

понедельник, 7 февраля 2011 г.

Загрузка параметров приложения в зависимости от языковых настроек (ActionScript)

Преамбула:
Для мультиязыкового приложения наверняка многие пользовались классом fl.lang.Locale с помощью которого в приложение подгружаются строковые ресурсы на нужном языке. Возникла необходимость аналогично строковым, сохранять и в последствии подгружать койкакие настройки приложения в зависимости от языка, например: имя дефолтного шрифта или имя метки кадра для фона главного экрана.

Проблема:
Использование различных настроек приложения в зависимости от языка.

Решение:
За основу взят все тот же класс fl.lang.Locale, т.к. обладает всем необходимым функционалом. Более того его можно было использовать для решения данной проблемы, но я отказался от него по 2-м причинам: 1. в проекте уже используется этот класс для подгрузки строковых, 2. модель ключ-значение превращает xml-"конфиг" в одну сплошную кучу.
И так, немного поработав напильником, появился класс (не претендующий на оригинальность или что то еще в этом духе) LocaleSettings (заранее прошу прощения за мусор в комментариях, остались от Locale).

Пример xml-я который кушает этот класс:


      
         Arial
       

      
         bg_en
       

      
         BTN_ACCEPT;BTN_OK;BTN_CANCEL
       

      
         BTN_BUY;BTN_CANCEL
       


Пример использования:
const DEFAULT_LANG:String = "ru";

LocaleSettings.setDefaultLang(DEFAULT_LANG);
LocaleSettings.addXMLPath(DEFAULT_LANG, "settings/set_" + DEFAULT_LANG + ".xml"); // связываем язык с путем xml-файла

var language:String = (loaderInfo.parameters.lang ? loaderInfo.parameters.lang : DEFAULT_LANG);

LocaleSettings.addXMLPath(language, "settings/set_" + language + ".xml");
LocaleSettings.loadSettingsXML(language, languageXMLLoaded); // запускаем загрузку файла с настройками

...

private function languageXMLLoaded(success:Boolean):void
{
   if (LocaleSettings.checkXMLStatus())
   {
      // пользуем загруженные настройки
      mainScreen.setBg(LocaleSettings.loadSetting("client", "bg_frame_label");
   }
}

Примечания:
  1. Файл с настройкам для языка по умолчанию 100% должен быть (на то он и по умолчанию)
  2. В случае если для заданного языка не нашлось настоечного файла (как по причине его фактического отсутствия так и в случае ошибки в именовании языкового кода), будет загружен файл для языка по умолчанию.

четверг, 3 февраля 2011 г.

Трансляция видео mjpeg-потока (ActionScript, MJPEG)

Преамбула:
Наклевывался как то один заказик от охранной конторы. Типа забубенить плеер для показа видео с камер наблюдения с возможностью просмотра видео как в режиме он-лайн, так и в офф-лайн. В офф-лайн было все понятно, просто гнать с сервера записанное видео, проблема встала с режимом он-лайн трансляции. У заказчика использовались ip-камеры, которые отдают видео в виде mjpeg-потока и решение проблемы трансляции mjpeg-потока я таки нарыл.

Проблема:
Трансляция mjpeg-потока в flash-приложении.

Решение:
Помощники: flasher.ru, codeproject.com
package  
{
   import flash.display.*;
   import flash.events.Event;
   import flash.events.ProgressEvent;
   import flash.net.*;
   import flash.utils.*;

   public class VideoMJPEG extends Sprite
   {
      private var _stream:URLStream = new URLStream();
      private var _loader:Loader = new Loader()
      private var _buffer:ByteArray;
      private var _write:Boolean = false;   
      private var _markerDetection:Boolean = false;   
      private var _request:URLRequest = new URLRequest("http://146.176.65.10/axis-cgi/mjpg/video.cgi");

      public function VideoMJPEG()
      {
         mouseEnabled = false;
         mouseChildren = false;
         addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
      }
      
      private function onAddedToStage(e:Event):void 
      {
         removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
         
         addChild(_loader);
         
         _stream.load(_request);
         _stream.addEventListener(ProgressEvent.PROGRESS, onProgress);
      }
      
      private function onProgress(event:ProgressEvent):void
      {
         var byte:int;

         // Если есть доступные байты - обработать их 
         while(_stream.bytesAvailable > 0)
         {
            byte = _stream.readUnsignedByte();
            if(_write) _buffer.writeByte(byte);
            if(_markerDetection)
            {
               switch(byte)
               {
                  case 0xD8:
                     _write = true;
                     _buffer = new ByteArray();
                     _buffer.writeByte(0xFF);
                     _buffer.writeByte(0xD8);
                  break;
                  case 0xD9:
                     _write = false;
                     _buffer.writeByte(0xD9);
                     _loader.loadBytes(_buffer);
                  break;
               }
 
            }
            _markerDetection = (byte == 0xFF);
         } 
      }
   }
}

Прим.: используемый в примере url реальный, можно прям в браузере видео-поток посмотреть.

UPD: Был как то случай с получением mjpeg-потока с камеры которая требовала базовую авторизацию, как это сделать описано в посте Доступ к ресурсам защищенным Basic authentication.

среда, 2 февраля 2011 г.

Сохранение номера ревизии в приложении (ActionScript, SVN)

Ниже описанный подход применён в контексте ActionScript-проекта находящемуся под контролем SVN. "Не ActionScript-ом единым жив программист", это подход может пригодится в других проектах с другими CSV-системами.

Проблема:
При достаточно частых обновлениях приложения в сети, хочется визуально видеть какая версия флешки перед тобой, т.е. получить подобную картинку:



Решение:
  1. Определить место, где будет сохранятся номер ревизии, например отдельный класс с настройками приложения или одно из полей уже существующего класса
    Пример класса для первого варианта:
    public class AppOption 
    {
       public static const REVISION:String = "@revision@";
    }
    
  2. Описать ant-овский task в build.xml
    <target name="buildClient">
       <!-- копируем оригинал файла где будет производится замена строки -->
       <copy file="${src}/AppOption.as" tofile="${projectDir}/AppOption.as" overwrite="true" verbose="false" />
    
       <!-- впечатываем номер ревизии по-вкусу, в данном случае последней закомиченой-->
       <property name="revision" value="COMMITTED"/>
       <exec executable="svn" outputproperty="svnlog.out">   
          <arg line="log -r ${revision} -q"/>
       </exec>
       
       <taskdef resource="net/sf/antcontrib/antcontrib.properties"/>
       <propertyregex property="revision.number" input="${svnlog.out}" select="\2">
          <regexp pattern="(r)([0-9]*)"/>
       </propertyregex>
       <replace file="${src}/AppOption.as" token="@revision@" value="${revision.number}"/>
    
    <!-- компилируем клиента -->
    
       <!-- перемещаем оригинал файла на родину -->
       <move file="${projectDir}/AppOption.as" tofile="${src}/AppOption.as" overwrite="true" verbose="false"/>
    </target>
    
  3. И наконец использовать номер ревизии
    public class Main extends Sprite 
    {
       private var _contextMenu:ContextMenu;
    
       public Main()
       {
          addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
       }
       
       private function onAddedToStage(e:Event):void
       {
          removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
          _contextMenu = new ContextMenu();
          var menuItem:ContextMenuItem = new ContextMenuItem("Version " + AppOption.REVISION);
          _contextMenu.customItems.push(menuItem);
          contextMenu = _contextMenu;
          
       }
    }
    
Номер ревизии можно использовать не только для визуального контроля версии приложения, но и в качестве антикеша ("http://anysite.com/logo.png?" + AppOption.REVISION) при подгрузке внешних ресурсов, которые могут быть изменены при публикации новой версии приложения.
P.S.: Очень полезно про автоматизацию с испольщованием ant-а см. на http://anykeytocreate.blogspot.com/2011/08/1-ant-flash-faq.html

вторник, 1 февраля 2011 г.

Отправка сообщений в Skype через консоль

Лень – двигатель прогресса.

Есть такой техпроцесс:

  • скомпилить проект
  • залить скомпиленую флешку и подгружаемые в неё ресурсы на сервера
  • оповестить через Skype всех заинтересованных пряников, что мол "приложение выложено на кудыкину гору и гору где рак вот вот свистнет" 

Исполняет все это, кроме последнего пункта, наш дорогой и все еще горячо любимый ant.

Время занимаемое этим процессом колеблется от 5 до 20 минут (в зависимости от количества серверов куда выкладывается контент и того насколько давно обновлялся этот самый контент). В общем в один прекрасный солнечный денек начались сильные ломки по поводу последней стадии процесса, а именно, оповещения об окончании выкладки. Немного поискав в интернетах, я не нашел подходящего решения как это можно было бы заделать. Но нарыв в сети небольшой мешочек нужных библиотек, заваял простенькое консольное java-приложение.

Обитает оно на гуглокоде.
Пользоваться можно как отдельно в консоли, так и в качестве антовского task-а.

Удаление всех дочерних DisplayObject-ов в DisplayObjectContainer-е (ActionScript)

function removeAllChilds(container:DisplayObjectContainer):void
{
   while (container.numChildren)
   {
      container.removeChildAt(0);
   }
}

Для того чтобы удаляемые объекты не засирали память, не оставалось покинутых листенеров и запущенных таймеров, а так же другого мусора, рекомендую во всех классах расширяющих DisplayObject реализовать следующий интерфейс:
public interface IDisposable 
{
   function dispose():void;
}

В этом единственном методе можно заключить блок кода который бы делал предсмертные операции по освобождению занятых ресурсов.
Если вы не намерены повторно использовать удаляемые объекты то можно воспользоватся немного модифицированным циклом очистки.
function removeAllChilds(container:DisplayObjectContainer):void
{
   var removableObject:DisplayObject;
   while (container.numChildren)
   {
      removableObject = container.removeChildAt(0);
      if (removableObject is IDisposable)
         (removableObject as IDisposable).dispose();
   }
}

Получение ссылки на класс объекта по его экземпляру (ActionScript)

Получить ссылку на класс можно одним из способов описанных ниже.


  1. function getClassRef(instance:Object):Class
     {
      return getDefinitionByName(getQualifiedClassName(instance)) as Class;
     }
    

  2. function getClassRef(instance:Object):Class
     {
      return Object(instance).constructor as Class;
     }
    
Второй вариант хоть и не документирован адобами, но имеет право на жизнь (уж не знаю на долгую ли, по крайней мере в последнем флешплеере она все еще фурычит).

Теперь по производительности...
Вариант №2 примерно в 4 раза быстрее (проверено на 100'000 итерациях), да и выглядит как то краше и без лишних вызовов.

Эту возможность я использовал в проекте в месте, где в зависимости от поданного экземпляра объекта нужно произвести те или иные действия, пример:
function polymorphAction(instance:Object):void
{
 var clazz:Class = Object(instance).constructor as Class;
 switch (clazz)
 {
  case Friend:
   trace("друг");
   break;
   
  case Enemy:
   trace("враг");
   break;
  default:
   trace("а так");
 }
}

И так, поехали!

Дорогой читатель!
Как водится/обычно/часто/иногда, первое сообщение являет собой текст аля "Здравствуйте! Меня зовут Эммануил Гидэоныч, я анонимный алкоголик. (жидкие аплодисменты, редкие томные вздохи в зале)". Не отступлю и я от этого обычая.

По стечению маловероятных обстоятельств, я - программист, да да это тот человек который руководит, хоть и косвенно, прибором который  намагничивает быстро вращающиеся диски.
В этом мне помогают товарищи: as3, java, action script, flash, sql, pl/sql, postgresql db, oracle db, oracle forms, delphi, бубен, вера в чистое-доброе-мягкое and more.

Частью адреса этого блога (profdiletant...) хочу признать, что отношу себя к легиону профессиональных дилетантов (неплохое мне кажется определеньице).
В настоящий момент тружусь на ниве ActionScript 3 программирования. После НГ решил освежить в себе знания по такой мегаштуке как  Java.

Данный блогнот завел исключительно в целях сохранения битов информации, которые будут полезны как писателю этих строк (т.е. для меня), так, я надеюсь, и для читателя (т.е. для тебя) этих же строк. Писать буду как на связанные с намагничиванием быстро вращающихся дисков, так и не связанные с этим темы.

За русский/орфографию/пунктуацию/стилистику сильно не бранить, ибо и во ибо, у меня не твердая 4ка по русскому ;)

– До скорых встреч! - сказал он, всматриваясь через яркий свет софитов, в черную пустоту не видя адресатов.