/// <summary> /// /// </summary> /// <param name="folderForThumbNailCache"></param> /// <param name="key">whatever system you want... just used for caching</param> /// <param name="document"></param> /// <param name="options"></param> /// <param name="callback"></param> /// <param name="errorCallback"></param> /// <param name="async"></param> /// <returns></returns> public void GetThumbnail(string folderForThumbNailCache, string key, HtmlDom document, ThumbnailOptions options, Action <Image> callback, Action <Exception> errorCallback, bool async) { //review: old code had it using "key" in one place(checking for existing), thumbNailFilePath in another (adding new) string thumbNailFilePath = null; if (!string.IsNullOrEmpty(folderForThumbNailCache)) { thumbNailFilePath = Path.Combine(folderForThumbNailCache, options.FileName); } //In our cache? Image image; if (!string.IsNullOrWhiteSpace(key) && _images.TryGetValue(key, out image)) { callback(image); return; } //Sitting on disk? if (!string.IsNullOrEmpty(folderForThumbNailCache)) { if (RobustFile.Exists(thumbNailFilePath)) { var thumbnail = ImageUtils.GetImageFromFile(thumbNailFilePath); thumbnail.Tag = thumbNailFilePath; if (!string.IsNullOrWhiteSpace(key)) { _images.Add(key, thumbnail); } callback(thumbnail); return; } } var order = new ThumbnailOrder { ThumbNailFilePath = thumbNailFilePath, Options = options, Callback = callback, ErrorCallback = errorCallback, Document = document, FolderForThumbNailCache = folderForThumbNailCache, CancelToken = Program.BloomThreadCancelService, Key = key }; if (async) { QueueOrder(order); } else { ProcessOrder(order); } }
/// <summary> /// A synchronous version of getting thumbnails, currently used by the image server /// to get thumbnails that are used in the add page dialog. /// </summary> /// <param name="key">Used to retrieve the thumbnail from a dictionary if we are asked for /// the same one repeatedly</param> /// <param name="document">Whose rendering will produce the thumbnail content.</param> /// <param name="options"></param> /// <returns></returns> public Image GetThumbnail(string key, HtmlDom document, ThumbnailOptions options) { Image image; Image thumbnail = null; lock (this) { //In our cache? if (!options.MustRegenerate && !String.IsNullOrWhiteSpace(key) && _images.TryGetValue(key, out image)) { Debug.WriteLine("Thumbnail Cache HIT: " + key + " thread=" + Thread.CurrentThread.ManagedThreadId); return(image); } Debug.WriteLine("Thumbnail Cache MISS: " + key + " thread=" + Thread.CurrentThread.ManagedThreadId); _backgroundColorOfResult = options.BackgroundColor; XmlHtmlConverter.MakeXmlishTagsSafeForInterpretationAsHtml(document.RawDom); var browser = GetBrowserForPaperSize(document.RawDom); if (browser == null) { return(Resources.PagePlaceHolder); } var order = new ThumbnailOrder() { Options = options, Document = document }; for (int i = 0; i < 4; i++) { if (CreateThumbNail(order, browser, out thumbnail)) { break; } // For some reason...possibly another navigation was in progress...we can't do this just now. // Try a few times. } if (thumbnail == null) // just can't get it. { return(Resources.PagePlaceHolder); // but don't save it...try again if we get another request. } if (!String.IsNullOrWhiteSpace(key)) { _images[key] = thumbnail; } } return(thumbnail); }
void Application_Idle(object sender, EventArgs e) { if (_orders.Count > 0) { ThumbnailOrder thumbnailOrder = _orders.Dequeue(); try { ProcessOrder(thumbnailOrder); } catch (Exception error) { //putting up a green box here, because, say, the page was messed up, is bad manners Logger.WriteEvent("HtmlThumbNailer reported exception:{0}", error.Message); thumbnailOrder.ErrorCallback(error); } } }
void _browser_Navigated(object sender, GeckoNavigatedEventArgs e) { ThumbnailOrder order = (ThumbnailOrder)((GeckoWebBrowser)sender).Tag; order.Done = true; }
void ProcessOrder(ThumbnailOrder order) { //e.Result = order; //Thread.CurrentThread.Name = "Thumbnailer" + order.Key; Image pendingThumbnail = null; lock (this) { Logger.WriteMinorEvent("HtmlThumbNailer: starting work on thumbnail ({0})", order.ThumbNailFilePath); _backgroundColorOfResult = order.BackgroundColorOfResult; XmlHtmlConverter.MakeXmlishTagsSafeForInterpretationAsHtml(order.Document); var browser = GetBrowserForPaperSize(order.Document); lock (browser) { using (var temp = TempFile.CreateHtm5FromXml(order.Document)) { order.Done = false; browser.Tag = order; browser.Navigate(temp.Path); var stopTime = DateTime.Now.AddSeconds(5); while (!_disposed && (!order.Done || browser.Document.ActiveElement == null) && DateTime.Now < stopTime) { Application.DoEvents(); //TODO: avoid this //Thread.Sleep(100); } if (_disposed) { return; } if (!order.Done) { Logger.WriteEvent("HtmlThumNailer: Timed out on ({0})", order.ThumbNailFilePath); Debug.Fail("(debug only) Make thumbnail timed out"); return; } Guard.AgainstNull(browser.Document.ActiveElement, "browser.Document.ActiveElement"); /* saw crash here, shortly after startup: * 1) opened an existing book * 2) added a title * 3) added a dog from aor * 4) got this crash * at Gecko.nsIDOMXPathEvaluator.CreateNSResolver(nsIDOMNode nodeResolver) * at Gecko.GeckoNode.GetElements(String xpath) in C:\dev\geckofx11hatton\Skybound.Gecko\DOM\GeckoNode.cs:line 222 * at Bloom.HtmlThumbNailer.ProcessOrder(ThumbnailOrder order) in C:\dev\Bloom\src\BloomExe\HtmlThumbNailer.cs:line 167 * at Bloom.HtmlThumbNailer.Application_Idle(Object sender, EventArgs e) in C:\dev\Bloom\src\BloomExe\HtmlThumbNailer.cs:line 53 */ var div = browser.Document.ActiveElement.GetElements("//div[contains(@class, 'bloom-page')]").FirstOrDefault(); if (div == null) { Logger.WriteEvent("HtmlThumNailer: found no div with a class of bloom-Page ({0})", order.ThumbNailFilePath); throw new ApplicationException("thumbnails found no div with a class of bloom-Page"); } browser.Height = div.ClientHeight; browser.Width = div.ClientWidth; try { Logger.WriteMinorEvent("HtmlThumNailer: browser.GetBitmap({0},{1})", browser.Width, (uint)browser.Height); //BUG (April 2013) found that the initial call to GetBitMap always had a zero width, leading to an exception which //the user doesn't see and then all is well. So at the moment, we avoid the exception, and just leave with //the placeholder thumbnail. if (browser.Width == 0 || browser.Height == 0) { var paperSizeName = GetPaperSizeName(order.Document); throw new ApplicationException( "Problem getting thumbnail browser for document with Paper Size: " + paperSizeName); } var docImage = browser.GetBitmap((uint)browser.Width, (uint)browser.Height); Logger.WriteMinorEvent(" HtmlThumNailer: finished GetBitmap("); #if DEBUG // docImage.Save(@"c:\dev\temp\zzzz.bmp"); #endif if (_disposed) { return; } pendingThumbnail = MakeThumbNail(docImage, _sizeInPixels, _sizeInPixels, Color.Transparent, order.DrawBorderDashed); } catch (Exception error) { #if DEBUG Debug.Fail(error.Message); #endif Logger.WriteEvent("HtmlThumNailer got " + error.Message); Logger.WriteEvent("Disposing of all browsers in hopes of getting a fresh start on life"); foreach (var browserCacheForDifferentPaperSize in _browserCacheForDifferentPaperSizes) { try { Logger.WriteEvent("Disposing of browser {0}", browserCacheForDifferentPaperSize.Key); browserCacheForDifferentPaperSize.Value.Dispose(); } catch (Exception e2) { Logger.WriteEvent("While trying to dispose of thumbnailer browsers as a result of an exception, go another: " + e2.Message); } } _browserCacheForDifferentPaperSizes.Clear(); } } } if (pendingThumbnail == null) { pendingThumbnail = Resources.PagePlaceHolder; } else if (!string.IsNullOrEmpty(order.ThumbNailFilePath)) { try { //gives a blank _pendingThumbnail.Save(thumbNailFilePath); using (Bitmap b = new Bitmap(pendingThumbnail)) { b.Save(order.ThumbNailFilePath); } } catch (Exception) { //this is going to fail if we don't have write permission } } pendingThumbnail.Tag = order.ThumbNailFilePath; //usefull if we later know we need to clear out that file Debug.WriteLine("THumbnail browser ({0},{1})", browser.Width, browser.Height); try //I saw a case where this threw saying that the key was already in there, even though back at the beginning of this function, it wasn't. { if (_images.ContainsKey(order.Key)) { _images.Remove(order.Key); } _images.Add(order.Key, pendingThumbnail); } catch (Exception error) { Logger.WriteMinorEvent("Skipping minor error: " + error.Message); //not worth crashing over, at this point in Bloom's life, since it's just a cache. But since then, I did add a lock() around all this. } } //order.ResultingThumbnail = pendingThumbnail; if (_disposed) { return; } Logger.WriteMinorEvent("HtmlThumNailer: finished work on thumbnail ({0})", order.ThumbNailFilePath); order.Callback(pendingThumbnail); }
/// <summary> /// Returns true if it make some attempt at an image, false if navigation is currently suppressed. /// </summary> /// <param name="order"></param> /// <param name="browser"></param> /// <param name="thumbnail"></param> /// <returns></returns> private bool CreateThumbNail(ThumbnailOrder order, GeckoWebBrowser browser, out Image thumbnail) { // runs on threadpool thread thumbnail = null; using (var temp = TempFileUtils.CreateHtm5FromXml(order.Document)) { order.Done = false; browser.Tag = order; if (!OpenTempFileInBrowser(browser, temp.Path)) { return(false); } var browserSize = SetWidthAndHeight(browser); if (browserSize.Height == 0) //happens when we run into the as-yet-unreproduced-or-fixed bl-254 { return(false); // will try again later } try { Logger.WriteMinorEvent("HtmlThumNailer ({2}): browser.GetBitmap({0},{1})", browserSize.Width, (uint)browserSize.Height, Thread.CurrentThread.ManagedThreadId); //BUG (April 2013) found that the initial call to GetBitMap always had a zero width, leading to an exception which //the user doesn't see and then all is well. So at the moment, we avoid the exception, and just leave with //the placeholder thumbnail. if (browserSize.Width == 0 || browserSize.Height == 0) { var paperSizeName = GetPaperSizeName(order.Document); throw new ApplicationException("Problem getting thumbnail browser for document with Paper Size: " + paperSizeName); } using (Image fullsizeImage = CreateImage(browser)) { if (_disposed) { return(false); } thumbnail = MakeThumbNail(fullsizeImage, order.Options); return(true); } } catch (Exception error) { Logger.WriteEvent("HtmlThumbNailer ({0}) got {1}", Thread.CurrentThread.ManagedThreadId, error.Message); Logger.WriteEvent("Disposing of all browsers in hopes of getting a fresh start on life"); foreach (var browserCacheForDifferentPaperSize in _browserCacheForDifferentPaperSizes) { try { Logger.WriteEvent("Disposing of browser {0}", browserCacheForDifferentPaperSize.Key); browserCacheForDifferentPaperSize.Value.Dispose(); } catch (Exception e2) { Logger.WriteEvent( "While trying to dispose of thumbnailer browsers as a result of an exception, go another: " + e2.Message); } } _browserCacheForDifferentPaperSizes.Clear(); #if DEBUG _syncControl.Invoke((Action)(() => Debug.Fail(error.Message))); #endif } } return(false); }
/// <summary> /// Returns true if it make some attempt at an image, false if navigation is currently suppressed. /// </summary> /// <param name="order"></param> /// <param name="browser"></param> /// <param name="thumbnail"></param> /// <returns></returns> private bool CreateThumbNail(ThumbnailOrder order, GeckoWebBrowser browser, out Image thumbnail) { // runs on threadpool thread _currentOrder = order; thumbnail = null; using (var temp = BloomServer.MakeSimulatedPageFileInBookFolder(order.Document, source: BloomServer.SimulatedPageFileSource.Thumb)) { order.Done = false; browser.Tag = order; Color coverColor; ImageUtils.TryCssColorFromString(Book.Book.GetCoverColorFromDom(order.Document.RawDom), out coverColor); if (!OpenTempFileInBrowser(browser, temp.Key)) { return(false); } var browserSize = SetWidthAndHeight(browser); if (browserSize.Height == 0) //happens when we run into the as-yet-unreproduced-or-fixed bl-254 { return(false); // will try again later } try { Logger.WriteMinorEvent("HtmlThumNailer ({2}): browser.GetBitmap({0},{1})", browserSize.Width, (uint)browserSize.Height, Thread.CurrentThread.ManagedThreadId); //BUG (April 2013) found that the initial call to GetBitMap always had a zero width, leading to an exception which //the user doesn't see and then all is well. So at the moment, we avoid the exception, and just leave with //the placeholder thumbnail. if (browserSize.Width == 0 || browserSize.Height == 0) { var paperSizeName = GetPaperSizeName(order.Document.RawDom); throw new ApplicationException("Problem getting thumbnail browser for document with Paper Size: " + paperSizeName); } int topOfCoverImage = -1; int bottomOfCoverImage = -1; _syncControl.Invoke((Action)(() => { Guard.AgainstNull(browser.Document.ActiveElement, "browser.Document.ActiveElement"); var div = browser.Document.ActiveElement.EvaluateXPath("//div[contains(@class, 'bloom-imageContainer')]").GetNodes().FirstOrDefault() as GeckoHtmlElement; if (div != null) { // Note that div.GetBoundingClientRect() returns nothing useful in Geckofx60: these values appear to work okay. topOfCoverImage = div.OffsetTop; bottomOfCoverImage = topOfCoverImage + div.OffsetHeight; } })); using (Image fullsizeImage = CreateImage(browser, coverColor, topOfCoverImage, bottomOfCoverImage)) { if (_disposed) { return(false); } thumbnail = MakeThumbNail(fullsizeImage, order.Options); return(true); } } catch (Exception error) { Logger.WriteEvent("HtmlThumbNailer ({0}) got {1}", Thread.CurrentThread.ManagedThreadId, error.Message); Logger.WriteEvent("Disposing of all browsers in hopes of getting a fresh start on life"); foreach (var browserCacheForDifferentPaperSize in _browserCacheForDifferentPaperSizes) { try { Logger.WriteEvent("Disposing of browser {0}", browserCacheForDifferentPaperSize.Key); browserCacheForDifferentPaperSize.Value.Dispose(); } catch (Exception e2) { Logger.WriteEvent( "While trying to dispose of thumbnailer browsers as a result of an exception, go another: " + e2.Message); } } _browserCacheForDifferentPaperSizes.Clear(); #if DEBUG _syncControl.Invoke((Action)(() => Debug.Fail(error.Message))); #endif } } return(false); }
private void QueueOrder(ThumbnailOrder order) { ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessOrder), order); }
/// <summary> /// Returns true if it make some attempt at an image, false if navigation is currently suppressed. /// </summary> /// <param name="order"></param> /// <param name="browser"></param> /// <param name="thumbnail"></param> /// <returns></returns> private bool CreateThumbNail(ThumbnailOrder order, GeckoWebBrowser browser, out Image thumbnail) { // runs on threadpool thread thumbnail = null; using (var temp = EnhancedImageServer.MakeSimulatedPageFileInBookFolder(order.Document)) { order.Done = false; browser.Tag = order; if (!OpenTempFileInBrowser(browser, temp.Key)) { return(false); } var browserSize = SetWidthAndHeight(browser); if (browserSize.Height == 0) //happens when we run into the as-yet-unreproduced-or-fixed bl-254 { return(false); // will try again later } try { Logger.WriteMinorEvent("HtmlThumNailer ({2}): browser.GetBitmap({0},{1})", browserSize.Width, (uint)browserSize.Height, Thread.CurrentThread.ManagedThreadId); // This short sleep was added to fix BL-4170, a problem where thumbnails were often generated without // images. We're not sure what changed or why it became necessary. Possibly there was a change in GeckoFx 45 // which caused it report document-complete before background images are sufficiently loaded to show up // in a captured image. Testing indicated that our image server did finish returning the background // images before the document-complete notification, but that doesn't prove they were fully rendered. // Also casting doubt on this hypothesis, we tried delays (some much longer) in several seemingly // more logical places where they didn't help, at least not without being unreasonably long. // The length of the delay is of course problematic. Experiments on my fast desktop indicate that 10ms // does not help; 20ms is enough for AOR images; 40ms for a 6mp/4M jpg photo; but somewhere between // 100 and 200ms can be needed by a really large (24mp/14M) image. 200ms is based on hoping that // most computers are no worse than five times slower than mine and accepting that the slower ones // might have problems with huge images. Thread.Sleep(200); //BUG (April 2013) found that the initial call to GetBitMap always had a zero width, leading to an exception which //the user doesn't see and then all is well. So at the moment, we avoid the exception, and just leave with //the placeholder thumbnail. if (browserSize.Width == 0 || browserSize.Height == 0) { var paperSizeName = GetPaperSizeName(order.Document.RawDom); throw new ApplicationException("Problem getting thumbnail browser for document with Paper Size: " + paperSizeName); } using (Image fullsizeImage = CreateImage(browser)) { if (_disposed) { return(false); } thumbnail = MakeThumbNail(fullsizeImage, order.Options); return(true); } } catch (Exception error) { Logger.WriteEvent("HtmlThumbNailer ({0}) got {1}", Thread.CurrentThread.ManagedThreadId, error.Message); Logger.WriteEvent("Disposing of all browsers in hopes of getting a fresh start on life"); foreach (var browserCacheForDifferentPaperSize in _browserCacheForDifferentPaperSizes) { try { Logger.WriteEvent("Disposing of browser {0}", browserCacheForDifferentPaperSize.Key); browserCacheForDifferentPaperSize.Value.Dispose(); } catch (Exception e2) { Logger.WriteEvent( "While trying to dispose of thumbnailer browsers as a result of an exception, go another: " + e2.Message); } } _browserCacheForDifferentPaperSizes.Clear(); #if DEBUG _syncControl.Invoke((Action)(() => Debug.Fail(error.Message))); #endif } } return(false); }
/// <summary> /// Returns true if it make some attempt at an image, false if navigation is currently suppressed. /// </summary> /// <param name="order"></param> /// <param name="browser"></param> /// <param name="thumbnail"></param> /// <returns></returns> private bool CreateThumbNail(ThumbnailOrder order, GeckoWebBrowser browser, out Image thumbnail) { // runs on threadpool thread thumbnail = null; using (var temp = EnhancedImageServer.MakeSimulatedPageFileInBookFolder(order.Document)) { order.Done = false; browser.Tag = order; if (!OpenTempFileInBrowser(browser, temp.Key)) return false; var browserSize = SetWidthAndHeight(browser); if (browserSize.Height == 0) //happens when we run into the as-yet-unreproduced-or-fixed bl-254 return false; // will try again later try { Logger.WriteMinorEvent("HtmlThumNailer ({2}): browser.GetBitmap({0},{1})", browserSize.Width, (uint) browserSize.Height, Thread.CurrentThread.ManagedThreadId); //BUG (April 2013) found that the initial call to GetBitMap always had a zero width, leading to an exception which //the user doesn't see and then all is well. So at the moment, we avoid the exception, and just leave with //the placeholder thumbnail. if (browserSize.Width == 0 || browserSize.Height == 0) { var paperSizeName = GetPaperSizeName(order.Document.RawDom); throw new ApplicationException("Problem getting thumbnail browser for document with Paper Size: " + paperSizeName); } using (Image fullsizeImage = CreateImage(browser)) { if (_disposed) return false; thumbnail = MakeThumbNail(fullsizeImage, order.Options); return true; } } catch (Exception error) { Logger.WriteEvent("HtmlThumbNailer ({0}) got {1}", Thread.CurrentThread.ManagedThreadId, error.Message); Logger.WriteEvent("Disposing of all browsers in hopes of getting a fresh start on life"); foreach (var browserCacheForDifferentPaperSize in _browserCacheForDifferentPaperSizes) { try { Logger.WriteEvent("Disposing of browser {0}", browserCacheForDifferentPaperSize.Key); browserCacheForDifferentPaperSize.Value.Dispose(); } catch (Exception e2) { Logger.WriteEvent( "While trying to dispose of thumbnailer browsers as a result of an exception, go another: " + e2.Message); } } _browserCacheForDifferentPaperSizes.Clear(); #if DEBUG _syncControl.Invoke((Action) (() => Debug.Fail(error.Message))); #endif } } return false; }
/// <summary> /// /// </summary> /// <param name="key">whatever system you want... just used for caching</param> /// <param name="document"></param> /// <param name="backgroundColorOfResult">use Color.Transparent if you'll be composing in onto something else</param> /// <param name="drawBorderDashed"></param> /// <returns></returns> public void GetThumbnail(string folderForThumbNailCache, string key, HtmlDom document, ThumbnailOptions options, Action<Image> callback, Action<Exception> errorCallback, bool async) { //review: old code had it using "key" in one place(checking for existing), thumbNailFilePath in another (adding new) string thumbNailFilePath = null; if (!string.IsNullOrEmpty(folderForThumbNailCache)) thumbNailFilePath = Path.Combine(folderForThumbNailCache, options.FileName); //In our cache? Image image; if (!String.IsNullOrWhiteSpace(key) && _images.TryGetValue(key, out image)) { callback(image); return; } //Sitting on disk? if (!string.IsNullOrEmpty(folderForThumbNailCache)) { if (RobustFile.Exists(thumbNailFilePath)) { var thumbnail = ImageUtils.GetImageFromFile(thumbNailFilePath); thumbnail.Tag = thumbNailFilePath; if (!String.IsNullOrWhiteSpace(key)) _images.Add(key, thumbnail); callback(thumbnail); return; } } var order = new ThumbnailOrder() { ThumbNailFilePath = thumbNailFilePath, Options = options, Callback = callback, ErrorCallback = errorCallback, Document = document, FolderForThumbNailCache = folderForThumbNailCache, Key = key }; if (async) QueueOrder(order); else { ProcessOrder(order); } }
/// <summary> /// A synchronous version of getting thumbnails, currently used by the image server /// to get thumbnails that are used in the add page dialog. /// </summary> /// <param name="key">Used to retrieve the thumbnail from a dictionary if we are asked for /// the same one repeatedly</param> /// <param name="document">Whose rendering will produce the thumbnail content.</param> /// <param name="options"></param> /// <returns></returns> public Image GetThumbnail(string key, HtmlDom document, ThumbnailOptions options) { Image image; Image thumbnail = null; lock (this) { //In our cache? if (!String.IsNullOrWhiteSpace(key) && _images.TryGetValue(key, out image)) { Debug.WriteLine("Thumbnail Cache HIT: "+ key + " thread=" + Thread.CurrentThread.ManagedThreadId); return image; } Debug.WriteLine("Thumbnail Cache MISS: " + key + " thread=" + Thread.CurrentThread.ManagedThreadId); _backgroundColorOfResult = options.BackgroundColor; XmlHtmlConverter.MakeXmlishTagsSafeForInterpretationAsHtml(document.RawDom); var browser = GetBrowserForPaperSize(document.RawDom); if (browser == null) return Resources.PagePlaceHolder; var order = new ThumbnailOrder() { Options = options, Document = document }; for (int i = 0; i < 4; i++) { if (CreateThumbNail(order, browser, out thumbnail)) break; // For some reason...possibly another navigation was in progress...we can't do this just now. // Try a few times. } if (thumbnail == null) // just can't get it. { return Resources.PagePlaceHolder; // but don't save it...try again if we get another request. } if (!String.IsNullOrWhiteSpace(key)) _images[key] = thumbnail; } return thumbnail; }
void ProcessOrder(ThumbnailOrder order) { //e.Result = order; //Thread.CurrentThread.Name = "Thumbnailer" + order.Key; Image pendingThumbnail = null; lock (this) { Logger.WriteMinorEvent("HtmlThumbNailer: starting work on thumbnail ({0})", order.ThumbNailFilePath); _backgroundColorOfResult = order.BackgroundColorOfResult; XmlHtmlConverter.MakeXmlishTagsSafeForInterpretationAsHtml(order.Document); var browser = GetBrowserForPaperSize(order.Document); lock (browser) { using (var temp = TempFile.CreateHtm5FromXml(order.Document)) { order.Done = false; browser.Tag = order; browser.Navigate(temp.Path); var stopTime = DateTime.Now.AddSeconds(5); while (!_disposed && (!order.Done || browser.Document.ActiveElement == null )&& DateTime.Now < stopTime) { Application.DoEvents(); //TODO: avoid this //Thread.Sleep(100); } if (_disposed) return; if (!order.Done) { Logger.WriteEvent("HtmlThumNailer: Timed out on ({0})", order.ThumbNailFilePath); Debug.Fail("(debug only) Make thumbnail timed out"); return; } Guard.AgainstNull(browser.Document.ActiveElement, "browser.Document.ActiveElement"); /* saw crash here, shortly after startup: * 1) opened an existing book * 2) added a title * 3) added a dog from aor * 4) got this crash * at Gecko.nsIDOMXPathEvaluator.CreateNSResolver(nsIDOMNode nodeResolver) at Gecko.GeckoNode.GetElements(String xpath) in C:\dev\geckofx11hatton\Skybound.Gecko\DOM\GeckoNode.cs:line 222 at Bloom.HtmlThumbNailer.ProcessOrder(ThumbnailOrder order) in C:\dev\Bloom\src\BloomExe\HtmlThumbNailer.cs:line 167 at Bloom.HtmlThumbNailer.Application_Idle(Object sender, EventArgs e) in C:\dev\Bloom\src\BloomExe\HtmlThumbNailer.cs:line 53 */ var div = browser.Document.ActiveElement.GetElements("//div[contains(@class, 'bloom-page')]").FirstOrDefault(); if (div == null) { Logger.WriteEvent("HtmlThumNailer: found no div with a class of bloom-Page ({0})", order.ThumbNailFilePath); throw new ApplicationException("thumbnails found no div with a class of bloom-Page"); } browser.Height = div.ClientHeight; browser.Width = div.ClientWidth; try { Logger.WriteMinorEvent("HtmlThumNailer: browser.GetBitmap({0},{1})", browser.Width, (uint) browser.Height); //BUG (April 2013) found that the initial call to GetBitMap always had a zero width, leading to an exception which //the user doesn't see and then all is well. So at the moment, we avoid the exception, and just leave with //the placeholder thumbnail. if (browser.Width == 0 || browser.Height == 0) { var paperSizeName = GetPaperSizeName(order.Document); throw new ApplicationException( "Problem getting thumbnail browser for document with Paper Size: " + paperSizeName); } var docImage = browser.GetBitmap((uint) browser.Width, (uint) browser.Height); Logger.WriteMinorEvent(" HtmlThumNailer: finished GetBitmap("); #if DEBUG // docImage.Save(@"c:\dev\temp\zzzz.bmp"); #endif if (_disposed) return; pendingThumbnail = MakeThumbNail(docImage, _sizeInPixels, _sizeInPixels, Color.Transparent, order.DrawBorderDashed); } catch (Exception error) { #if DEBUG Debug.Fail(error.Message); #endif Logger.WriteEvent("HtmlThumNailer got " + error.Message); Logger.WriteEvent("Disposing of all browsers in hopes of getting a fresh start on life"); foreach (var browserCacheForDifferentPaperSize in _browserCacheForDifferentPaperSizes) { try { Logger.WriteEvent("Disposing of browser {0}", browserCacheForDifferentPaperSize.Key); browserCacheForDifferentPaperSize.Value.Dispose(); } catch (Exception e2) { Logger.WriteEvent("While trying to dispose of thumbnailer browsers as a result of an exception, go another: " + e2.Message); } } _browserCacheForDifferentPaperSizes.Clear(); } } } if (pendingThumbnail == null) { pendingThumbnail = Resources.PagePlaceHolder; } else if (!string.IsNullOrEmpty(order.ThumbNailFilePath)) { try { //gives a blank _pendingThumbnail.Save(thumbNailFilePath); using (Bitmap b = new Bitmap(pendingThumbnail)) { b.Save(order.ThumbNailFilePath); } } catch (Exception) { //this is going to fail if we don't have write permission } } pendingThumbnail.Tag = order.ThumbNailFilePath; //usefull if we later know we need to clear out that file Debug.WriteLine("THumbnail browser ({0},{1})", browser.Width, browser.Height); try //I saw a case where this threw saying that the key was already in there, even though back at the beginning of this function, it wasn't. { if (_images.ContainsKey(order.Key)) _images.Remove(order.Key); _images.Add(order.Key, pendingThumbnail); } catch (Exception error) { Logger.WriteMinorEvent("Skipping minor error: " + error.Message); //not worth crashing over, at this point in Bloom's life, since it's just a cache. But since then, I did add a lock() around all this. } } //order.ResultingThumbnail = pendingThumbnail; if (_disposed) return; Logger.WriteMinorEvent("HtmlThumNailer: finished work on thumbnail ({0})", order.ThumbNailFilePath); order.Callback(pendingThumbnail); }