source: branches/eraser6/BlackBox/Eraser/BlackBoxUploadForm.cs @ 1403

Revision 1403, 10.9 KB checked in by lowjoel, 5 years ago (diff)

Decide once the length of the boundary since placing the Random call in the loop condition would introduce too much unpredictability in the boundary length.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 * Copyright 2008-2009 The Eraser Project
4 * Original Author: Joel Low <lowjoel@users.sourceforge.net>
5 * Modified By:
6 *
7 * This file is part of Eraser.
8 *
9 * Eraser is free software: you can redistribute it and/or modify it under the
10 * terms of the GNU General Public License as published by the Free Software
11 * Foundation, either version 3 of the License, or (at your option) any later
12 * version.
13 *
14 * Eraser is distributed in the hope that it will be useful, but WITHOUT ANY
15 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 *
18 * A copy of the GNU General Public License can be found at
19 * <http://www.gnu.org/licenses/>.
20 */
21
22using System;
23using System.Collections.Generic;
24using System.ComponentModel;
25using System.Data;
26using System.Drawing;
27using System.Text;
28using System.Windows.Forms;
29using System.IO;
30
31using Eraser.Util;
32using ICSharpCode.SharpZipLib.Tar;
33using ICSharpCode.SharpZipLib.BZip2;
34using System.Net;
35
36namespace Eraser
37{
38    public partial class BlackBoxUploadForm : Form
39    {
40        /// <summary>
41        /// Constructor.
42        /// </summary>
43        /// <param name="reports">The list of reports to upload.</param>
44        public BlackBoxUploadForm(IList<BlackBoxReport> reports)
45        {
46            InitializeComponent();
47            UXThemeApi.UpdateControlTheme(this);
48
49            Reports = reports;
50            UploadWorker.RunWorkerAsync(reports);
51        }
52
53        private void UploadWorker_DoWork(object sender, DoWorkEventArgs e)
54        {
55            IList<BlackBoxReport> reports = (IList<BlackBoxReport>)e.Argument;
56            for (int i = 0; i < reports.Count; ++i)
57            {
58                //Calculate the base progress percentage for this job.
59                int progressPerReport = 100 / reports.Count;
60                int baseProgress = i * progressPerReport;
61                int stepsPerReport = 2;
62
63                BlackBoxReportUploader uploader = new BlackBoxReportUploader(reports[i]);
64
65                //Check that a similar report has not yet been uploaded.
66                UploadWorker.ReportProgress(baseProgress,
67                    S._("Checking for status of report {0}...", reports[i].Name));
68                if (!uploader.ReportIsNew())
69                    continue;
70               
71                //No similar reports have been uploaded. Compress the report.
72                UploadWorker.ReportProgress(baseProgress,
73                    S._("Compressing Report {0}: {1}%", reports[i].Name, 0));
74                uploader.Compress(delegate(object from, ProgressChangedEventArgs progress)
75                    {
76                        UploadWorker.ReportProgress(baseProgress +
77                            progress.ProgressPercentage * progressPerReport / 100 / stepsPerReport,
78                            S._("Compressing Report {0}: {1}%",
79                                reports[i].Name, progress.ProgressPercentage));
80                    });
81
82                //Upload the report.
83                UploadWorker.ReportProgress(baseProgress + progressPerReport / 2,
84                    S._("Uploading Report {0}: {1}%", reports[i].Name, 0));
85                uploader.Upload(delegate(object from, ProgressChangedEventArgs progress)
86                    {
87                        UploadWorker.ReportProgress(baseProgress + progressPerReport / stepsPerReport +
88                            progress.ProgressPercentage * progressPerReport / 100 / stepsPerReport,
89                            S._("Uploading Report {0}: {1}%",
90                                reports[i].Name, progress.ProgressPercentage));
91                    });
92            }
93        }
94
95        private void UploadWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
96        {
97            if (e.UserState != null)
98                ProgressLbl.Text = e.UserState as string;
99            ProgressPb.Value = e.ProgressPercentage;
100        }
101
102        private void UploadWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
103        {
104            if (e.Error != null)
105                MessageBox.Show(e.Error.Message);
106            CancelBtn.Text = S._("Close");
107        }
108
109        private void CancelBtn_Click(object sender, EventArgs e)
110        {
111            if (UploadWorker.IsBusy)
112                UploadWorker.CancelAsync();
113            else
114                Close();
115        }
116
117        private IList<BlackBoxReport> Reports;
118    }
119
120    class BlackBoxReportUploader
121    {
122        /// <summary>
123        /// Constructor.
124        /// </summary>
125        /// <param name="report">The report to upload.</param>
126        public BlackBoxReportUploader(BlackBoxReport report)
127        {
128            Report = report;
129            if (!Directory.Exists(UploadTempDir))
130                Directory.CreateDirectory(UploadTempDir);
131
132            ReportBaseName =  Path.Combine(UploadTempDir, Report.Name);
133        }
134
135        /// <summary>
136        /// Verifies the stack trace against the server to see if the report is new.
137        /// </summary>
138        /// <returns>True if the report is new; false otherwise</returns>
139        public bool ReportIsNew()
140        {
141            throw new NotImplementedException();
142        }
143
144        public void Compress(ProgressChangedEventHandler progressChanged)
145        {
146            using (FileStream archiveStream = new FileStream(ReportBaseName + ".tar",
147                    FileMode.Create, FileAccess.Write))
148            {
149                //Add the report into a tar file
150                TarArchive archive = TarArchive.CreateOutputTarArchive(archiveStream);
151                foreach (FileInfo file in Report.Files)
152                {
153                    TarEntry entry = TarEntry.CreateEntryFromFile(file.FullName);
154                    entry.Name = Path.GetFileName(entry.Name);
155                    archive.WriteEntry(entry, false);
156                }
157                archive.Close();
158            }
159
160            using (FileStream bzipFile = new FileStream(ReportBaseName + ".tbz",
161                FileMode.Create))
162            using (FileStream tarStream = new FileStream(ReportBaseName + ".tar",
163                FileMode.Open, FileAccess.Read, FileShare.Read, 262144, FileOptions.DeleteOnClose))
164            using (BZip2OutputStream bzipStream = new BZip2OutputStream(bzipFile, 262144))
165            {
166                //Compress the tar file
167                int lastRead = 0;
168                byte[] buffer = new byte[524288];
169                while ((lastRead = tarStream.Read(buffer, 0, buffer.Length)) != 0)
170                {
171                    bzipStream.Write(buffer, 0, lastRead);
172                    progressChanged(this, new ProgressChangedEventArgs(
173                        (int)(tarStream.Position * 100 / tarStream.Length), null));
174                }
175            }
176        }
177
178        public void Upload(ProgressChangedEventHandler progressChanged)
179        {
180            using (FileStream bzipFile = new FileStream(ReportBaseName + ".tbz",
181                FileMode.Open, FileAccess.Read, FileShare.Read, 131072, FileOptions.DeleteOnClose))
182            using (Stream logFile = Report.DebugLog)
183            {
184                //Build the POST request
185                MultipartFormDataBuilder builder = new MultipartFormDataBuilder();
186                builder.AddPart(new FormFileField("CrashReport", "Report.tbz", bzipFile));
187                builder.AddPart(new FormFileField("DebugLog", "Debug.log", logFile));
188
189                //Upload the POST request
190                Uri blackBoxServer = new Uri("http://eraser.heidi.ie/BlackBox/upload.php");
191                WebRequest reportRequest = HttpWebRequest.Create(blackBoxServer);
192                reportRequest.ContentType = "multipart/form-data; boundary=" + builder.Boundary;
193                reportRequest.Method = "POST";
194                using (Stream formStream = builder.Stream)
195                {
196                    reportRequest.ContentLength = formStream.Length;
197                    using (Stream requestStream = reportRequest.GetRequestStream())
198                    {
199                        int lastRead = 0;
200                        byte[] buffer = new byte[32768];
201                        while ((lastRead = formStream.Read(buffer, 0, buffer.Length)) != 0)
202                        {
203                            requestStream.Write(buffer, 0, lastRead);
204                            progressChanged(this, new ProgressChangedEventArgs(
205                                (int)(formStream.Position * 100 / formStream.Length), null));
206                        }
207                    }
208                }
209
210                HttpWebResponse response = reportRequest.GetResponse() as HttpWebResponse;
211                if (response.StatusCode != HttpStatusCode.OK)
212                {
213                    using (Stream responseStream = response.GetResponseStream())
214                    using (TextReader reader = new StreamReader(responseStream))
215                        throw new ArgumentException(reader.ReadToEnd());
216                }
217            }
218        }
219
220        /// <summary>
221        /// The path to where the temporary files are stored before uploading.
222        /// </summary>
223        private static readonly string UploadTempDir =
224            Path.Combine(Path.GetTempPath(), "Eraser Crash Reports");
225
226        /// <summary>
227        /// The report being uploaded.
228        /// </summary>
229        private BlackBoxReport Report;
230
231        /// <summary>
232        /// The base name of the report.
233        /// </summary>
234        private readonly string ReportBaseName;
235    }
236
237    class MultipartFormDataBuilder
238    {
239        public MultipartFormDataBuilder()
240        {
241            FileName = Path.GetTempFileName();
242        }
243
244        public void AddPart(FormField field)
245        {
246            //Generate a random part boundary
247            if (Boundary == null)
248            {
249                Random rand = new Random();
250                for (int i = 0, j = 20 + rand.Next(40); i < j; ++i)
251                    Boundary += ValidBoundaryChars[rand.Next(ValidBoundaryChars.Length)];
252            }
253
254            using (FileStream stream = new FileStream(FileName, FileMode.Open, FileAccess.Write,
255                FileShare.Read))
256            {
257                //Append data!
258                stream.Seek(0, SeekOrigin.End);
259
260                StringBuilder currentBoundary = new StringBuilder();
261                currentBoundary.AppendFormat("--{0}\r\n", Boundary);
262                if (field is FormFileField)
263                {
264                    currentBoundary.AppendFormat(
265                        "Content-Disposition: file; name=\"{0}\"; filename=\"{1}\"\r\n",
266                        field.FieldName, ((FormFileField)field).FileName);
267                    currentBoundary.AppendLine("Content-Type: application/octet-stream");
268                }
269                else
270                {
271                    currentBoundary.AppendFormat("Content-Disposition: form-data; name=\"{0}\"\r\n",
272                        field.FieldName);
273                }
274               
275                currentBoundary.AppendLine();
276                byte[] boundary = Encoding.UTF8.GetBytes(currentBoundary.ToString());
277                stream.Write(boundary, 0, boundary.Length);
278               
279                int lastRead = 0;
280                byte[] buffer = new byte[524288];
281                while ((lastRead = field.Stream.Read(buffer, 0, buffer.Length)) != 0)
282                    stream.Write(buffer, 0, lastRead);
283
284                currentBoundary = new StringBuilder();
285                currentBoundary.AppendFormat("\r\n--{0}--\r\n", Boundary);
286                boundary = Encoding.UTF8.GetBytes(currentBoundary.ToString());
287                stream.Write(boundary, 0, boundary.Length);
288            }
289        }
290
291        /// <summary>
292        /// Gets a stream with which to read the data from.
293        /// </summary>
294        public Stream Stream
295        {
296            get
297            {
298                return new FileStream(FileName, FileMode.Open, FileAccess.Read, FileShare.Read);
299            }
300        }
301
302        /// <summary>
303        /// The Multipart/Form-Data boundary in use. If this is NULL, WritePostData will generate one
304        /// and store it here.
305        /// </summary>
306        public string Boundary
307        {
308            get;
309            set;
310        }
311
312        private string FileName;
313
314        /// <summary>
315        /// Characters valid for use in the multipart boundary.
316        /// </summary>
317        private static readonly string ValidBoundaryChars =
318            "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'()+_,-./:=?";
319    }
320
321    class FormField
322    {
323        /// <summary>
324        /// Constructor.
325        /// </summary>
326        /// <param name="fieldName">The name of the field.</param>
327        /// <param name="stream">The stream containing the field data.</param>
328        public FormField(string fieldName, Stream stream)
329        {
330            FieldName = fieldName;
331            Stream = stream;
332        }
333
334        /// <summary>
335        /// The name of the field.
336        /// </summary>
337        public string FieldName;
338
339        /// <summary>
340        /// The stream containing the data for this field.
341        /// </summary>
342        public Stream Stream;
343    }
344
345    class FormFileField : FormField
346    {
347        /// <summary>
348        /// Constructor.
349        /// </summary>
350        /// <param name="fieldName">The name of the form field.</param>
351        /// <param name="fileName">The name of the file.</param>
352        /// <param name="stream">The stream containing the field data.</param>
353        public FormFileField(string fieldName, string fileName, Stream stream)
354            : base(fieldName, stream)
355        {
356            FileName = fileName;
357        }
358
359        /// <summary>
360        /// The name of the file.
361        /// </summary>
362        public string FileName;
363    }
364}
Note: See TracBrowser for help on using the repository browser.