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

Revision 2789, 11.5 KB checked in by lowjoel, 2 years ago (diff)

Do not trigger a GUI update so often -- this caused compression times to be excessively long. Addresses #448

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Rev URL
Line 
1/*
2 * $Id$
3 * Copyright 2008-2012 The Eraser Project
4 * Original Author: Joel Low <lowjoel@users.sourceforge.net>
5 * Modified By:
6 *
7 * This file is part of Eraser.
8 *
9 * Eraser is free software: you can redistribute it and/or modify it under the
10 * terms of the GNU General Public License as published by the Free Software
11 * Foundation, either version 3 of the License, or (at your option) any later
12 * version.
13 *
14 * Eraser is distributed in the hope that it will be useful, but WITHOUT ANY
15 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 *
18 * A copy of the GNU General Public License can be found at
19 * <http://www.gnu.org/licenses/>.
20 */
21
22using System;
23using System.Collections.Generic;
24using System.Linq;
25using System.Text;
26
27using System.IO;
28using System.Net;
29using System.Xml;
30using System.Globalization;
31
32using SevenZip;
33using SevenZip.Compression.LZMA;
34using ICSharpCode.SharpZipLib.Tar;
35using ICSharpCode.SharpZipLib.BZip2;
36using Microsoft.Win32.SafeHandles;
37
38using Eraser.Util;
39using Eraser.Plugins;
40using ProgressChangedEventHandler = Eraser.Plugins.ProgressChangedEventHandler;
41using ProgressChangedEventArgs = Eraser.Plugins.ProgressChangedEventArgs;
42
43namespace Eraser.BlackBox
44{
45    /// <summary>
46    /// Uploads <see cref="BlackBoxReport"/>s to the Eraser server.
47    /// </summary>
48    public class BlackBoxReportUploader
49    {
50        private class SevenZipProgressCallback : ICodeProgress
51        {
52            public SevenZipProgressCallback(BlackBoxReportUploader uploader,
53                SteppedProgressManager progress, ProgressManager stepProgress,
54                ProgressChangedEventHandler progressChanged)
55            {
56                Uploader = uploader;
57                Progress = progress;
58                StepProgress = stepProgress;
59                EventHandler = progressChanged;
60                LastProgressReport = DateTime.MinValue;
61            }
62
63            #region ICodeProgress Members
64
65            public void SetProgress(long inSize, long outSize)
66            {
67                if ((DateTime.Now - LastProgressReport).Ticks > TimeSpan.TicksPerSecond)
68                {
69                    StepProgress.Completed = inSize;
70                    EventHandler(Uploader, new ProgressChangedEventArgs(Progress, null));
71                    LastProgressReport = DateTime.Now;
72                }
73            }
74
75            #endregion
76
77            private BlackBoxReportUploader Uploader;
78            private SteppedProgressManager Progress;
79            private ProgressManager StepProgress;
80            private ProgressChangedEventHandler EventHandler;
81
82            private DateTime LastProgressReport;
83        }
84
85        /// <summary>
86        /// Constructor.
87        /// </summary>
88        /// <param name="report">The report to upload.</param>
89        public BlackBoxReportUploader(BlackBoxReport report)
90        {
91            Report = report;
92            if (!Directory.Exists(UploadTempDir))
93                Directory.CreateDirectory(UploadTempDir);
94
95            ReportBaseName = Path.Combine(UploadTempDir, Report.Name);
96        }
97
98        /// <summary>
99        /// Gets the status of the report.
100        /// </summary>
101        public BlackBoxReportStatus Status
102        {
103            get
104            {
105                //Get the status from the server.
106                XmlDocument result = QueryServer("status", null,
107                    GetStackTraceField(Report.StackTrace).ToArray());
108
109                //Parse the result document
110                XmlNode node = result.SelectSingleNode("/crashReport");
111                string reportStatus = node.Attributes.GetNamedItem("status").Value;
112                try
113                {
114                    return (BlackBoxReportStatus)Enum.Parse(typeof(BlackBoxReportStatus), reportStatus, true);
115                }
116                catch (ArgumentException e)
117                {
118                    throw new InvalidDataException(
119                        "Unknown crash report server response.", e);
120                }
121            }
122        }
123
124        /// <summary>
125        /// Compresses the report for uploading.
126        /// </summary>
127        /// <param name="progress">The <see cref="ProgressManager"/> instance that the
128        /// Upload function is using.</param>
129        /// <param name="progressChanged">The progress changed event handler that should
130        /// be called for upload progress updates.</param>
131        private void Compress(SteppedProgressManager progress,
132            ProgressChangedEventHandler progressChanged)
133        {
134            using (FileStream archiveStream = new FileStream(ReportBaseName + ".tar",
135                    FileMode.Create, FileAccess.Write))
136            {
137                //Add the report into a tar file
138                TarArchive archive = TarArchive.CreateOutputTarArchive(archiveStream);
139                foreach (FileInfo file in Report.Files)
140                {
141                    TarEntry entry = TarEntry.CreateEntryFromFile(file.FullName);
142                    entry.Name = Path.GetFileName(entry.Name);
143                    archive.WriteEntry(entry, false);
144                }
145                archive.Close();
146            }
147
148            ProgressManager step = new ProgressManager();
149            progress.Steps.Add(new SteppedProgressManagerStep(step, 0.5f, "Compressing"));
150            CoderPropID[] propIDs = 
151                {
152                    CoderPropID.DictionarySize,
153                    CoderPropID.PosStateBits,
154                    CoderPropID.LitContextBits,
155                    CoderPropID.LitPosBits,
156                    CoderPropID.Algorithm,
157                    CoderPropID.NumFastBytes,
158                    CoderPropID.MatchFinder,
159                    CoderPropID.EndMarker
160                };
161            object[] properties = 
162                {
163                    (Int32)(1 << 24),           //Dictionary Size
164                    (Int32)2,                   //PosState Bits
165                    (Int32)0,                   //LitContext Bits
166                    (Int32)2,                   //LitPos Bits
167                    (Int32)2,                   //Algorithm
168                    (Int32)128,                 //Fast Bytes
169                    "bt4",                      //Match Finger
170                    true                        //Write end-of-stream
171                };
172
173            SevenZip.Compression.LZMA.Encoder encoder = new SevenZip.Compression.LZMA.Encoder();
174            encoder.SetCoderProperties(propIDs, properties);
175
176            using (FileStream sevenZipFile = new FileStream(ReportBaseName + ".tar.7z",
177                FileMode.Create))
178            using (FileStream tarStream = new FileStream(ReportBaseName + ".tar",
179                FileMode.Open, FileAccess.Read, FileShare.Read, 262144, FileOptions.DeleteOnClose))
180            {
181                encoder.WriteCoderProperties(sevenZipFile);
182                Int64 fileSize = -1;
183                for (int i = 0; i < 8; i++)
184                    sevenZipFile.WriteByte((Byte)(fileSize >> (8 * i)));
185
186                step.Total = tarStream.Length;
187                ICodeProgress callback = progressChanged == null ? null :
188                    new SevenZipProgressCallback(this, progress, step, progressChanged);
189                encoder.Code(tarStream, sevenZipFile, -1, -1, callback);
190            }
191        }
192
193        /// <summary>
194        /// Compresses the report, then uploads it to the server.
195        /// </summary>
196        /// <param name="progressChanged">The progress changed event handler that should
197        /// be called for upload progress updates.</param>
198        public void Submit(ProgressChangedEventHandler progressChanged)
199        {
200            SteppedProgressManager overallProgress = new SteppedProgressManager();
201            Compress(overallProgress, progressChanged);
202
203            using (FileStream dumpFile = new FileStream(ReportBaseName + ".tar.7z",
204                FileMode.Open, FileAccess.Read, FileShare.Read, 131072, FileOptions.DeleteOnClose))
205            {
206                List<PostDataField> fields = GetStackTraceField(Report.StackTrace);
207                fields.Add(new PostDataFileField("crashReport", "Report.tar.7z", dumpFile));
208
209                ProgressManager progress = new ProgressManager();
210                overallProgress.Steps.Add(new SteppedProgressManagerStep(
211                    progress, 0.5f, "Uploading"));
212
213                XmlDocument result = QueryServer("upload", delegate(long uploaded, long total)
214                    {
215                        progress.Total = total;
216                        progress.Completed = uploaded;
217                        progressChanged(this, new ProgressChangedEventArgs(overallProgress, null));
218                    }, fields.ToArray());
219
220                //Parse the result document
221                XmlNode node = result.SelectSingleNode("/crashReport");
222                string reportStatus = node.Attributes.GetNamedItem("status").Value;
223                if (reportStatus == "exists")
224                {
225                    string reportId = node.Attributes.GetNamedItem("id").Value;
226                    Report.Status = BlackBoxReportStatus.Uploaded;
227                    Report.ID = Convert.ToInt32(reportId);
228                }
229            }
230        }
231
232        /// <summary>
233        /// Builds the stackTrace POST data field and retrieves the Post data fields to include
234        /// with the request.
235        /// </summary>
236        /// <param name="stackTrace">The stack trace to add.</param>
237        /// <returns>A list of PostDataField objects which can be added to a PostDataBuilder
238        /// object to add stack trace information to the request.</returns>
239        private static List<PostDataField> GetStackTraceField(IList<BlackBoxExceptionEntry> stackTrace)
240        {
241            int exceptionIndex = 0;
242            List<PostDataField> result = new List<PostDataField>();
243            foreach (BlackBoxExceptionEntry exceptionStack in stackTrace)
244            {
245                foreach (string stackFrame in exceptionStack.StackTrace)
246                    result.Add(new PostDataField(
247                        string.Format(CultureInfo.InvariantCulture, "stackTrace[{0}][]", exceptionIndex), stackFrame));
248
249                result.Add(new PostDataField(string.Format(CultureInfo.InvariantCulture,
250                    "stackTrace[{0}][exception]", exceptionIndex), exceptionStack.ExceptionType));
251                ++exceptionIndex;
252            }
253
254            return result;
255        }
256
257        /// <summary>
258        /// Builds a WebRequest object and queries the server for a response.
259        /// </summary>
260        /// <param name="action">The action to perform.</param>
261        /// <param name="progressChanged">A progress changed event handler receiving
262        /// upload progress information.</param>
263        /// <param name="fields">The POST fields to upload along with the request.</param>
264        /// <returns>An XmlReader containing the response.</returns>
265        private XmlDocument QueryServer(string action, QueryProgress progressChanged = null,
266            params PostDataField[] fields)
267        {
268            PostDataBuilder builder = new PostDataBuilder();
269            builder.AddPart(new PostDataField("action", action));
270            builder.AddParts(fields);
271
272            WebRequest reportRequest = HttpWebRequest.Create(BlackBoxServer);
273            reportRequest.ContentType = builder.ContentType;
274            reportRequest.Method = "POST";
275            reportRequest.Timeout = int.MaxValue;
276            using (Stream formStream = builder.Stream)
277            {
278                reportRequest.ContentLength = formStream.Length;
279                using (Stream requestStream = reportRequest.GetRequestStream())
280                {
281                    int lastRead = 0;
282                    byte[] buffer = new byte[32768];
283                    while ((lastRead = formStream.Read(buffer, 0, buffer.Length)) != 0)
284                    {
285                        requestStream.Write(buffer, 0, lastRead);
286                        if (progressChanged != null)
287                            progressChanged(formStream.Position, formStream.Length);
288                    }
289                }
290            }
291
292            try
293            {
294                HttpWebResponse response = reportRequest.GetResponse() as HttpWebResponse;
295                using (Stream responseStream = response.GetResponseStream())
296                {
297                    XmlReader reader = XmlReader.Create(responseStream);
298                    XmlDocument result = new XmlDocument();
299                    result.Load(reader);
300                    return result;
301                }
302            }
303            catch (XmlException)
304            {
305                return null;
306            }
307            catch (WebException e)
308            {
309                if (e.Response == null)
310                    throw;
311
312                using (Stream responseStream = e.Response.GetResponseStream())
313                {
314                    try
315                    {
316                        XmlReader reader = XmlReader.Create(responseStream);
317                        reader.ReadToFollowing("error");
318                        throw new InvalidDataException(string.Format(CultureInfo.CurrentCulture,
319                            "The server encountered a problem while processing the request: {0}",
320                            reader.ReadString()));
321                    }
322                    catch (XmlException)
323                    {
324                    }
325                }
326
327                throw new InvalidDataException(((HttpWebResponse)e.Response).StatusDescription);
328            }
329        }
330
331        /// <summary>
332        /// Delegate object for upload progress callbacks.
333        /// </summary>
334        /// <param name="uploaded">The amount of data uploaded.</param>
335        /// <param name="total">The amount of data to upload.</param>
336        private delegate void QueryProgress(long uploaded, long total);
337
338        /// <summary>
339        /// The path to where the temporary files are stored before uploading.
340        /// </summary>
341        private static readonly string UploadTempDir =
342            Path.Combine(Path.GetTempPath(), "Eraser Crash Reports");
343
344        /// <summary>
345        /// The URI to the BlackBox server.
346        /// </summary>
347        private static readonly Uri BlackBoxServer =
348            new Uri("http://eraser.clients.bookmarkconsultants.com/scripts/blackbox/upload.php");
349
350        /// <summary>
351        /// The report being uploaded.
352        /// </summary>
353        private BlackBoxReport Report;
354
355        /// <summary>
356        /// The base name of the report.
357        /// </summary>
358        private readonly string ReportBaseName;
359    }
360}
Note: See TracBrowser for help on using the repository browser.