source: trunk/eraser/Eraser.BlackBox/BlackBox.cs @ 2214

Revision 2214, 26.4 KB checked in by lowjoel, 4 years ago (diff)

Specify the culture we are interested in for BlackBox? when calling string.Format (the Invariant culture, since developers will be reading it)

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