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

Revision 1874, 25.1 KB checked in by lowjoel, 4 years ago (diff)

Add the current working directory, Operating system (including Vista/7 edition and service pack), CPU count and running processes to the debug report.

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