ReactOS Sin Custom Revision Action (Revision ISO Creator)
Added: trunk/cis/ReactOS.CustomRevisionAction/
Added: trunk/cis/ReactOS.CustomRevisionAction/App.config
Added: trunk/cis/ReactOS.CustomRevisionAction/AssemblyInfo.cs
Added: trunk/cis/ReactOS.CustomRevisionAction/Default.build
Added: trunk/cis/ReactOS.CustomRevisionAction/Main.cs
Added: trunk/cis/ReactOS.CustomRevisionAction/RedirectableProcess.cs

Added: trunk/cis/ReactOS.CustomRevisionAction/App.config
--- trunk/cis/ReactOS.CustomRevisionAction/App.config	2005-11-23 17:25:55 UTC (rev 19490)
+++ trunk/cis/ReactOS.CustomRevisionAction/App.config	2005-11-23 17:31:28 UTC (rev 19491)
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8" ?> 
+<configuration>
+	<appSettings>
+		<add key="publishPath" value="c:\iso" />
+		<add key="smtpServer" value="localhost" />
+		<add key="errorEmail" value="mailbox@somewhere-on-the-net" />
+		<add key="makeParameters" value="" />
+		<add key="fastDisk" value="" />
+	</appSettings>
+</configuration>

Added: trunk/cis/ReactOS.CustomRevisionAction/AssemblyInfo.cs
--- trunk/cis/ReactOS.CustomRevisionAction/AssemblyInfo.cs	2005-11-23 17:25:55 UTC (rev 19490)
+++ trunk/cis/ReactOS.CustomRevisionAction/AssemblyInfo.cs	2005-11-23 17:31:28 UTC (rev 19491)
@@ -0,0 +1,14 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+[assembly: AssemblyTitle("ReactOS Sin Custom Revision Action")]
+[assembly: AssemblyDescription("ReactOS Sin Custom Revision Action")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("ReactOS Project")]
+[assembly: AssemblyProduct("React Operating System")]
+[assembly: AssemblyCopyright("Copyright 2005 ReactOS Project")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+[assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyDelaySign(false)]
+[assembly: AssemblyKeyFile("")]

Added: trunk/cis/ReactOS.CustomRevisionAction/Default.build
--- trunk/cis/ReactOS.CustomRevisionAction/Default.build	2005-11-23 17:25:55 UTC (rev 19490)
+++ trunk/cis/ReactOS.CustomRevisionAction/Default.build	2005-11-23 17:31:28 UTC (rev 19491)
@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+<project name="ReactOS.CustomRevisionAction" default="build">
+
+	<property name="output.dir" value="bin" />
+
+	<target name="build" description="Build component">
+		<mkdir dir="${output.dir}" />
+		<csc target="exe"
+			output="${output.dir}\ReactOS.CustomRevisionAction.exe"
+			optimize="true"
+			debug="true"
+			doc="${output.dir}\ReactOS.CustomRevisionAction.xml"
+			warninglevel="0">
+			<sources>
+				<include name="*.cs" />
+			</sources>
+		</csc>
+	</target>
+
+</project>

Added: trunk/cis/ReactOS.CustomRevisionAction/Main.cs
--- trunk/cis/ReactOS.CustomRevisionAction/Main.cs	2005-11-23 17:25:55 UTC (rev 19490)
+++ trunk/cis/ReactOS.CustomRevisionAction/Main.cs	2005-11-23 17:31:28 UTC (rev 19491)
@@ -0,0 +1,254 @@
+using System;
+using System.IO;
+using System.Diagnostics;
+using System.Configuration;
+using System.Web.Mail;
+
+namespace ReactOS.CustomRevisionAction
+{
+	public class MainClass
+	{
+		/// <summary>
+		/// Path to store published binaries at.
+		/// </summary>
+		private static string publishPath;
+
+		/// <summary>
+		/// Run the application.
+		/// </summary>
+		/// <param name="script">Script to run.</param>
+		/// <param name="args">Arguments to pass to script.</param>
+		/// <param name="workingDirectory">Working directory.</param>
+		/// <param name="standardOutput">Receives standard output.</param>
+		/// <param name="standardError">Receives standard error.</param>
+		/// <returns>
+		/// Exit code.
+		/// </returns>
+		private static int RunScript(string script,
+		                             string args,
+		                             string workingDirectory,
+		                             out string standardOutput,
+		                             out string standardError)
+		{
+			ProcessStartInfo scriptProcessStartInfo = new ProcessStartInfo(script,
+			                                                               args);
+			scriptProcessStartInfo.CreateNoWindow = true;
+			/*
+			 * All standard streams must be redirected.
+			 * Otherwise DuplicateHandle() will fail.
+			 */
+			scriptProcessStartInfo.RedirectStandardInput = true;
+			scriptProcessStartInfo.RedirectStandardError = true;
+			scriptProcessStartInfo.RedirectStandardOutput = true;
+			scriptProcessStartInfo.UseShellExecute = false;
+			scriptProcessStartInfo.WorkingDirectory = workingDirectory;
+			RedirectableProcess redirectableProcess = new RedirectableProcess(scriptProcessStartInfo);
+			standardOutput = redirectableProcess.ProcessOutput;
+			standardError = redirectableProcess.ProcessError;
+			return redirectableProcess.ExitCode;
+		}
+
+		/// <summary>
+		/// Retrieve value of configuration from configuration file.
+		/// </summary>
+		/// <param name="name">Name of configuration option.</param>
+		/// <param name="defaultValue">
+		/// Default value to be returned if the option does not exist.
+		/// </param>
+		/// <returns>
+		/// Value of configuration option or null if the option does not
+		/// exist and no default value is provided.
+		/// </returns>
+		private static string GetConfigurationOption(string name,
+		                                             string defaultValue)
+		{
+			if (ConfigurationSettings.AppSettings[name] != null)
+				return ConfigurationSettings.AppSettings[name];
+			else
+				return defaultValue;
+		}
+		
+		/// <summary>
+		/// Send an email.
+		/// </summary>
+		/// <param name="subject">Subject of the email.</param>
+		/// <param name="body">Content of the email.</param>
+		private static void SendErrorMail(string subject, string body)
+		{
+			try
+			{
+				string smtpServer = GetConfigurationOption("smtpServer", "localhost");
+				string toEmail = GetConfigurationOption("errorEmail", null);
+				if (toEmail == null)
+					return;
+				string fromEmail = GetConfigurationOption("fromEmail", null);
+				if (fromEmail == null)
+					fromEmail = toEmail;
+				MailMessage mm = new MailMessage();
+				mm.Priority = MailPriority.Normal;
+				mm.From = toEmail;
+				mm.To = toEmail;
+				mm.Subject = subject;
+				mm.Body += body;
+				mm.Body += "<br>";
+				mm.BodyFormat = MailFormat.Html;
+				SmtpMail.SmtpServer = smtpServer;
+				SmtpMail.Send(mm);
+			}
+			catch (Exception ex)
+			{
+				Console.Error.WriteLine(ex.Message);
+			}
+		}
+
+		/// <summary>
+		/// Fail with an error message.
+		/// </summary>
+		/// <param name="revision">Repository revision.</param>
+		/// <param name="text">Error message.</param>
+		private static void Fail(int revision,
+		                         string text)
+		{
+			Console.WriteLine(text);
+			Console.Error.WriteLine(text);
+			SendErrorMail(String.Format("[{0}] ReactOS Publish Error", revision), text);
+		}
+
+		/// <summary>
+		/// Fail with an error message.
+		/// </summary>
+		/// <param name="text">Error message.</param>
+		private static void Fail(string text)
+		{
+			Console.WriteLine(text);
+			Console.Error.WriteLine(text);
+			SendErrorMail("ReactOS Publish Error", text);
+		}
+
+		/// <summary>
+		/// Generate filename of distribution.
+		/// </summary>
+		/// <param name="branch">Branch.</param>
+		/// <param name="revision">Revision.</param>
+		private static string GetDistributionFilename(string branch,
+		                                              int revision)
+		{
+			return String.Format("ReactOS-{0}-r{1}.iso",
+			                     branch,
+			                     revision);
+		}
+
+		/// <summary>
+		/// Copy ISO to the destination.
+		/// </summary>
+		/// <param name="sourceFilename">Name of source ISO file to copy.</param>
+		/// <param name="branch">Branch.</param>
+		/// <param name="revision">Revision.</param>
+		/// <remarks>
+		/// Structure is <branch>\ReactOS-<branch>-r<revision>.iso.
+		/// </remarks>
+		private static void CopyISOToDestination(string sourceFilename,
+		                                         string branch,
+		                                         int revision)
+		{
+			string distributionFilename = GetDistributionFilename(branch,
+			                                                      revision);
+			string destinationDirectory = Path.Combine(publishPath,
+			                                           branch);
+			string destinationFilename = Path.Combine(destinationDirectory,
+			                                          distributionFilename);
+			if (!Directory.Exists(destinationDirectory))
+				Directory.CreateDirectory(destinationDirectory);
+			File.Copy(sourceFilename,
+			          destinationFilename);
+		}
+
+		/// <summary>
+		/// Publish a revision of ReactOS.
+		/// </summary>
+		/// <param name="text">Error message.</param>
+		private static int Publish(string branch,
+		                           int revision,
+		                           string workingDirectory)
+		{
+			string make = "mingw32-make";
+			string makeParameters = GetConfigurationOption("makeParameters", "");
+			string reactosDirectory = Path.Combine(workingDirectory,
+			                                       "reactos");
+			Console.WriteLine(String.Format("ReactOS directory is {0}",
+			                                reactosDirectory));
+			string standardOutput;
+			string standardError;
+			int exitCode = RunScript(make,
+			                         makeParameters + " bootcd",
+			                         reactosDirectory,
+			                         out standardOutput,
+			                         out standardError);
+			if (exitCode != 0)
+			{
+				Fail(revision,
+				     String.Format("make bootcd failed: (error: {0}) {1}",
+				                   standardError,
+				                   standardOutput));
+				return exitCode;
+			}
+
+			string sourceFilename = Path.Combine(reactosDirectory,
+			                                     "ReactOS.iso");
+			if (File.Exists(sourceFilename))
+				CopyISOToDestination(sourceFilename,
+				                     branch,
+				                     revision);
+			else
+			{
+				Fail(revision,
+				     "make bootcd produced no ReactOS.iso");
+				return exitCode;
+			}
+
+			return exitCode;
+		}
+
+		/// <summary>
+		/// Program entry point.
+		/// </summary>
+		/// <param name="args">Arguments from command line.</param>
+		/// <remarks>
+		/// If exit code is 0, then the commit was processed successfully.
+		/// If exit code is 1, then the commit was not processed successfully.
+		/// </remarks>
+		public static void Main(string[] args)
+		{
+			try
+			{
+				System.Environment.ExitCode = 1;
+
+				publishPath = ConfigurationSettings.AppSettings["publishPath"];
+				if (publishPath == null)
+				{
+					Fail("PublishPath option not set.");
+					return;
+				}
+
+				if (args.Length < 3)
+				{
+					Fail("Usage: ReactOS.CustomRevisionAction action branch revision");
+					return;
+				}
+
+				string action = args[0]; /* bootcd */
+				string branch = args[1];
+				int revision = Int32.Parse(args[2]);
+				
+				System.Environment.ExitCode = Publish(branch,
+				                                      revision,
+				                                      System.Environment.CurrentDirectory);
+			}
+			catch (Exception ex)
+			{
+				Fail(String.Format("Exception: {0}", ex));
+				System.Environment.ExitCode = 1;
+			}
+		}
+	}
+}

Added: trunk/cis/ReactOS.CustomRevisionAction/RedirectableProcess.cs
--- trunk/cis/ReactOS.CustomRevisionAction/RedirectableProcess.cs	2005-11-23 17:25:55 UTC (rev 19490)
+++ trunk/cis/ReactOS.CustomRevisionAction/RedirectableProcess.cs	2005-11-23 17:31:28 UTC (rev 19491)
@@ -0,0 +1,145 @@
+/*
+ * When using the ProcessStartInfo.RedirectStandardXxx properties there is a chance of
+ * the parent and child process blocking due to a race condition. This class handles the
+ * problem.
+ * http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemdiagnosticsprocessstartinfoclassredirectstandardoutputtopic.asp
+ */
+using System;
+using System.IO;
+using System.Threading;
+using System.Diagnostics;
+
+namespace ReactOS.CustomRevisionAction
+{
+	/// <summary>
+	/// Process that redirects standard output and standard error streams.
+	/// </summary>
+	public class RedirectableProcess
+	{
+		/// <summary>
+		/// Process.
+		/// </summary>
+		private Process process;
+
+		/// <summary>
+		/// Redirected standard error stream.
+		/// </summary>
+		private string processError;
+
+		/// <summary>
+		/// Redirected standard output stream.
+		/// </summary>
+		private string processOutput;
+
+		/// <summary>
+		/// Exit code.
+		/// </summary>
+		private int exitCode;
+
+		/// <summary>
+		/// Redirected standard error stream.
+		/// </summary>
+		public string ProcessError
+		{
+			get
+			{
+				return processError;
+			}
+		}
+
+		/// <summary>
+		/// Redirected standard output stream.
+		/// </summary>
+		public string ProcessOutput
+		{
+			get
+			{
+				return processOutput;
+			}
+		}
+
+		/// <summary>
+		/// Exit code.
+		/// </summary>
+		public int ExitCode
+		{
+			get
+			{
+				return exitCode;
+			}
+		}
+
+		/// <summary>
+		/// Run an excutable and redirect standard error and/or standard output safely.
+		/// </summary>
+		public RedirectableProcess(ProcessStartInfo processStartInfo)
+		{
+			Run(processStartInfo, null);
+		}
+
+		/// <summary>
+		/// Run an excutable and redirect standard error and/or standard output safely.
+		/// </summary>
+		public RedirectableProcess(ProcessStartInfo processStartInfo, string input)
+		{
+			Run(processStartInfo, input);
+		}
+		
+		private void Run(ProcessStartInfo processStartInfo, string input)
+		{
+			process = new Process();
+			process.StartInfo = processStartInfo;
+			process.Start();
+			if (processStartInfo.RedirectStandardInput && input != null)
+			{
+				process.StandardInput.AutoFlush = true;
+				process.StandardInput.WriteLine(input);
+			}
+			Thread readStandardError = null;
+			if (processStartInfo.RedirectStandardError)
+			{
+				readStandardError = new Thread(new ThreadStart(ReadStandardError));
+				readStandardError.Start();
+			}
+			Thread readStandardOutput = null;
+			if (processStartInfo.RedirectStandardOutput)
+			{
+				readStandardOutput = new Thread(new ThreadStart(ReadStandardOutput));
+				readStandardOutput.Start();
+			}
+			if (processStartInfo.RedirectStandardError)
+			{
+				readStandardError.Join();
+			}
+			if (processStartInfo.RedirectStandardOutput)
+			{
+				readStandardOutput.Join();
+			}
+			process.WaitForExit();
+			exitCode = process.ExitCode;
+			process = null;
+		}
+
+		/// <summary>
+		/// Read standard error thread entry-point.
+		/// </summary>		
+		private void ReadStandardError()
+		{
+			if (process != null)
+			{
+				processError = process.StandardError.ReadToEnd();
+			}
+		}
+
+		/// <summary>
+		/// Read standard output thread entry-point.
+		/// </summary>		
+		private void ReadStandardOutput()
+		{
+			if (process != null)
+			{
+				processOutput = process.StandardOutput.ReadToEnd();
+			}
+		}
+	}
+}