source: trunk/eraser6/Eraser.Util/BlackBox.cs @ 1842

Revision 1842, 23.4 KB checked in by lowjoel, 4 years ago (diff)

Fixed potential problem creating the report directory if the disk is full.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 * Copyright 2008-2010 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.Text;
25
26using System.Windows.Forms;
27using System.IO;
28using System.Runtime.InteropServices;
29using System.Diagnostics;
30using System.Reflection;
31using Microsoft.Win32.SafeHandles;
32
33using System.Drawing;
34using System.Drawing.Imaging;
35using System.Collections.ObjectModel;
36using System.Globalization;
37
38using ICSharpCode.SharpZipLib.Tar;
39using ICSharpCode.SharpZipLib.BZip2;
40using System.Net;
41using System.Xml;
42
43namespace Eraser.Util
44{
45    /// <summary>
46    /// Handles application exceptions, stores minidumps and uploads them to the
47    /// Eraser server.
48    /// </summary>
49    public class BlackBox
50    {
51        /// <summary>
52        /// Initialises the BlackBox handler. Call this initialiser once throughout
53        /// the lifespan of the application.
54        /// </summary>
55        /// <returns>The global BlackBox instance.</returns>
56        public static BlackBox Get()
57        {
58            if (Instance == null)
59                Instance = new BlackBox();
60            return Instance;
61        }
62
63        /// <summary>
64        /// Creates a new BlackBox report based on the exception provided.
65        /// </summary>
66        /// <param name="e">The exception which triggered this dump.</param>
67        public void CreateReport(Exception e)
68        {
69            if (e == null)
70                throw new ArgumentNullException("e");
71
72            //Generate a unique identifier for this report.
73            string crashName = DateTime.Now.ToUniversalTime().ToString(
74                CrashReportName, CultureInfo.InvariantCulture);
75            string currentCrashReport = Path.Combine(CrashReportsPath, crashName);
76
77            //Create the report folder. If we can't create the report folder, we can't
78            //create the report contents.
79            Directory.CreateDirectory(currentCrashReport);
80            if (!Directory.Exists(currentCrashReport))
81                return;
82
83            //Store the steps which we have completed.
84            int currentStep = 0;
85
86            try
87            {
88                //First, write a user-readable summary
89                WriteDebugLog(currentCrashReport, e);
90                ++currentStep;
91
92                //Take a screenshot
93                WriteScreenshot(currentCrashReport);
94                ++currentStep;
95
96                //Write a memory dump to the folder
97                WriteMemoryDump(currentCrashReport, e);
98                ++currentStep;
99            }
100            catch
101            {
102                //If an exception was caught while creating the report, we should just
103                //abort as that may cause a cascade. However, we need to remove the
104                //report folder if the crash report is empty.
105                if (currentStep == 0)
106                    Directory.Delete(currentCrashReport);
107            }
108        }
109
110        /// <summary>
111        /// Enumerates the list of crash dumps waiting for upload.
112        /// </summary>
113        /// <returns>A string array containing the list of dumps waiting for upload.</returns>
114        public BlackBoxReport[] GetDumps()
115        {
116            DirectoryInfo dirInfo = new DirectoryInfo(CrashReportsPath);
117            List<BlackBoxReport> result = new List<BlackBoxReport>();
118            if (dirInfo.Exists)
119                foreach (DirectoryInfo subDir in dirInfo.GetDirectories())
120                    try
121                    {
122                        result.Add(new BlackBoxReport(Path.Combine(CrashReportsPath, subDir.Name)));
123                    }
124                    catch (InvalidDataException)
125                    {
126                        //Do nothing: invalid reports are automatically deleted.
127                    }
128
129            return result.ToArray();
130        }
131
132        /// <summary>
133        /// Constructor. Use the <see cref="Initialise"/> function to use this class.
134        /// </summary>
135        private BlackBox()
136        {
137            //If we have a debugger attached we shouldn't bother with exceptions.
138            if (Debugger.IsAttached)
139                return;
140
141            AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
142            Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException);
143        }
144
145        /// <summary>
146        /// Called when an unhandled exception is raised in the application.
147        /// </summary>
148        private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
149        {
150            CreateReport(e.ExceptionObject as Exception);
151        }
152
153        /// <summary>
154        /// Dumps the contents of memory to a dumpfile.
155        /// </summary>
156        /// <param name="dumpFolder">Path to the folder to store the dump file.</param>
157        /// <param name="e">The exception which is being handled.</param>
158        private void WriteMemoryDump(string dumpFolder, Exception e)
159        {
160            //Open a file stream
161            using (FileStream stream = new FileStream(Path.Combine(dumpFolder, MemoryDumpFileName),
162                FileMode.OpenOrCreate, FileAccess.Write, FileShare.None))
163            {
164                //Store the exception information
165                NativeMethods.MiniDumpExceptionInfo exception =
166                    new NativeMethods.MiniDumpExceptionInfo();
167                exception.ClientPointers = false;
168                exception.ExceptionPointers = Marshal.GetExceptionPointers();
169                exception.ThreadId = (uint)AppDomain.GetCurrentThreadId();
170
171                NativeMethods.MiniDumpWriteDump(Process.GetCurrentProcess().Handle,
172                    (uint)Process.GetCurrentProcess().Id, stream.SafeFileHandle,
173                    NativeMethods.MiniDumpType.MiniDumpWithFullMemory,
174                    ref exception, IntPtr.Zero, IntPtr.Zero);
175            }
176        }
177
178        /// <summary>
179        /// Writes a debug log to the given directory.
180        /// </summary>
181        /// <param name="screenshotPath">The path to store the screenshot into.</param>
182        /// <param name="exception">The exception to log about.</param>
183        private void WriteDebugLog(string dumpFolder, Exception exception)
184        {
185            using (FileStream file = new FileStream(Path.Combine(dumpFolder, DebugLogFileName),
186                FileMode.OpenOrCreate, FileAccess.Write, FileShare.None))
187            using (StreamWriter stream = new StreamWriter(file))
188            {
189                //Application information
190                string separator = new string('-', 76);
191                string lineFormat = "{0,15}: {1}";
192                stream.WriteLine("Application Information");
193                stream.WriteLine(separator);
194                stream.WriteLine(string.Format(lineFormat, "Version",
195                    Assembly.GetEntryAssembly().GetName().Version));
196                StringBuilder commandLine = new StringBuilder();
197                foreach (string param in Environment.GetCommandLineArgs())
198                {
199                    commandLine.Append(param);
200                    commandLine.Append(' ');
201                }
202                stream.WriteLine(string.Format(lineFormat, "Command Line",
203                    commandLine.ToString().Trim()));
204
205                //Exception Information
206                stream.WriteLine();
207                stream.WriteLine("Exception Information (Outermost to innermost)");
208                stream.WriteLine(separator);
209
210                //Open a stream to the Stack Trace Log file. We want to separate the stack
211                //trace do we can check against the server to see if the crash is a new one
212                using (StreamWriter stackTraceLog = new StreamWriter(
213                    Path.Combine(dumpFolder, BlackBoxReport.StackTraceFileName)))
214                {
215                    Exception currentException = exception;
216                    for (uint i = 1; currentException != null; ++i)
217                    {
218                        stream.WriteLine(string.Format("Exception {0}:", i));
219                        stream.WriteLine(string.Format(lineFormat, "Message", currentException.Message));
220                        stream.WriteLine(string.Format(lineFormat, "Exception Type",
221                            currentException.GetType().FullName));
222                        stackTraceLog.WriteLine(string.Format("Exception {0}: {1}", i,
223                            currentException.GetType().FullName));
224
225                        //Parse the stack trace
226                        string[] stackTrace = currentException.StackTrace.Split(new char[] { '\n' });
227                        for (uint j = 0; j < stackTrace.Length; ++j)
228                        {
229                            stream.WriteLine(string.Format(lineFormat,
230                                string.Format("Stack Trace [{0}]", j), stackTrace[j].Trim()));
231                            stackTraceLog.WriteLine(string.Format("{0}", stackTrace[j].Trim()));
232                        }
233
234                        uint k = 0;
235                        foreach (System.Collections.DictionaryEntry value in currentException.Data)
236                            stream.WriteLine(string.Format(lineFormat, string.Format("Data[{0}]", ++k),
237                                string.Format("{0} {1}", value.Key.ToString(), value.Value.ToString())));
238
239                        //End the exception and get the inner exception.
240                        stream.WriteLine();
241                        currentException = currentException.InnerException;
242                    }
243                }
244            }
245        }
246
247        /// <summary>
248        /// Writes a screenshot to the given directory
249        /// </summary>
250        /// <param name="dumpFolder">The path to save the screenshot to.</param>
251        private void WriteScreenshot(string dumpFolder)
252        {
253            //Get the size of the screen
254            Rectangle rect = new Rectangle(int.MaxValue, int.MaxValue, int.MinValue, int.MinValue);
255            foreach (Screen screen in Screen.AllScreens)
256                rect = Rectangle.Union(rect, screen.Bounds);
257
258            //Copy a screen DC to the screenshot bitmap
259            Bitmap screenShot = new Bitmap(rect.Width, rect.Height);
260            Graphics bitmap = Graphics.FromImage(screenShot);
261            bitmap.CopyFromScreen(0, 0, 0, 0, rect.Size, CopyPixelOperation.SourceCopy);
262
263            //Save the bitmap to disk
264            screenShot.Save(Path.Combine(dumpFolder, ScreenshotFileName), ImageFormat.Png);
265        }
266
267        /// <summary>
268        /// The global BlackBox instance.
269        /// </summary>
270        private static BlackBox Instance;
271
272        /// <summary>
273        /// The path to all Eraser crash reports.
274        /// </summary>
275        private static readonly string CrashReportsPath = Path.Combine(Environment.GetFolderPath(
276            Environment.SpecialFolder.LocalApplicationData), @"Eraser 6\Crash Reports");
277
278        /// <summary>
279        /// The report name format.
280        /// </summary>
281        internal static readonly string CrashReportName = "yyyyMMdd HHmmss.FFF";
282
283        /// <summary>
284        /// The file name of the memory dump.
285        /// </summary>
286        ///
287        internal static readonly string MemoryDumpFileName = "Memory.dmp";
288
289        /// <summary>
290        /// The file name of the debug log.
291        /// </summary>
292        internal static readonly string DebugLogFileName = "Debug.log";
293
294        /// <summary>
295        /// The file name of the screenshot.
296        /// </summary>
297        internal static readonly string ScreenshotFileName = "Screenshot.png";
298    }
299
300    /// <summary>
301    /// Represents one BlackBox crash report.
302    /// </summary>
303    public class BlackBoxReport
304    {
305        /// <summary>
306        /// Constructor.
307        /// </summary>
308        /// <param name="path">Path to the folder containing the memory dump, screenshot and
309        /// debug log.</param>
310        internal BlackBoxReport(string path)
311        {
312            Path = path;
313
314            string stackTracePath = System.IO.Path.Combine(Path, StackTraceFileName);
315            if (!File.Exists(stackTracePath))
316            {
317                Delete();
318                throw new InvalidDataException("The BlackBox report is corrupt.");
319            }
320
321            string[] stackTrace = null;
322            using (StreamReader reader = new StreamReader(stackTracePath))
323                stackTrace = reader.ReadToEnd().Split(new char[] { '\n' });
324
325            //Parse the lines in the file.
326            StackTraceCache = new List<BlackBoxExceptionEntry>();
327            List<string> currentException = new List<string>();
328            string exceptionType = null;
329            foreach (string str in stackTrace)
330            {
331                if (str.StartsWith("Exception "))
332                {
333                    //Add the current exception to the list of exceptions.
334                    if (currentException.Count != 0)
335                    {
336                        StackTraceCache.Add(new BlackBoxExceptionEntry(exceptionType,
337                            new List<string>(currentException)));
338                        currentException.Clear();
339                    }
340
341                    //Set the exception type for the next exception.
342                    exceptionType = str.Substring(str.IndexOf(':') + 1).Trim();
343                }
344                else if (!string.IsNullOrEmpty(str.Trim()))
345                {
346                    currentException.Add(str.Trim());
347                }
348            }
349
350            if (currentException.Count != 0)
351                StackTraceCache.Add(new BlackBoxExceptionEntry(exceptionType, currentException));
352        }
353
354        /// <summary>
355        /// Deletes the report and its contents.
356        /// </summary>
357        public void Delete()
358        {
359            Directory.Delete(Path, true);
360        }
361
362        /// <summary>
363        /// The name of the report.
364        /// </summary>
365        public string Name
366        {
367            get
368            {
369                return System.IO.Path.GetFileName(Path);
370            }
371        }
372
373        /// <summary>
374        /// The timestamp of the report.
375        /// </summary>
376        public DateTime Timestamp
377        {
378            get
379            {
380                return DateTime.ParseExact(Name, BlackBox.CrashReportName,
381                    CultureInfo.InvariantCulture).ToLocalTime();
382            }
383        }
384
385        /// <summary>
386        /// The path to the folder containing the report.
387        /// </summary>
388        public string Path
389        {
390            get;
391            private set;
392        }
393
394        /// <summary>
395        /// The files which comprise the error report.
396        /// </summary>
397        public ReadOnlyCollection<FileInfo> Files
398        {
399            get
400            {
401                List<FileInfo> result = new List<FileInfo>();
402                DirectoryInfo directory = new DirectoryInfo(Path);
403                foreach (FileInfo file in directory.GetFiles())
404                    if (!InternalFiles.Contains(file.Name))
405                        result.Add(file);
406
407                return result.AsReadOnly();
408            }
409        }
410
411        /// <summary>
412        /// Gets a read-only stream which reads the Debug log.
413        /// </summary>
414        public Stream DebugLog
415        {
416            get
417            {
418                return new FileStream(System.IO.Path.Combine(Path, BlackBox.DebugLogFileName),
419                    FileMode.Open, FileAccess.Read, FileShare.Read);
420            }
421        }
422
423        /// <summary>
424        /// Gets the stack trace for this crash report.
425        /// </summary>
426        public ReadOnlyCollection<BlackBoxExceptionEntry> StackTrace
427        {
428            get
429            {
430                return StackTraceCache.AsReadOnly();
431            }
432        }
433
434        /// <summary>
435        /// Gets or sets whether the given report has been uploaded to the server.
436        /// </summary>
437        public bool Submitted
438        {
439            get
440            {
441                byte[] buffer = new byte[1];
442                using (FileStream stream = new FileStream(System.IO.Path.Combine(Path, StatusFileName),
443                    FileMode.OpenOrCreate, FileAccess.Read, FileShare.Read))
444                {
445                    stream.Read(buffer, 0, buffer.Length);
446                }
447
448                return buffer[0] == 1;
449            }
450
451            set
452            {
453                byte[] buffer = { Convert.ToByte(value) };
454                using (FileStream stream = new FileStream(System.IO.Path.Combine(Path, StatusFileName),
455                    FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read))
456                {
457                    stream.Write(buffer, 0, buffer.Length);
458                }
459            }
460        }
461
462        public override string ToString()
463        {
464            return Name;
465        }
466
467        /// <summary>
468        /// The backing variable for the <see cref="StackTrace"/> field.
469        /// </summary>
470        private List<BlackBoxExceptionEntry> StackTraceCache;
471
472        /// <summary>
473        /// The file name for the status file.
474        /// </summary>
475        private static readonly string StatusFileName = "Status.txt";
476
477        /// <summary>
478        /// The file name of the stack trace.
479        /// </summary>
480        internal static readonly string StackTraceFileName = "Stack Trace.log";
481
482        /// <summary>
483        /// The list of files internal to the report.
484        /// </summary>
485        private static readonly List<string> InternalFiles = new List<string>(
486            new string[] {
487                 StackTraceFileName,
488                 StatusFileName
489            }
490        );
491    }
492
493    /// <summary>
494    /// Represents one exception which can be chained <see cref="InnerException"/>
495    /// to represent the exception handled by BlackBox
496    /// </summary>
497    public class BlackBoxExceptionEntry
498    {
499        /// <summary>
500        /// Constructor.
501        /// </summary>
502        /// <param name="exceptionType">The type of the exception.</param>
503        /// <param name="stackTrace">The stack trace for this exception.</param>
504        internal BlackBoxExceptionEntry(string exceptionType, List<string> stackTrace)
505        {
506            ExceptionType = exceptionType;
507            StackTraceCache = stackTrace;
508        }
509
510        /// <summary>
511        /// The type of the exception.
512        /// </summary>
513        public string ExceptionType
514        {
515            get;
516            private set;
517        }
518
519        /// <summary>
520        /// The stack trace for this exception.
521        /// </summary>
522        public ReadOnlyCollection<string> StackTrace
523        {
524            get
525            {
526                return StackTraceCache.AsReadOnly();
527            }
528        }
529
530        /// <summary>
531        /// The backing variable for the <see cref="StackTrace"/> property.
532        /// </summary>
533        private List<string> StackTraceCache;
534    }
535
536    /// <summary>
537    /// Uploads <see cref="BlackBoxReport"/>s to the Eraser server.
538    /// </summary>
539    public class BlackBoxReportUploader
540    {
541        /// <summary>
542        /// Constructor.
543        /// </summary>
544        /// <param name="report">The report to upload.</param>
545        public BlackBoxReportUploader(BlackBoxReport report)
546        {
547            Report = report;
548            if (!Directory.Exists(UploadTempDir))
549                Directory.CreateDirectory(UploadTempDir);
550
551            ReportBaseName = Path.Combine(UploadTempDir, Report.Name);
552        }
553
554        /// <summary>
555        /// Gets from the server based on the stack trace whether this report is
556        /// new.
557        /// </summary>
558        public bool IsNew
559        {
560            get
561            {
562                PostDataBuilder builder = new PostDataBuilder();
563                builder.AddPart(new PostDataField("action", "status"));
564                AddStackTraceToRequest(Report.StackTrace, builder);
565
566                WebRequest reportRequest = HttpWebRequest.Create(BlackBoxServer);
567                reportRequest.ContentType = builder.ContentType;
568                reportRequest.Method = "POST";
569                using (Stream formStream = builder.Stream)
570                {
571                    reportRequest.ContentLength = formStream.Length;
572                    using (Stream requestStream = reportRequest.GetRequestStream())
573                    {
574                        int lastRead = 0;
575                        byte[] buffer = new byte[32768];
576                        while ((lastRead = formStream.Read(buffer, 0, buffer.Length)) != 0)
577                            requestStream.Write(buffer, 0, lastRead);
578                    }
579                }
580
581                try
582                {
583                    HttpWebResponse response = reportRequest.GetResponse() as HttpWebResponse;
584                    using (Stream responseStream = response.GetResponseStream())
585                    {
586                        XmlReader reader = XmlReader.Create(responseStream);
587                        reader.ReadToFollowing("crashReport");
588                        string reportStatus = reader.GetAttribute("status");
589                        switch (reportStatus)
590                        {
591                            case "exists":
592                                Report.Submitted = true;
593                                return false;
594
595                            case "new":
596                                return true;
597
598                            default:
599                                throw new InvalidDataException(
600                                    "Unknown crash report server response.");
601                        }
602                    }
603                }
604                catch (WebException e)
605                {
606                    using (Stream responseStream = e.Response.GetResponseStream())
607                    {
608                        try
609                        {
610                            XmlReader reader = XmlReader.Create(responseStream);
611                            reader.ReadToFollowing("error");
612                            throw new InvalidDataException(string.Format(CultureInfo.CurrentCulture,
613                                "The server encountered a problem while processing the request: {0}",
614                                reader.ReadString()));
615                        }
616                        catch (XmlException)
617                        {
618                        }
619                    }
620
621                    throw new InvalidDataException(((HttpWebResponse)e.Response).StatusDescription);
622                }
623            }
624        }
625
626        /// <summary>
627        /// Compresses the report for uploading.
628        /// </summary>
629        /// <param name="progress">The <see cref="ProgressManager"/> instance that the
630        /// Upload function is using.</param>
631        /// <param name="progressChanged">The progress changed event handler that should
632        /// be called for upload progress updates.</param>
633        private void Compress(SteppedProgressManager progress,
634            ProgressChangedEventHandler progressChanged)
635        {
636            using (FileStream archiveStream = new FileStream(ReportBaseName + ".tar",
637                    FileMode.Create, FileAccess.Write))
638            {
639                //Add the report into a tar file
640                TarArchive archive = TarArchive.CreateOutputTarArchive(archiveStream);
641                foreach (FileInfo file in Report.Files)
642                {
643                    TarEntry entry = TarEntry.CreateEntryFromFile(file.FullName);
644                    entry.Name = Path.GetFileName(entry.Name);
645                    archive.WriteEntry(entry, false);
646                }
647                archive.Close();
648            }
649
650            ProgressManager step = new ProgressManager();
651            progress.Steps.Add(new SteppedProgressManagerStep(step, 0.5f, "Compressing"));
652            using (FileStream bzipFile = new FileStream(ReportBaseName + ".tbz",
653                FileMode.Create))
654            using (FileStream tarStream = new FileStream(ReportBaseName + ".tar",
655                FileMode.Open, FileAccess.Read, FileShare.Read, 262144, FileOptions.DeleteOnClose))
656            using (BZip2OutputStream bzipStream = new BZip2OutputStream(bzipFile, 262144))
657            {
658                //Compress the tar file
659                int lastRead = 0;
660                byte[] buffer = new byte[524288];
661                while ((lastRead = tarStream.Read(buffer, 0, buffer.Length)) != 0)
662                {
663                    bzipStream.Write(buffer, 0, lastRead);
664                    step.Total = tarStream.Length;
665                    step.Completed = tarStream.Position;
666
667                    if (progressChanged != null)
668                        progressChanged(this, new ProgressChangedEventArgs(progress, null));
669                }
670            }
671        }
672
673        /// <summary>
674        /// Compresses the report, then uploads it to the server.
675        /// </summary>
676        /// <param name="progressChanged">The progress changed event handler that should
677        /// be called for upload progress updates.</param>
678        public void Submit(ProgressChangedEventHandler progressChanged)
679        {
680            SteppedProgressManager overallProgress = new SteppedProgressManager();
681            Compress(overallProgress, progressChanged);
682
683            using (FileStream bzipFile = new FileStream(ReportBaseName + ".tbz",
684                FileMode.Open, FileAccess.Read, FileShare.Read, 131072, FileOptions.DeleteOnClose))
685            using (Stream logFile = Report.DebugLog)
686            {
687                //Build the POST request
688                PostDataBuilder builder = new PostDataBuilder();
689                builder.AddPart(new PostDataField("action", "upload"));
690                builder.AddPart(new PostDataFileField("crashReport", "Report.tbz", bzipFile));
691                AddStackTraceToRequest(Report.StackTrace, builder);
692
693                //Upload the POST request
694                WebRequest reportRequest = HttpWebRequest.Create(BlackBoxServer);
695                reportRequest.ContentType = builder.ContentType;
696                reportRequest.Method = "POST";
697                reportRequest.Timeout = int.MaxValue;
698                using (Stream formStream = builder.Stream)
699                {
700                    ProgressManager progress = new ProgressManager();
701                    overallProgress.Steps.Add(new SteppedProgressManagerStep(
702                        progress, 0.5f, "Uploading"));
703                    reportRequest.ContentLength = formStream.Length;
704
705                    using (Stream requestStream = reportRequest.GetRequestStream())
706                    {
707                        int lastRead = 0;
708                        byte[] buffer = new byte[32768];
709                        while ((lastRead = formStream.Read(buffer, 0, buffer.Length)) != 0)
710                        {
711                            requestStream.Write(buffer, 0, lastRead);
712
713                            progress.Total = formStream.Length;
714                            progress.Completed = formStream.Position;
715                            progressChanged(this, new ProgressChangedEventArgs(overallProgress, null));
716                        }
717                    }
718                }
719
720                try
721                {
722                    reportRequest.GetResponse();
723                    Report.Submitted = true;
724                }
725                catch (WebException e)
726                {
727                    using (Stream responseStream = e.Response.GetResponseStream())
728                    {
729                        try
730                        {
731                            XmlReader reader = XmlReader.Create(responseStream);
732                            reader.ReadToFollowing("error");
733                            throw new InvalidDataException(string.Format(CultureInfo.CurrentCulture,
734                                "The server encountered a problem while processing the request: {0}",
735                                reader.ReadString()));
736                        }
737                        catch (XmlException)
738                        {
739                        }
740                    }
741
742                    throw new InvalidDataException(((HttpWebResponse)e.Response).StatusDescription);
743                }
744            }
745        }
746
747        /// <summary>
748        /// Adds the stack trace to the given form request.
749        /// </summary>
750        /// <param name="stackTrace">The stack trace to add.</param>
751        /// <param name="builder">The Form request builder to add the stack trace to.</param>
752        private static void AddStackTraceToRequest(IList<BlackBoxExceptionEntry> stackTrace,
753            PostDataBuilder builder)
754        {
755            int exceptionIndex = 0;
756            foreach (BlackBoxExceptionEntry exceptionStack in stackTrace)
757            {
758                foreach (string stackFrame in exceptionStack.StackTrace)
759                    builder.AddPart(new PostDataField(
760                        string.Format("stackTrace[{0}][]", exceptionIndex), stackFrame));
761                builder.AddPart(new PostDataField(string.Format(
762                    "stackTrace[{0}][exception]", exceptionIndex), exceptionStack.ExceptionType));
763                ++exceptionIndex;
764            }
765        }
766
767        /// <summary>
768        /// The path to where the temporary files are stored before uploading.
769        /// </summary>
770        private static readonly string UploadTempDir =
771            Path.Combine(Path.GetTempPath(), "Eraser Crash Reports");
772
773        /// <summary>
774        /// The URI to the BlackBox server.
775        /// </summary>
776        private static readonly Uri BlackBoxServer =
777            new Uri("http://eraser.heidi.ie/scripts/blackbox/upload.php");
778
779        /// <summary>
780        /// The report being uploaded.
781        /// </summary>
782        private BlackBoxReport Report;
783
784        /// <summary>
785        /// The base name of the report.
786        /// </summary>
787        private readonly string ReportBaseName;
788    }
789}
Note: See TracBrowser for help on using the repository browser.