Автоматизация публикации FLA-файлов; вызывая Process.Start несколько раз

Проблема фона

Я создал процесс публикации в один клик для моих файлов проекта Flash (*.fla) с помощью программы на C#, которая выполняет следующие операции:

  • Обновляет статическую переменную "версия" в двух основных файлах проекта (по одному для каждого проекта FLA)
  • Изменяет встроенный файл шаблона JSFL (автоматизирует открытие / публикацию определенного FLA-файла) и записывает его на диск для использования в следующем шаге
  • Вызывает Flash Professional через Process.Start, передавая путь к flash.exe, а также имя файла JSFL в качестве аргумента, поэтому flash запускает файл JSFL и публикует проект
  • Публикует файлы проекта на моем веб-сервере (простое резервное копирование / копирование файлов через подключенный общий диск через VPN-туннель Win7<->WinServer2012)

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

Для каждого проекта он считывает файл AS основного документа в память в виде строки и использует регулярное выражение для обновления определенных статических переменных "версия", чтобы иметь текущую метку времени. Затем он перезаписывает файлы с обновленной версией. Назначение переменной version - это отображение во время выполнения в браузере, поэтому я уверен, что я тестирую самую последнюю скомпилированную версию проекта.

Flash Professional принимает имя файла JSFL в качестве аргумента командной строки, но не позволяет передавать дополнительные аргументы в этот файл JSFL, поэтому программа изменяет шаблон JSFL для включения соответствующих параметров и передает настроенный временный файл JSFL во Flash Professional через Process.Start.

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


проблема

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

Flash - это приложение только для одного экземпляра, поэтому при запуске Process.Start Flash Professional запускается, если он еще не запущен, и запускается сценарий JSFL, либо он запускает сценарий JSFL в существующем экземпляре Flash Professional.

Первая проблема - после вызова Process.Start, я не могу вызвать waitForExit для завершения задачи, потому что Flash остается открытым. Если Flash уже открыт, waitForExit на самом деле возвращается довольно быстро, потому что второй экземпляр Flash.exe закроется после пересылки команды первичному экземпляру. Приложения, предназначенные только для одного экземпляра, на самом деле не препятствуют запуску второго процесса, они просто быстро убивают второй процесс, когда он обнаруживает, что один уже запущен, и передает ему команду. По этой причине я не могу просто ждать завершения процесса, так как Flash может открываться или не открываться.

Предполагая, что я совсем не жду, мое приложение будет довольно быстро вызывать Process.Start дважды, по одному разу для каждого проекта, передавая уникальное имя файла JSFL-скрипта для каждого из них. Проблема в том, что первый вызов, похоже, отбрасывается. Я не уверен, коренится ли это поведение в ОС Windows или в приложении Flash Professional. Это происходит, когда Flash еще не открыт. Первоначальный вызов Process.Start и соответствующие параметры должны быть тем, что активирует Flash, поэтому я ожидал, что это будет сделано, однако, когда Flash запускается, когда он, наконец, показывает главное окно, запускается только второй сценарий.

Интересно, что, если Flash уже запущен, оба сценария запускаются, несмотря на их быструю активацию (я вижу, что оба документа открыты в IDE), но одновременная публикация фактически приводит к сбою Flash (основное окно исчезает, и процесс завершается внезапно без каких-либо ошибок.).

Поэтому мне нужен способ координировать выдачу этих команд Process.Start. К счастью, метод "публикации" JSFL является синхронным, и JSFL способен выполнять команды командной строки, поэтому, возможно, когда метод публикации вернется, я могу просто вызвать некоторый внешний EXE-файл, чтобы он функционировал как механизм координации, чтобы определить, когда каждый сценарий завершил свою работу до этого. выполнение следующего? Есть ли у кого-нибудь опыт такого рода межпроцессного взаимодействия, который мог бы мне помочь?

TL; DR

