54c43198de240879046be144648d80b5e96f0545
[reactos.git] / cis / ReactOS.CustomRevisionAction / Main.cs
1 using System;
2 using System.IO;
3 using System.Diagnostics;
4 using System.Configuration;
5 using System.Web.Mail;
6
7 namespace ReactOS.CustomRevisionAction
8 {
9 public class MainClass
10 {
11 /// <summary>
12 /// Path to store published binaries at.
13 /// </summary>
14 private static string publishPath;
15
16 /// <summary>
17 /// Whether or not to publish ISOs to a remote destination via FTP.
18 /// </summary>
19 private static bool PublishToRemoteFtpLocation
20 {
21 get
22 {
23 return publishPath.StartsWith("ftp://");
24 }
25 }
26
27 /// <summary>
28 /// Run the application.
29 /// </summary>
30 /// <param name="script">Script to run.</param>
31 /// <param name="args">Arguments to pass to script.</param>
32 /// <param name="workingDirectory">Working directory.</param>
33 /// <param name="standardOutput">Receives standard output.</param>
34 /// <param name="standardError">Receives standard error.</param>
35 /// <returns>
36 /// Exit code.
37 /// </returns>
38 private static int RunScript(string script,
39 string args,
40 string workingDirectory,
41 out string standardOutput,
42 out string standardError)
43 {
44 ProcessStartInfo scriptProcessStartInfo = new ProcessStartInfo(script,
45 args);
46 scriptProcessStartInfo.CreateNoWindow = true;
47 /*
48 * All standard streams must be redirected.
49 * Otherwise DuplicateHandle() will fail.
50 */
51 scriptProcessStartInfo.RedirectStandardInput = true;
52 scriptProcessStartInfo.RedirectStandardError = true;
53 scriptProcessStartInfo.RedirectStandardOutput = true;
54 scriptProcessStartInfo.UseShellExecute = false;
55 scriptProcessStartInfo.WorkingDirectory = workingDirectory;
56 RedirectableProcess redirectableProcess = new RedirectableProcess(scriptProcessStartInfo);
57 standardOutput = redirectableProcess.ProcessOutput;
58 standardError = redirectableProcess.ProcessError;
59 return redirectableProcess.ExitCode;
60 }
61
62 /// <summary>
63 /// Retrieve value of configuration from configuration file.
64 /// </summary>
65 /// <param name="name">Name of configuration option.</param>
66 /// <param name="defaultValue">
67 /// Default value to be returned if the option does not exist.
68 /// </param>
69 /// <returns>
70 /// Value of configuration option or null if the option does not
71 /// exist and no default value is provided.
72 /// </returns>
73 private static string GetConfigurationOption(string name,
74 string defaultValue)
75 {
76 if (ConfigurationSettings.AppSettings[name] != null)
77 return ConfigurationSettings.AppSettings[name];
78 else
79 return defaultValue;
80 }
81
82 /// <summary>
83 /// Send an email.
84 /// </summary>
85 /// <param name="subject">Subject of the email.</param>
86 /// <param name="body">Content of the email.</param>
87 private static void SendErrorMail(string subject, string body)
88 {
89 try
90 {
91 string smtpServer = GetConfigurationOption("smtpServer", "localhost");
92 string toEmail = GetConfigurationOption("errorEmail", null);
93 if (toEmail == null)
94 return;
95 string fromEmail = GetConfigurationOption("fromEmail", null);
96 if (fromEmail == null)
97 fromEmail = toEmail;
98 MailMessage mm = new MailMessage();
99 mm.Priority = MailPriority.Normal;
100 mm.From = toEmail;
101 mm.To = toEmail;
102 mm.Subject = subject;
103 mm.Body += body;
104 mm.Body += "<br>";
105 mm.BodyFormat = MailFormat.Html;
106 SmtpMail.SmtpServer = smtpServer;
107 SmtpMail.Send(mm);
108 }
109 catch (Exception ex)
110 {
111 Console.Error.WriteLine(ex.Message);
112 }
113 }
114
115 /// <summary>
116 /// Fail with an error message.
117 /// </summary>
118 /// <param name="revision">Repository revision.</param>
119 /// <param name="text">Error message.</param>
120 private static void Fail(int revision,
121 string text)
122 {
123 Console.WriteLine(text);
124 Console.Error.WriteLine(text);
125 SendErrorMail(String.Format("[{0}] ReactOS Publish Error", revision), text);
126 }
127
128 /// <summary>
129 /// Fail with an error message.
130 /// </summary>
131 /// <param name="text">Error message.</param>
132 private static void Fail(string text)
133 {
134 Console.WriteLine(text);
135 Console.Error.WriteLine(text);
136 SendErrorMail("ReactOS Publish Error", text);
137 }
138
139 /// <summary>
140 /// Generate filename of distribution.
141 /// </summary>
142 /// <param name="branch">Branch.</param>
143 /// <param name="revision">Revision.</param>
144 private static string GetDistributionFilename(string branch,
145 int revision)
146 {
147 return String.Format("ReactOS-{0}-r{1}.iso",
148 branch,
149 revision);
150 }
151
152 private static void SplitRemotePublishPath(string publishPath,
153 out string server,
154 out string directory)
155 {
156 string searchString = "://";
157 int index = publishPath.IndexOf(searchString);
158 if (index == -1)
159 throw new InvalidOperationException();
160 int endOfProtocolIndex = index + searchString.Length;
161 string withoutProtocol = publishPath.Remove(0, endOfProtocolIndex);
162 index = withoutProtocol.IndexOf("/");
163 if (index == -1)
164 {
165 server = withoutProtocol;
166 directory = "";
167 }
168 else
169 {
170 server = withoutProtocol.Substring(0, index);
171 directory = withoutProtocol.Remove(0, index + 1);
172 }
173 }
174
175 /// <summary>
176 /// Copy ISO to the (remote) destination.
177 /// </summary>
178 /// <param name="sourceFilename">Name of source ISO file to copy.</param>
179 /// <param name="branch">Branch.</param>
180 /// <param name="revision">Revision.</param>
181 /// <remarks>
182 /// Structure is ftp://ftp.server.com/whereever/<branch>/ReactOS-<branch>-r<revision>.iso.
183 /// </remarks>
184 private static void CopyISOToRemoteFtpDestination(string sourceFilename,
185 string branch,
186 int revision)
187 {
188 string server;
189 string directory;
190 SplitRemotePublishPath(publishPath, out server, out directory);
191 FtpClient ftpClient = new FtpClient(server, "anonymous", "sin@svn.reactos.com");
192 ftpClient.Login();
193 if (directory != "")
194 ftpClient.ChangeDir(directory);
195 /* Create destination directory if it does not already exist */
196 if (!ftpClient.DirectoryExists(branch))
197 ftpClient.MakeDir(branch);
198 ftpClient.ChangeDir(branch);
199 ftpClient.Upload(sourceFilename);
200 ftpClient.Close();
201 }
202
203 /// <summary>
204 /// Copy ISO to the (local) destination.
205 /// </summary>
206 /// <param name="sourceFilename">Name of source ISO file to copy.</param>
207 /// <param name="branch">Branch.</param>
208 /// <param name="revision">Revision.</param>
209 /// <remarks>
210 /// Structure is <branch>\ReactOS-<branch>-r<revision>.iso.
211 /// </remarks>
212 private static void CopyISOToLocalDestination(string sourceFilename,
213 string branch,
214 int revision)
215 {
216 string distributionFilename = GetDistributionFilename(branch,
217 revision);
218 string destinationDirectory = Path.Combine(publishPath,
219 branch);
220 string destinationFilename = Path.Combine(destinationDirectory,
221 distributionFilename);
222 if (!Directory.Exists(destinationDirectory))
223 Directory.CreateDirectory(destinationDirectory);
224 File.Copy(sourceFilename,
225 destinationFilename);
226 }
227
228 /// <summary>
229 /// Copy ISO to the destination.
230 /// </summary>
231 /// <param name="sourceFilename">Name of source ISO file to copy.</param>
232 /// <param name="branch">Branch.</param>
233 /// <param name="revision">Revision.</param>
234 /// <remarks>
235 /// Structure is <branch>\ReactOS-<branch>-r<revision>.iso.
236 /// </remarks>
237 private static void CopyISOToDestination(string sourceFilename,
238 string branch,
239 int revision)
240 {
241 if (PublishToRemoteFtpLocation)
242 CopyISOToRemoteFtpDestination(sourceFilename, branch, revision);
243 else
244 CopyISOToLocalDestination(sourceFilename, branch, revision);
245 }
246
247 /// <summary>
248 /// Publish a revision of ReactOS.
249 /// </summary>
250 /// <param name="text">Error message.</param>
251 private static int Publish(string branch,
252 int revision,
253 string workingDirectory)
254 {
255 string make = "mingw32-make";
256 string makeParameters = GetConfigurationOption("makeParameters", "");
257 string reactosDirectory = Path.Combine(workingDirectory,
258 "reactos");
259 Console.WriteLine(String.Format("ReactOS directory is {0}",
260 reactosDirectory));
261 string standardOutput;
262 string standardError;
263 int exitCode = RunScript(make,
264 makeParameters + " bootcd",
265 reactosDirectory,
266 out standardOutput,
267 out standardError);
268 if (exitCode != 0)
269 {
270 Fail(revision,
271 String.Format("make bootcd failed: (error: {0}) {1}",
272 standardError,
273 standardOutput));
274 return exitCode;
275 }
276
277 string sourceFilename = Path.Combine(reactosDirectory,
278 "ReactOS.iso");
279 if (File.Exists(sourceFilename))
280 CopyISOToDestination(sourceFilename,
281 branch,
282 revision);
283 else
284 {
285 Fail(revision,
286 "make bootcd produced no ReactOS.iso");
287 return exitCode;
288 }
289
290 return exitCode;
291 }
292
293 /// <summary>
294 /// Program entry point.
295 /// </summary>
296 /// <param name="args">Arguments from command line.</param>
297 /// <remarks>
298 /// If exit code is 0, then the commit was processed successfully.
299 /// If exit code is 1, then the commit was not processed successfully.
300 /// </remarks>
301 public static void Main(string[] args)
302 {
303 try
304 {
305 System.Environment.ExitCode = 1;
306
307 publishPath = ConfigurationSettings.AppSettings["publishPath"];
308 if (publishPath == null)
309 {
310 Fail("PublishPath option not set.");
311 return;
312 }
313
314 if (args.Length < 3)
315 {
316 Fail("Usage: ReactOS.CustomRevisionAction action branch revision");
317 return;
318 }
319
320 string action = args[0]; /* bootcd */
321 string branch = args[1];
322 int revision = Int32.Parse(args[2]);
323
324 System.Environment.ExitCode = Publish(branch,
325 revision,
326 System.Environment.CurrentDirectory);
327 }
328 catch (Exception ex)
329 {
330 Fail(String.Format("Exception: {0}", ex));
331 System.Environment.ExitCode = 1;
332 }
333 }
334 }
335 }