From c3dec09d9c14ed8de0c9ea752b7288f01fdde3e3 Mon Sep 17 00:00:00 2001
From: Dragos Daian <daian.dragos@yahoo.com>
Date: Sun, 20 Nov 2022 12:27:51 +0100
Subject: [PATCH 1/5] add loop for re-trying failed tiles [Take 3]

---
 src/imageloader.js   | 23 +++++++++++++++++++++--
 src/openseadragon.js |  2 ++
 src/viewer.js        |  4 +++-
 3 files changed, 26 insertions(+), 3 deletions(-)

diff --git a/src/imageloader.js b/src/imageloader.js
index 91d33b50..2dbbb3a8 100644
--- a/src/imageloader.js
+++ b/src/imageloader.js
@@ -50,12 +50,14 @@
  * @param {Function} [options.callback] - Called once image has been downloaded.
  * @param {Function} [options.abort] - Called when this image job is aborted.
  * @param {Number} [options.timeout] - The max number of milliseconds that this image job may take to complete.
+ * @param {Number} [options.tries] - Actual number of the current try.
  */
 $.ImageJob = function(options) {
 
     $.extend(true, this, {
         timeout: $.DEFAULT_SETTINGS.timeout,
-        jobId: null
+        jobId: null,
+        tries: 0
     }, options);
 
     /**
@@ -87,6 +89,8 @@ $.ImageJob.prototype = {
      * @method
      */
     start: function() {
+        this.tries++;
+
         var self = this;
         var selfAbort = this.abort;
 
@@ -138,6 +142,7 @@ $.ImageLoader = function(options) {
         jobLimit:       $.DEFAULT_SETTINGS.imageLoaderLimit,
         timeout:        $.DEFAULT_SETTINGS.timeout,
         jobQueue:       [],
+        failedTiles:    [],
         jobsInProgress: 0
     }, options);
 
@@ -220,7 +225,8 @@ $.ImageLoader.prototype = {
 };
 
 /**
- * Cleans up ImageJob once completed.
+ * Cleans up ImageJob once completed. Restarts job after tileRetryDelay seconds if failed
+ * but max tileRetryMax times
  * @method
  * @private
  * @param loader - ImageLoader used to start job.
@@ -228,6 +234,9 @@ $.ImageLoader.prototype = {
  * @param callback - Called once cleanup is finished.
  */
 function completeJob(loader, job, callback) {
+    if (job.errorMsg != '' && job.image === null && job.tries < 1 + loader.tileRetryMax) {
+        loader.failedTiles.push(job);
+    }
     var nextJob;
 
     loader.jobsInProgress--;
@@ -238,6 +247,16 @@ function completeJob(loader, job, callback) {
         loader.jobsInProgress++;
     }
 
+    if (loader.tileRetryMax > 0 && loader.jobQueue.length === 0) {
+        if ((!loader.jobLimit || loader.jobsInProgress < loader.jobLimit) && loader.failedTiles.length > 0) {
+             nextJob = loader.failedTiles.shift();
+             setTimeout(function () {
+                 nextJob.start();
+             }, loader.tileRetryDelay);
+             loader.jobsInProgress++;
+         }
+     }
+
     callback(job.data, job.errorMsg, job.request);
 }
 
diff --git a/src/openseadragon.js b/src/openseadragon.js
index 45e4c8e7..5f40f070 100644
--- a/src/openseadragon.js
+++ b/src/openseadragon.js
@@ -1354,6 +1354,8 @@ function OpenSeadragon( options ){
             maxImageCacheCount:     200,
             timeout:                30000,
             useCanvas:              true,  // Use canvas element for drawing if available
+            tileRetryMax:           0,
+            tileRetryDelay:         2500,
 
             //INTERFACE RESOURCE SETTINGS
             prefixUrl:              "/images/",
diff --git a/src/viewer.js b/src/viewer.js
index d963bf14..be6fdef1 100644
--- a/src/viewer.js
+++ b/src/viewer.js
@@ -393,7 +393,9 @@ $.Viewer = function( options ) {
     // Create the image loader
     this.imageLoader = new $.ImageLoader({
         jobLimit: this.imageLoaderLimit,
-        timeout: options.timeout
+        timeout: options.timeout,
+        tileRetryMax: this.tileRetryMax,
+        tileRetryDelay: this.tileRetryDelay
     });
 
     // Create the tile cache

From 77bc130636ab01246739f1d92f93b710eed8002c Mon Sep 17 00:00:00 2001
From: Dragos Daian <daian.dragos@yahoo.com>
Date: Mon, 23 Jan 2023 19:49:43 +0100
Subject: [PATCH 2/5] Add tileRetryMax documentation.

---
 src/openseadragon.js | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/openseadragon.js b/src/openseadragon.js
index 5f40f070..5eb13a39 100644
--- a/src/openseadragon.js
+++ b/src/openseadragon.js
@@ -495,6 +495,9 @@
   * @property {Number} [timeout=30000]
   *     The max number of milliseconds that an image job may take to complete.
   *
+  * @property {Number} [tileRetryMax=0]
+  *     The max number of retries when a tile download fails. By default it's 0, so retries are disabled.
+  *
   * @property {Boolean} [useCanvas=true]
   *     Set to false to not use an HTML canvas element for image rendering even if canvas is supported.
   *

From 5d70a807da2814456dabc0159ad9c8b7c6a649ba Mon Sep 17 00:00:00 2001
From: Dragos Daian <daian.dragos@yahoo.com>
Date: Mon, 23 Jan 2023 19:54:51 +0100
Subject: [PATCH 3/5] fix build error

---
 src/imageloader.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/imageloader.js b/src/imageloader.js
index 2dbbb3a8..85ea7b4c 100644
--- a/src/imageloader.js
+++ b/src/imageloader.js
@@ -234,7 +234,7 @@ $.ImageLoader.prototype = {
  * @param callback - Called once cleanup is finished.
  */
 function completeJob(loader, job, callback) {
-    if (job.errorMsg != '' && job.image === null && job.tries < 1 + loader.tileRetryMax) {
+    if (job.errorMsg !== '' && job.image === null && job.tries < 1 + loader.tileRetryMax) {
         loader.failedTiles.push(job);
     }
     var nextJob;

From f0f12c459e678a36fb8364c6bf1e19181b19ced6 Mon Sep 17 00:00:00 2001
From: Dragos Daian <daian.dragos@yahoo.com>
Date: Mon, 23 Jan 2023 22:10:23 +0100
Subject: [PATCH 4/5] try fix with check for null and undefined

---
 src/imageloader.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/imageloader.js b/src/imageloader.js
index 85ea7b4c..365c5c79 100644
--- a/src/imageloader.js
+++ b/src/imageloader.js
@@ -234,7 +234,7 @@ $.ImageLoader.prototype = {
  * @param callback - Called once cleanup is finished.
  */
 function completeJob(loader, job, callback) {
-    if (job.errorMsg !== '' && job.image === null && job.tries < 1 + loader.tileRetryMax) {
+    if (job.errorMsg !== '' && (job.image === null || job.image === undefined) && job.tries < 1 + loader.tileRetryMax) {
         loader.failedTiles.push(job);
     }
     var nextJob;

From b4700d28bda8ac551e9372302f4a8a40604c235c Mon Sep 17 00:00:00 2001
From: Dragos Daian <daian.dragos@yahoo.com>
Date: Mon, 23 Jan 2023 22:16:05 +0100
Subject: [PATCH 5/5] Also add documentation for tileRetryDelay

---
 src/openseadragon.js | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/openseadragon.js b/src/openseadragon.js
index 5eb13a39..efcd087c 100644
--- a/src/openseadragon.js
+++ b/src/openseadragon.js
@@ -498,6 +498,9 @@
   * @property {Number} [tileRetryMax=0]
   *     The max number of retries when a tile download fails. By default it's 0, so retries are disabled.
   *
+  * @property {Number} [tileRetryDelay=2500]
+  *     Milliseconds to wait after each tile retry if tileRetryMax is set.
+  *
   * @property {Boolean} [useCanvas=true]
   *     Set to false to not use an HTML canvas element for image rendering even if canvas is supported.
   *