Мне нужно знать, как создать простой исполняемый файл так, чтобы при вызове из командной строки он отправлял сообщение определенному внешнему процессу, указывающее, что операция завершена. По сути, JSFL-скрипт, работающий во Flash Professional, должен вызывать exe-файл через "недокументированный" метод FLfile.runCommandLine после завершения публикации файла, и этот exe-файл должен затем уведомить мою программу автоматизации, чтобы он знал, что Flash завершил публикацию файла и готов запустить другой JSFL-скрипт для публикации следующего файла.

1 ответ

Я решил эту проблему с помощью следующего проекта: http://www.codeproject.com/Articles/17606/NET-Interprocess-Communication

Он предлагает простую реализацию IPC с нулевой конфигурацией в виде библиотеки классов.

Я добавил его в свою программу автоматизации и разрешил запуск исполняемого файла с аргументами, указывающими, что он должен сигнализировать о главном экземпляре и завершить работу. Основная логика просто проверяет: Environment.GetCommandLineArgs() для флагов, указывающих, что он должен отправить сообщение IPC и закрыть вместо фактического отображения основной формы.

Вот полная реализация системы сигнализации основной программы:

static class Program
{
    private static readonly string MUTEX_AND_CHANNEL_NAME = "FlashPublishingAutomation";
    private static bool acquired_app_lock = false;
    private static Mutex app_lock;

    private static XDListener listener;
    public static ManualResetEvent publishCompleteSignal = new ManualResetEvent( true );

    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        string[] args = Environment.GetCommandLineArgs();
        if ((args.Length > 1) && (args[1] == "-publishcomplete"))
        {
            XDBroadcast.SendToChannel( MUTEX_AND_CHANNEL_NAME, "publishcomplete" );
            Application.Exit();
            return;
        }
        else
        {
            bool createdNew = false;
            MutexSecurity security = new MutexSecurity();
            MutexAccessRule rule = new MutexAccessRule( "Users", MutexRights.Synchronize | MutexRights.Modify | MutexRights.ReadPermissions, AccessControlType.Allow );
            security.AddAccessRule( rule );
            app_lock = new Mutex( false, "Global\\" + MUTEX_AND_CHANNEL_NAME, out createdNew, security ); //Name must start with "Global\\" in order to be a system-wide mutex for all logged on usesr.
            acquired_app_lock = app_lock.WaitOne( TimeSpan.Zero, true );

            if (!acquired_app_lock)
            {
                MessageBox.Show( "An instance of FlashPublishingAutomation is already running.\r\nOnly one instance is allowed." );
            }
            else
            {
                listener = new XDListener();
                listener.RegisterChannel( MUTEX_AND_CHANNEL_NAME );
                listener.MessageReceived += listener_MessageReceived;
                Application.ApplicationExit += Application_ApplicationExit;
                Application.Run(new Form1());
            }

            if (acquired_app_lock)
                app_lock.ReleaseMutex();
            app_lock.Close();
        }
    }

    static void listener_MessageReceived(object sender, XDMessageEventArgs e)
    {
        switch (e.DataGram.Message)
        {
            case "publishcomplete":
                publishCompleteSignal.Set();
                break;
        }
    }

    static void Application_ApplicationExit(object sender, EventArgs e)
    {
        listener.MessageReceived -= listener_MessageReceived;
        listener.UnRegisterChannel( MUTEX_AND_CHANNEL_NAME );
    }
}

