source: trunk/eraser/Eraser.BlackBox/BlackBoxReportUploader.cs @ 2696

Revision 2696, 11.3 KB checked in by lowjoel, 12 months ago (diff)

When submitting the report, read the response to get the report ID. We can use the report ID to check for resolutions in future.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Rev URL
Line 
1/*
2 * $Id$
3 * Copyright 2008-2012 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.Linq;
25using System.Text;
26
27using System.IO;
28using System.Net;
29using System.Xml;
30using System.Globalization;
31
32using SevenZip;
33using SevenZip.Compression.LZMA;
34using ICSharpCode.SharpZipLib.Tar;
35using ICSharpCode.SharpZipLib.BZip2;
36using Microsoft.Win32.SafeHandles;
37
38using Eraser.Util;
39using Eraser.Plugins;
40using ProgressChangedEventHandler = Eraser.Plugins.ProgressChangedEventHandler;
41using ProgressChangedEventArgs = Eraser.Plugins.ProgressChangedEventArgs;
42
43namespace Eraser.BlackBox
44{
45    /// <summary>
46    /// Uploads <see cref="BlackBoxReport"/>s to the Eraser server.
47    /// </summary>
48    public class BlackBoxReportUploader
49    {
50        private class SevenZipProgressCallback : ICodeProgress
51        {
52            public SevenZipProgressCallback(BlackBoxReportUploader uploader,
53                SteppedProgressManager progress, ProgressManager stepProgress,
54                ProgressChangedEventHandler progressChanged)
55            {
56                Uploader = uploader;
57                Progress = progress;
58                StepProgress = stepProgress;
59                EventHandler = progressChanged;
60            }
61
62            #region ICodeProgress Members
63
64            public void SetProgress(long inSize, long outSize)
65            {
66                StepProgress.Completed = inSize;
67                EventHandler(Uploader, new ProgressChangedEventArgs(Progress, null));
68            }
69
70            #endregion
71
72            private BlackBoxReportUploader Uploader;
73            private SteppedProgressManager Progress;
74            private ProgressManager StepProgress;
75            private ProgressChangedEventHandler EventHandler;
76        }
77
78        /// <summary>
79        /// Constructor.
80        /// </summary>
81        /// <param name="report">The report to upload.</param>
82        public BlackBoxReportUploader(BlackBoxReport report)
83        {
84            Report = report;
85            if (!Directory.Exists(UploadTempDir))
86                Directory.CreateDirectory(UploadTempDir);
87
88            ReportBaseName = Path.Combine(UploadTempDir, Report.Name);
89        }
90
91        /// <summary>
92        /// Gets from the server based on the stack trace whether this report is
93        /// new.
94        /// </summary>
95        public bool IsNew
96        {
97            get
98            {
99                PostDataBuilder builder = new PostDataBuilder();
100                builder.AddPart(new PostDataField("action", "status"));
101                AddStackTraceToRequest(Report.StackTrace, builder);
102
103                WebRequest reportRequest = HttpWebRequest.Create(BlackBoxServer);
104                reportRequest.ContentType = builder.ContentType;
105                reportRequest.Method = "POST";
106                using (Stream formStream = builder.Stream)
107                {
108                    reportRequest.ContentLength = formStream.Length;
109                    using (Stream requestStream = reportRequest.GetRequestStream())
110                    {
111                        int lastRead = 0;
112                        byte[] buffer = new byte[32768];
113                        while ((lastRead = formStream.Read(buffer, 0, buffer.Length)) != 0)
114                            requestStream.Write(buffer, 0, lastRead);
115                    }
116                }
117
118                try
119                {
120                    HttpWebResponse response = reportRequest.GetResponse() as HttpWebResponse;
121                    using (Stream responseStream = response.GetResponseStream())
122                    {
123                        XmlReader reader = XmlReader.Create(responseStream);
124                        reader.ReadToFollowing("crashReport");
125                        string reportStatus = reader.GetAttribute("status");
126                        switch (reportStatus)
127                        {
128                            case "exists":
129                                Report.Submitted = true;
130                                return false;
131
132                            case "new":
133                                return true;
134
135                            default:
136                                throw new InvalidDataException(
137                                    "Unknown crash report server response.");
138                        }
139                    }
140                }
141                catch (WebException e)
142                {
143                    if (e.Response == null)
144                        throw;
145
146                    using (Stream responseStream = e.Response.GetResponseStream())
147                    {
148                        try
149                        {
150                            XmlReader reader = XmlReader.Create(responseStream);
151                            reader.ReadToFollowing("error");
152                            throw new InvalidDataException(string.Format(CultureInfo.CurrentCulture,
153                                "The server encountered a problem while processing the request: {0}",
154                                reader.ReadString()));
155                        }
156                        catch (XmlException)
157                        {
158                        }
159                    }
160
161                    throw new InvalidDataException(((HttpWebResponse)e.Response).StatusDescription);
162                }
163            }
164        }
165
166        /// <summary>
167        /// Compresses the report for uploading.
168        /// </summary>
169        /// <param name="progress">The <see cref="ProgressManager"/> instance that the
170        /// Upload function is using.</param>
171        /// <param name="progressChanged">The progress changed event handler that should
172        /// be called for upload progress updates.</param>
173        private void Compress(SteppedProgressManager progress,
174            ProgressChangedEventHandler progressChanged)
175        {
176            using (FileStream archiveStream = new FileStream(ReportBaseName + ".tar",
177                    FileMode.Create, FileAccess.Write))
178            {
179                //Add the report into a tar file
180                TarArchive archive = TarArchive.CreateOutputTarArchive(archiveStream);
181                foreach (FileInfo file in Report.Files)
182                {
183                    TarEntry entry = TarEntry.CreateEntryFromFile(file.FullName);
184                    entry.Name = Path.GetFileName(entry.Name);
185                    archive.WriteEntry(entry, false);
186                }
187                archive.Close();
188            }
189
190            ProgressManager step = new ProgressManager();
191            progress.Steps.Add(new SteppedProgressManagerStep(step, 0.5f, "Compressing"));
192            CoderPropID[] propIDs = 
193                {
194                    CoderPropID.DictionarySize,
195                    CoderPropID.PosStateBits,
196                    CoderPropID.LitContextBits,
197                    CoderPropID.LitPosBits,
198                    CoderPropID.Algorithm,
199                    CoderPropID.NumFastBytes,
200                    CoderPropID.MatchFinder,
201                    CoderPropID.EndMarker
202                };
203            object[] properties = 
204                {
205                    (Int32)(1 << 24),           //Dictionary Size
206                    (Int32)2,                   //PosState Bits
207                    (Int32)0,                   //LitContext Bits
208                    (Int32)2,                   //LitPos Bits
209                    (Int32)2,                   //Algorithm
210                    (Int32)128,                 //Fast Bytes
211                    "bt4",                      //Match Finger
212                    true                        //Write end-of-stream
213                };
214
215            SevenZip.Compression.LZMA.Encoder encoder = new SevenZip.Compression.LZMA.Encoder();
216            encoder.SetCoderProperties(propIDs, properties);
217
218            using (FileStream sevenZipFile = new FileStream(ReportBaseName + ".tar.7z",
219                FileMode.Create))
220            using (FileStream tarStream = new FileStream(ReportBaseName + ".tar",
221                FileMode.Open, FileAccess.Read, FileShare.Read, 262144, FileOptions.DeleteOnClose))
222            {
223                encoder.WriteCoderProperties(sevenZipFile);
224                Int64 fileSize = -1;
225                for (int i = 0; i < 8; i++)
226                    sevenZipFile.WriteByte((Byte)(fileSize >> (8 * i)));
227
228                step.Total = tarStream.Length;
229                ICodeProgress callback = progressChanged == null ? null :
230                    new SevenZipProgressCallback(this, progress, step, progressChanged);
231                encoder.Code(tarStream, sevenZipFile, -1, -1, callback);
232            }
233        }
234
235        /// <summary>
236        /// Compresses the report, then uploads it to the server.
237        /// </summary>
238        /// <param name="progressChanged">The progress changed event handler that should
239        /// be called for upload progress updates.</param>
240        public void Submit(ProgressChangedEventHandler progressChanged)
241        {
242            SteppedProgressManager overallProgress = new SteppedProgressManager();
243            Compress(overallProgress, progressChanged);
244
245            using (FileStream bzipFile = new FileStream(ReportBaseName + ".tar.7z",
246                FileMode.Open, FileAccess.Read, FileShare.Read, 131072, FileOptions.DeleteOnClose))
247            using (Stream logFile = Report.DebugLog)
248            {
249                //Build the POST request
250                PostDataBuilder builder = new PostDataBuilder();
251                builder.AddPart(new PostDataField("action", "upload"));
252                builder.AddPart(new PostDataFileField("crashReport", "Report.tar.7z", bzipFile));
253                AddStackTraceToRequest(Report.StackTrace, builder);
254
255                //Upload the POST request
256                WebRequest reportRequest = HttpWebRequest.Create(BlackBoxServer);
257                reportRequest.ContentType = builder.ContentType;
258                reportRequest.Method = "POST";
259                reportRequest.Timeout = int.MaxValue;
260                using (Stream formStream = builder.Stream)
261                {
262                    ProgressManager progress = new ProgressManager();
263                    overallProgress.Steps.Add(new SteppedProgressManagerStep(
264                        progress, 0.5f, "Uploading"));
265                    reportRequest.ContentLength = formStream.Length;
266
267                    using (Stream requestStream = reportRequest.GetRequestStream())
268                    {
269                        int lastRead = 0;
270                        byte[] buffer = new byte[32768];
271                        while ((lastRead = formStream.Read(buffer, 0, buffer.Length)) != 0)
272                        {
273                            requestStream.Write(buffer, 0, lastRead);
274
275                            progress.Total = formStream.Length;
276                            progress.Completed = formStream.Position;
277                            progressChanged(this, new ProgressChangedEventArgs(overallProgress, null));
278                        }
279                    }
280                }
281
282                try
283                {
284                    HttpWebResponse response = reportRequest.GetResponse() as HttpWebResponse;
285                    using (Stream responseStream = response.GetResponseStream())
286                    {
287                        XmlReader reader = XmlReader.Create(responseStream);
288                        reader.ReadToFollowing("crashReport");
289                        string reportStatus = reader.GetAttribute("status");
290                        string reportId = reader.GetAttribute("id");
291                    }
292
293                    Report.Submitted = true;
294                }
295                catch (WebException e)
296                {
297                    if (e.Response == null)
298                        throw;
299
300                    using (Stream responseStream = e.Response.GetResponseStream())
301                    {
302                        try
303                        {
304                            XmlReader reader = XmlReader.Create(responseStream);
305                            reader.ReadToFollowing("error");
306                            throw new InvalidDataException(string.Format(CultureInfo.CurrentCulture,
307                                "The server encountered a problem while processing the request: {0}",
308                                reader.ReadString()));
309                        }
310                        catch (XmlException)
311                        {
312                        }
313                    }
314
315                    throw new InvalidDataException(((HttpWebResponse)e.Response).StatusDescription);
316                }
317            }
318        }
319
320        /// <summary>
321        /// Adds the stack trace to the given form request.
322        /// </summary>
323        /// <param name="stackTrace">The stack trace to add.</param>
324        /// <param name="builder">The Form request builder to add the stack trace to.</param>
325        private static void AddStackTraceToRequest(IList<BlackBoxExceptionEntry> stackTrace,
326            PostDataBuilder builder)
327        {
328            int exceptionIndex = 0;
329            foreach (BlackBoxExceptionEntry exceptionStack in stackTrace)
330            {
331                foreach (string stackFrame in exceptionStack.StackTrace)
332                    builder.AddPart(new PostDataField(
333                        string.Format(CultureInfo.InvariantCulture, "stackTrace[{0}][]", exceptionIndex), stackFrame));
334                builder.AddPart(new PostDataField(string.Format(CultureInfo.InvariantCulture,
335                    "stackTrace[{0}][exception]", exceptionIndex), exceptionStack.ExceptionType));
336                ++exceptionIndex;
337            }
338        }
339
340        /// <summary>
341        /// The path to where the temporary files are stored before uploading.
342        /// </summary>
343        private static readonly string UploadTempDir =
344            Path.Combine(Path.GetTempPath(), "Eraser Crash Reports");
345
346        /// <summary>
347        /// The URI to the BlackBox server.
348        /// </summary>
349        private static readonly Uri BlackBoxServer =
350            new Uri("http://eraser.heidi.ie/scripts/blackbox/upload.php");
351
352        /// <summary>
353        /// The report being uploaded.
354        /// </summary>
355        private BlackBoxReport Report;
356
357        /// <summary>
358        /// The base name of the report.
359        /// </summary>
360        private readonly string ReportBaseName;
361    }
362}
Note: See TracBrowser for help on using the repository browser.