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

Revision 2696, 11.3 KB checked in by lowjoel, 3 years 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.