From 2a1090ffa81f14709b3d7786a4cfc4f539dd9dd2 Mon Sep 17 00:00:00 2001 From: Aiosa Date: Sun, 19 Nov 2023 16:14:28 +0100 Subject: [PATCH] Fix wrong test comparison. Add equality comparator to TileSource API. Return deprecated support for getCompletionCallback. Turn on zombie cache if sources replaced & equal. --- src/dzitilesource.js | 8 ++++++++ src/iiiftilesource.js | 7 +++++++ src/imagetilesource.js | 7 +++++++ src/legacytilesource.js | 15 +++++++++++++++ src/osmtilesource.js | 7 +++++++ src/tiledimage.js | 33 ++++++++++++++++++++++++++------- src/tilesource.js | 11 +++++++++++ src/tmstilesource.js | 7 +++++++ src/viewer.js | 10 +++++++--- src/zoomifytilesource.js | 7 +++++++ test/modules/tilecache.js | 12 ++---------- 11 files changed, 104 insertions(+), 20 deletions(-) diff --git a/src/dzitilesource.js b/src/dzitilesource.js index 57b9f5a3..492bedec 100644 --- a/src/dzitilesource.js +++ b/src/dzitilesource.js @@ -167,6 +167,14 @@ $.extend( $.DziTileSource.prototype, $.TileSource.prototype, /** @lends OpenSead }, + /** + * Equality comparator + */ + equals: function(otherSource) { + return this.tilesUrl === otherSource.tilesUrl; + }, + + /** * @function * @param {Number} level diff --git a/src/iiiftilesource.js b/src/iiiftilesource.js index 54ec073f..1ef8b80a 100644 --- a/src/iiiftilesource.js +++ b/src/iiiftilesource.js @@ -503,6 +503,13 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea return uri; }, + /** + * Equality comparator + */ + equals: function(otherSource) { + return this._id === otherSource._id; + }, + __testonly__: { canBeTiled: canBeTiled, constructLevels: constructLevels diff --git a/src/imagetilesource.js b/src/imagetilesource.js index 7c4b571e..c5990266 100644 --- a/src/imagetilesource.js +++ b/src/imagetilesource.js @@ -180,6 +180,13 @@ $.ImageTileSource = class extends $.TileSource { return `${this.url}?l=${level}&x=${x}&y=${y}`; } + /** + * Equality comparator + */ + equals(otherSource) { + return this.url === otherSource.url; + } + getTilePostData(level, x, y) { return {level: level, x: x, y: y}; } diff --git a/src/legacytilesource.js b/src/legacytilesource.js index e80b931f..3ddb6122 100644 --- a/src/legacytilesource.js +++ b/src/legacytilesource.js @@ -187,6 +187,21 @@ $.extend( $.LegacyTileSource.prototype, $.TileSource.prototype, /** @lends OpenS url = this.levels[ level ].url; } return url; + }, + + /** + * Equality comparator + */ + equals: function (otherSource) { + if (!otherSource.levels || otherSource.levels.length !== this.levels.length) { + return false; + } + for (let i = this.minLevel; i <= this.maxLevel; i++) { + if (this.levels[i].url !== otherSource.levels[i].url) { + return false; + } + } + return true; } } ); diff --git a/src/osmtilesource.js b/src/osmtilesource.js index 43e525ab..5a380d71 100644 --- a/src/osmtilesource.js +++ b/src/osmtilesource.js @@ -139,6 +139,13 @@ $.extend( $.OsmTileSource.prototype, $.TileSource.prototype, /** @lends OpenSead */ getTileUrl: function( level, x, y ) { return this.tilesUrl + (level - 8) + "/" + x + "/" + y + ".png"; + }, + + /** + * Equality comparator + */ + equals: function(otherSource) { + return this.tilesUrl === otherSource.tilesUrl; } }); diff --git a/src/tiledimage.js b/src/tiledimage.js index 042cb61c..5fe09c26 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -1785,13 +1785,19 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag // -> reason why it is not in the constructor tile.setCache(tile.cacheKey, data, dataType, false, cutoff); - let resolver = null; + let resolver = null, + increment = 0, + eventFinished = false; const _this = this, finishPromise = new $.Promise(r => { resolver = r; }); function completionCallback() { + increment--; + if (increment > 0) { + return; + } //do not override true if set (false is default) tile.hasTransparency = tile.hasTransparency || _this.source.hasTransparency( undefined, tile.getUrl(), tile.ajaxHeaders, tile.postData @@ -1823,6 +1829,17 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag tile.save(); } + function getCompletionCallback() { + if (eventFinished) { + $.console.error("Event 'tile-loaded' argument getCompletionCallback must be called synchronously. " + + "Its return value should be called asynchronously."); + } + increment++; + return completionCallback; + } + + const fallbackCompletion = getCompletionCallback(); + /** * Triggered when a tile has just been loaded in memory. That means that the * image has been downloaded and can be modified before being drawn to the canvas. @@ -1841,7 +1858,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag * @property {OpenSeadragon.Promise} - Promise resolved when the tile gets fully loaded. * @property {function} getCompletionCallback - deprecated */ - const promise = this.viewer.raiseEventAwaiting("tile-loaded", { + this.viewer.raiseEventAwaiting("tile-loaded", { tile: tile, tiledImage: this, tileRequest: tileRequest, @@ -1855,13 +1872,15 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag return data; }, getCompletionCallback: function () { - $.console.error("[tile-loaded] getCompletionCallback is not supported: it is compulsory to handle the event with async functions if applicable."); + $.console.error("[tile-loaded] getCompletionCallback is deprecated: it introduces race conditions: " + + "use async event handlers instead, execution order is deducted by addHandler(...) priority"); + return getCompletionCallback(); }, - }); - promise.then(completionCallback).catch(() => { + }).catch(() => { $.console.error("[tile-loaded] event finished with failure: there might be a problem with a plugin you are using."); - completionCallback(); - }); + }).then(() => { + eventFinished = true; + }).then(fallbackCompletion); }, /** diff --git a/src/tilesource.js b/src/tilesource.js index 0accb534..d9b6e35a 100644 --- a/src/tilesource.js +++ b/src/tilesource.js @@ -591,6 +591,17 @@ $.TileSource.prototype = { return false; }, + /** + * Check whether two tileSources are equal. This is used for example + * when replacing tile-sources, which turns on the zombie cache before + * old item removal. + * @param {OpenSeadragon.TileSource} otherSource + * @returns {Boolean} + */ + equals: function (otherSource) { + return false; + }, + /** * Responsible for parsing and configuring the * image metadata pertinent to this TileSources implementation. diff --git a/src/tmstilesource.js b/src/tmstilesource.js index b6deeb03..cb866f4e 100644 --- a/src/tmstilesource.js +++ b/src/tmstilesource.js @@ -131,6 +131,13 @@ $.extend( $.TmsTileSource.prototype, $.TileSource.prototype, /** @lends OpenSead var yTiles = this.getNumTiles( level ).y - 1; return this.tilesUrl + level + "/" + x + "/" + (yTiles - y) + ".png"; + }, + + /** + * Equality comparator + */ + equals: function (otherSource) { + return this.tilesUrl === otherSource.tilesUrl; } }); diff --git a/src/viewer.js b/src/viewer.js index 36e61a0d..561ee212 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -1452,7 +1452,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, * A set of headers to include when making tile AJAX requests. * Note that these headers will be merged over any headers specified in {@link OpenSeadragon.Options}. * Specifying a falsy value for a header will clear its existing value set at the Viewer level (if any). - * @param {Function} [options.success] A function that gets called when the image is + * @param {Function} [options.success] A function tadhat gets called when the image is * successfully added. It's passed the event object which contains a single property: * "item", which is the resulting instance of TiledImage. * @param {Function} [options.error] A function that gets called if the image is @@ -1575,11 +1575,15 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, _this._loadQueue.splice(0, 1); if (queueItem.options.replace) { - var newIndex = _this.world.getIndexOfItem(queueItem.options.replaceItem); + const replaced = queueItem.options.replaceItem; + const newIndex = _this.world.getIndexOfItem(replaced); if (newIndex !== -1) { queueItem.options.index = newIndex; } - _this.world.removeItem(queueItem.options.replaceItem); + if (!replaced._zombieCache && replaced.source.equals(queueItem.tileSource)) { + replaced.allowZombieCache(true); + } + _this.world.removeItem(replaced); } tiledImage = new $.TiledImage({ diff --git a/src/zoomifytilesource.js b/src/zoomifytilesource.js index 5798d8eb..1da5eece 100644 --- a/src/zoomifytilesource.js +++ b/src/zoomifytilesource.js @@ -143,6 +143,13 @@ result = Math.floor(num / 256); return this.tilesUrl + 'TileGroup' + result + '/' + level + '-' + x + '-' + y + '.' + this.fileFormat; + }, + + /** + * Equality comparator + */ + equals: function (otherSource) { + return this.tilesUrl === otherSource.tilesUrl; } }); diff --git a/test/modules/tilecache.js b/test/modules/tilecache.js index f6f6ac7b..deb0eaf7 100644 --- a/test/modules/tilecache.js +++ b/test/modules/tilecache.js @@ -278,21 +278,13 @@ viewer.world.removeHandler('add-item', openHandler); viewer.world.addHandler('add-item', reopenHandler); - const oldCacheSize = event.item._tileCache._cachesLoadedCount + - event.item._tileCache._zombiesLoadedCount; - - waitFor(() => { + waitFor(() => { if (tilesFinished === jobCounter && event.item._fullyLoaded) { coverage = $.extend(true, {}, event.item.coverage); viewer.addTiledImage({ tileSource: '/test/data/testpattern.dzi', index: 0, - replace: true, - success: e => { - test.equal(oldCacheSize, e.item._tileCache._cachesLoadedCount + - e.item._tileCache._zombiesLoadedCount, - "Image replace should erase no cache with zombies."); - } + replace: true }); return true; }