自动发布FLA文件;多次调用Process.Start

时间:2014-05-07 18:27:20

标签: flash automation publishing inter-process-communicat jsfl

问题背景

我创造了一个" One-Click"我的Flash(* .fla)项目文件的发布过程使用C#程序执行以下操作:

  • 更新静态"版本"两个主项目文件中的变量(每个FLA项目一个)
  • 更改嵌入式JSFL模板文件(自动打开/发布特定FLA文件)并将其写入磁盘以便在下一步中使用
  • 通过Process.Start调用Flash Professional,将路径传递给flash.exe以及JSFL文件名作为参数,因此flash运行JSFL文件并发布项目
  • 将项目文件发布到我的Web服务器(通过Win7< - > WinServer2012 VPN隧道上的映射共享驱动器进行简单备份/文件复制)

UI有三个按钮,一个用于发布第一个项目,一个用于发布第二个项目,另一个用于发布两个项目。第二个项目取决于第一个项目,因此,如果"发布两个"单击按钮,它应该在发布第二个文件之前完全发布第一个文件。

对于每个项目,它将主文档类AS文件作为字符串读入内存,并使用正则表达式更新特定的静态"版本"变量具有当前时间戳。然后它使用更新的版本重写文件。 "版本"的目的变量将在运行时在浏览器中显示,因此我确定我正在测试最近编译的项目版本。

Flash Professional接受JSFL文件名作为命令行参数,但不允许将其他参数传递给该JSFL文件,因此程序会更改JSFL模板以包含正确的参数并将自定义的临时JSFL文件传递给Flash Professional通过Process.Start。

通过另一个通用功能发布到Web,这些功能允许我指定源路径和目标路径列表(每个文件的可选时间戳备份),自动备份和将特定发布文件复制到我的Web服务器。


问题

首先,我要提一下,当我只发布一个项目文件时,一切正常,而我试图解决的问题是时间或事件信令之一。

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

我需要知道如何构建一个简单的可执行文件,以便在从命令行调用时,它会向特定的外部进程发送一条消息,指示操作已完成。基本上,在Flash Professional中运行的JSFL脚本必须在文件完成发布后通过"undocumented" FLfile.runCommandLine方法调用exe,然后exe必须通知我的自动化程序,因此它知道Flash已完成发布文件,并准备运行另一个JSFL脚本来发布下一个文件。

1 个答案:

答案 0 :(得分:4)

我通过以下项目解决了这个问题: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" ));
}

此外,这是自动化程序在Flash中执行之前填写的JSFL模板。它嵌入在EmbeddedResources.OpenAndPublishJSFLTemplate`下的字符串中。 C#app将FLAFILENAME,FLAFILEPATH和COMPLETECOMMAND字符串替换为目标FLA文件名,FLA uri(格式为file:/// path_to_FLA),最后是上面实现的C#app本身的路径(加上“-publishcomplete”) “转换”。 C#app通过System.Windows.Forms.Application.ExecutablePath获取自己的路径。填充此模板后,它将作为JSFL文件写入磁盘,并通过Process.Start作为参数传递给Flash Professional(flash.exe)。一旦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项目的端到端发布(版本控制,编译,备份和部署到Web服务器),这一切都完成了在10秒内。

如果将JSFL模板简化为仅调用甚至不打开文件的静默FLA发布方法,并且允许您指定要使用的发布配置文件:fl.publishDocument( flaURI [, publishProfile] ),这可能会更快地运行。 / p>