И методы "публикации", которые вызываются при нажатии кнопки для проекта (вместе с методом "fillTemplate":

private static readonly string FLASH_PATH = @"C:\Program Files (x86)\Adobe\Adobe Flash CS6\Flash.exe";
public void publish( string fla_directory, string fla_filename, string jsfl_filename )
{
    Program.publishCompleteSignal.Reset();
    string template = fillTemplate( fla_directory, fla_filename );
    string curdir = Environment.CurrentDirectory;
    string tempJSFLfilepath = Path.Combine( curdir, jsfl_filename );
    File.WriteAllText( tempJSFLfilepath, template );
    Process p = Process.Start( FLASH_PATH, tempJSFLfilepath );
    Program.publishCompleteSignal.WaitOne( 30000 ); //wait for signal from JSFL runnCommandLine; timeout after 30 seconds; may want to increase this value if Flash needs time to startup or files take a long time to publish
}

private string fillTemplate( string fla_directory, string fla_filename )
{
    string fileuri = "file:///" + Path.Combine( fla_directory, fla_filename ).Replace( '\\','/' ); //convert path to file URI
    return EmbeddedResources.OpenAndPublishJSFLTemplate
        .Replace( "FLAFILEPATH", HttpUtility.JavaScriptStringEncode( fileuri ) )
        .Replace("FLAFILENAME", HttpUtility.JavaScriptStringEncode( fla_filename ) )
        .Replace("COMPLETECOMMAND", HttpUtility.JavaScriptStringEncode( "\"" + Application.ExecutablePath + "\"" + " -publishcomplete" ));
}

Кроме того, вот шаблон JSFL, который заполняет программа автоматизации перед выполнением его во Flash. Он встроен в виде строки в EmbeddedResources.OpenAndPublishJSFLTemplate`. Приложение C# заменяет строки FLAFILENAME, FLAFILEPATH и COMPLETECOMMAND на целевое имя файла FLA, FLA uri (из файла формы:///path_to_FLA) и, наконец, путь к самому приложению C#, как реализовано выше (плюс "-publishcomplete") выключатель). Приложение C# получает свой собственный путь через System.Windows.Forms.Application.ExecutablePath. После заполнения этого шаблона он записывается на диск в виде файла JSFL и передается в качестве аргумента Flash Professional (flash.exe) через Process.Start. Как только файл JSFL публикует FLA, он запускает новый экземпляр программы автоматизации с флагом "-publishcomplete", который сигнализирует основному экземпляру программы автоматизации о запуске события ручного сброса.

Таким образом, программа автоматизации сбрасывает событие перед вызовом Flash, затем ожидает сигнала после завершения публикации Flash, прежде чем пытаться опубликовать следующий файл.

var myDocument = null;
var wasOpen = false;
var isOpen = false;
var openDocs = fl.documents;
var filename = "FLAFILENAME"; //template parameter: the filename (name only, without the path) of the FLA file to publish
var filepath = "FLAFILEPATH"; //template parameter: the URI (beginning with "file:///") of the FLA file to publish
for(var i=0;i < openDocs.length; i++)
{
    myDocument = openDocs[i];
    if (myDocument.name.toLowerCase() == filename.toLowerCase())
    {
        wasOpen = true;
        isOpen = true;
        break;
    }
}
if (!wasOpen)
{
    myDocument = null;
    fl.openDocument( filepath );
    openDocs = fl.documents;
    for(var i=0;i < openDocs.length; i++)
    {
        myDocument = openDocs[i];
        if (myDocument.name.toLowerCase() == filename.toLowerCase())
        {
            isOpen = true;
            break;
        }
    }
}
if (isOpen && (myDocument != null))
{
    //Publish the document
    myDocument.publish(); //this method is synchronous, so it won't return until the publish operation has fully completed

    //Signal the automation program that publishing has completed (COMPLETECOMMAND should be 
    FLfile.runCommandLine("COMPLETECOMMAND"); //tempate parameter: the automation program's executable path plus the "-publishcomplete" argument
}
else
    alert( "Publishing of " + filename + " failed.  File was not open and failed to open." );

Я на самом деле очень впечатлен тем, что я создал здесь. Он обеспечивает сквозную публикацию (управление версиями, компиляцию, резервное копирование и развертывание на веб-сервере) двух очень больших (десятки тысяч строк; сотни классов) проектов FLA одним нажатием кнопки, и все это завершается. менее чем за 10 секунд.

Вероятно, это могло бы выполняться еще быстрее, если бы шаблон JSFL был упрощен, чтобы просто вызвать тихий метод публикации FLA, который даже не открывает файл и позволяет указать используемый профиль публикации: fl.publishDocument( flaURI [, publishProfile] ),

Другие вопросы по тегам