/// <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);
        }
Exemple #6
0
        /// <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);
 }
Exemple #9
0
        /// <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);
        }
 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);
                    //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);
        }