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

Revision 2095, 25.5 KB checked in by lowjoel, 5 years ago (diff)

Fixed potential nested exception when the HTTP request failed.

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