From 360f0d67968c2e36b9b5847a3b84c111b475f3b8 Mon Sep 17 00:00:00 2001 From: Aiosa <469130@mail.muni.cz> Date: Sun, 3 Mar 2024 14:50:01 +0100 Subject: [PATCH] Fix docs, commit before upstream merge. --- src/imageloader.js | 4 +- src/openseadragon.js | 4 +- src/tile.js | 104 ++++++++++++++++++++++++++++---------- src/tilecache.js | 4 +- src/tiledimage.js | 9 +--- src/tilesource.js | 16 ++++-- test/modules/tilecache.js | 20 ++++---- 7 files changed, 107 insertions(+), 54 deletions(-) diff --git a/src/imageloader.js b/src/imageloader.js index c07b6e22..87b79079 100644 --- a/src/imageloader.js +++ b/src/imageloader.js @@ -48,7 +48,7 @@ * @param {Boolean} [options.ajaxWithCredentials] - Whether to set withCredentials on AJAX requests. * @param {String} [options.crossOriginPolicy] - CORS policy to use for downloads * @param {String} [options.postData] - HTTP POST data (usually but not necessarily in k=v&k2=v2... form, - * see TileSource::getPostData) or null + * see TileSource::getTilePostData) or null * @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. @@ -193,7 +193,7 @@ $.ImageLoader.prototype = { * @param {String} [options.ajaxHeaders] - Headers to add to the image request if using AJAX. * @param {String|Boolean} [options.crossOriginPolicy] - CORS policy to use for downloads * @param {String} [options.postData] - POST parameters (usually but not necessarily in k=v&k2=v2... form, - * see TileSource::getPostData) or null + * see TileSource::getTilePostData) or null * @param {Boolean} [options.ajaxWithCredentials] - Whether to set withCredentials on AJAX * requests. * @param {Function} [options.callback] - Called once image has been downloaded. diff --git a/src/openseadragon.js b/src/openseadragon.js index 0f272b84..e94ad8e8 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -2383,7 +2383,7 @@ function OpenSeadragon( options ){ * @param {Object} options.headers - headers to add to the AJAX request * @param {String} options.responseType - the response type of the AJAX request * @param {String} options.postData - HTTP POST data (usually but not necessarily in k=v&k2=v2... form, - * see TileSource::getPostData), GET method used if null + * see TileSource::getTilePostData), GET method used if null * @param {Boolean} [options.withCredentials=false] - whether to set the XHR's withCredentials * @throws {Error} * @returns {XMLHttpRequest} @@ -2697,7 +2697,7 @@ function OpenSeadragon( options ){ //@private, runs tile update event invalidateTile: function(tile, image, tStamp, viewer, i = -1) { - console.log(i, "tile: process", tile); + //console.log(i, "tile: process", tile); //todo consider also ability to cut execution of ongoing event if outdated by providing comparison timestamp viewer.raiseEventAwaiting('tile-needs-update', { diff --git a/src/tile.js b/src/tile.js index 7789448a..4a19d103 100644 --- a/src/tile.js +++ b/src/tile.js @@ -53,7 +53,7 @@ * drawing operation, in pixels. Note that this only works when drawing with canvas; when drawing * with HTML the entire tile is always used. * @param {String} postData HTTP POST data (usually but not necessarily in k=v&k2=v2... form, - * see TileSource::getPostData) or null + * see TileSource::getTilePostData) or null * @param {String} cacheKey key to act as a tile cache, must be unique for tiles with unique image data */ $.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, ajaxHeaders, sourceBounds, postData, cacheKey) { @@ -112,7 +112,7 @@ $.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, aja * Post parameters for this tile. For example, it can be an URL-encoded string * in k1=v1&k2=v2... format, or a JSON, or a FormData instance... or null if no POST request used * @member {String} postData HTTP POST data (usually but not necessarily in k=v&k2=v2... form, - * see TileSource::getPostData) or null + * see TileSource::getTilePostData) or null * @memberof OpenSeadragon.Tile# */ this.postData = postData; @@ -300,14 +300,15 @@ $.Tile.prototype = { return this._cKey; }, set cacheKey(value) { - if (this._cKey !== value) { - let ref = this._caches[this._cKey]; - if (ref) { - // make sure we free drawer internal cache - ref.destroyInternalCache(); - } - this._cKey = value; + if (value === this.cacheKey) { + return; } + const cache = this.getCache(value); + if (!cache) { + // It's better to first set cache, then change the key to existing one. Warn if otherwise. + $.console.warn("[Tile.cacheKey] should not be set manually. Use addCache() with setAsMain=true."); + } + this._updateMainCacheKey(value); }, /** @@ -431,11 +432,19 @@ $.Tile.prototype = { $.console.error("[Tile.cacheImageRecord] property has been deprecated. Use Tile::addCache."); const cache = this._caches[this.cacheKey]; - if (!value) { + if (cache) { this.removeCache(this.cacheKey); - } else { - const _this = this; - cache.await().then(x => _this.addCache(this.cacheKey, x, cache.type, false)); + } + + if (value) { + // Note: the value's data is probably not preserved - if a cacheKey cache exists, it will ignore + // data - it would have to call setData(...) + // TODO: call setData() ? + if (value.loaded) { + this.addCache(this.cacheKey, value.data, value.type, true, false); + } else { + value.await().then(x => this.addCache(this.cacheKey, x, value.type, true, false)); + } } }, @@ -492,11 +501,8 @@ $.Tile.prototype = { if (preserveOriginalData && this.cacheKey === this.originalCacheKey) { //caches equality means we have only one cache: - // change current pointer to a new cache and create it: new tiles will - // not arrive at this data, but at originalCacheKey state - // todo setting cache key makes the notification trigger ensure we do not do unnecessary stuff - this.cacheKey = "mod://" + this.originalCacheKey; - return this.addCache(this.cacheKey, value, type)._promise; + // create new cache record with main cache key changed to 'mod' + return this.addCache("mod://" + this.originalCacheKey, value, type, true)._promise; } //else overwrite cache const cache = this.getCache(this.cacheKey); @@ -512,7 +518,7 @@ $.Tile.prototype = { * @param {string} [key=this.cacheKey] cache key to read that belongs to this tile * @return {OpenSeadragon.CacheRecord} */ - getCache: function(key = this.cacheKey) { + getCache: function(key = this._cKey) { const cache = this._caches[key]; if (cache) { cache.withTileReference(this); @@ -526,20 +532,21 @@ $.Tile.prototype = { * value and extend it with some another unique content, by default overrides the existing * main cache used for drawing, if not existing. * @param {*} data data to cache - this data will be IGNORED if cache already exists! - * @param {?string} type data type, will be guessed if not provided + * @param {string} [type=undefined] data type, will be guessed if not provided + * @param {boolean} [setAsMain=false] if true, the key will be set as the tile.cacheKey * @param [_safely=true] private * @returns {OpenSeadragon.CacheRecord|null} - The cache record the tile was attached to. */ - addCache: function(key, data, type = undefined, _safely = true) { + addCache: function(key, data, type = undefined, setAsMain = false, _safely = true) { if (!this.tiledImage) { return null; //async can access outside its lifetime } if (!type) { - if (!this.tiledImage.__typeWarningReported) { + if (!this.__typeWarningReported) { $.console.warn(this, "[Tile.addCache] called without type specification. " + "Automated deduction is potentially unsafe: prefer specification of data type explicitly."); - this.tiledImage.__typeWarningReported = true; + this.__typeWarningReported = true; } type = $.convertor.guessType(data); } @@ -568,9 +575,31 @@ $.Tile.prototype = { } this._caches[key] = cachedItem; } + + // Update cache key if differs and main requested + if (!writesToRenderingCache && setAsMain) { + this._updateMainCacheKey(key); + } return cachedItem; }, + /** + * Sets the main cache key for this tile and + * performs necessary updates + * @param value + * @private + */ + _updateMainCacheKey: function(value) { + let ref = this._caches[this._cKey]; + if (ref) { + // make sure we free drawer internal cache + ref.destroyInternalCache(); + } + this._cKey = value; + // when key changes the image probably needs re-render + this.tiledImage.redraw(); + }, + /** * Get the number of caches available to this tile * @returns {number} number of caches @@ -585,11 +614,32 @@ $.Tile.prototype = { * @param {boolean} [freeIfUnused=true] set to false if zombie should be created */ removeCache: function(key, freeIfUnused = true) { - if (this.cacheKey === key) { - if (this.cacheKey !== this.originalCacheKey) { - this.cacheKey = this.originalCacheKey; + if (!this._caches[key]) { + // try to erase anyway in case the cache got stuck in memory + this.tiledImage._tileCache.unloadCacheForTile(this, key, freeIfUnused); + return; + } + + const currentMainKey = this.cacheKey, + originalDataKey = this.originalCacheKey, + sameBuiltinKeys = currentMainKey === originalDataKey; + + if (!sameBuiltinKeys && originalDataKey === key) { + $.console.warn("[Tile.removeCache] original data must not be manually deleted: other parts of the code might rely on it!", + "If you want the tile not to preserve the original data, toggle of data perseverance in tile.setData()."); + return; + } + + if (currentMainKey === key) { + if (!sameBuiltinKeys && this._caches[originalDataKey]) { + // if we have original data let's revert back + // TODO consider calling drawer.getDataToDraw(...) + // or even better, first ensure the data is compatible and then update...? + this._updateMainCacheKey(originalDataKey); } else { - $.console.warn("[Tile.removeCache] trying to remove the only cache that is used to draw the tile!"); + $.console.warn("[Tile.removeCache] trying to remove the only cache that can be used to draw the tile!", + "If you want to remove the main cache, first set different cache as main with tile.addCache()"); + return; } } if (this.tiledImage._tileCache.unloadCacheForTile(this, key, freeIfUnused)) { diff --git a/src/tilecache.js b/src/tilecache.js index 91baaf5b..b2d4a76e 100644 --- a/src/tilecache.js +++ b/src/tilecache.js @@ -106,7 +106,7 @@ */ setDataAs(data, type) { //allow set data with destroyed state, destroys the data if necessary - $.console.assert(data !== undefined, "[CacheRecord.setDataAs] needs valid data to set!"); + $.console.assert(data !== undefined && data !== null, "[CacheRecord.setDataAs] needs valid data to set!"); if (this._conversionJobQueue) { //delay saving if ongiong conversion, these were registered first let resolver = null; @@ -412,7 +412,7 @@ _triggerNeedsDraw() { for (let tile of this._tiles) { - tile.tiledImage._needsDraw = true; + tile.tiledImage.redraw(); } } diff --git a/src/tiledimage.js b/src/tiledimage.js index ba4b6e60..bf973086 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -2095,14 +2095,9 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag * @param {?Boolean} [withEvent=true] do not trigger event if true */ _setTileLoaded: function(tile, data, cutoff, tileRequest, dataType, withEvent = true) { - const originalDelete = tile.unload; - tile.unload = (function () { - throw `Cannot unload tile while being loaded!`; - }); - tile.tiledImage = this; //unloaded with tile.unload(), so we need to set it back // does nothing if tile.cacheKey already present - tile.addCache(tile.cacheKey, data, dataType, false); + tile.addCache(tile.cacheKey, data, dataType, false, false); let resolver = null, increment = 0, @@ -2135,13 +2130,11 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag } return cacheRef; }).then(_ => { - tile.unload = originalDelete; tile.loading = false; tile.loaded = true; resolver(tile); }); } else { - tile.unload = originalDelete; tile.loading = false; tile.loaded = true; resolver(tile); diff --git a/src/tilesource.js b/src/tilesource.js index f6712122..7ac6dccf 100644 --- a/src/tilesource.js +++ b/src/tilesource.js @@ -55,7 +55,8 @@ * @param {Object} options * You can either specify a URL, or literally define the TileSource (by specifying * width, height, tileSize, tileOverlap, minLevel, and maxLevel). For the former, - * the extending class is expected to implement 'getImageInfo' and 'configure'. + * the extending class is expected to implement 'supports' and 'configure'. + * Note that _in this case, the child class of getImageInfo() is ignored!_ * For the latter, the construction is assumed to occur through * the extending classes implementation of 'configure'. * @param {String} [options.url] @@ -72,6 +73,7 @@ * @param {Boolean} [options.splitHashDataForPost] * First occurrence of '#' in the options.url is used to split URL * and the latter part is treated as POST data (applies to getImageInfo(...)) + * Does not work if getImageInfo() is overridden and used (see the options description) * @param {Number} [options.width] * Width of the source image at max resolution in pixels. * @param {Number} [options.height] @@ -176,6 +178,8 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve * @memberof OpenSeadragon.TileSource# */ + // TODO potentially buggy behavior: what if .url is used by child class before it calls super constructor? + // this can happen if old JS class definition is used if( 'string' === $.type( arguments[ 0 ] ) ){ this.url = arguments[0]; } @@ -431,6 +435,12 @@ $.TileSource.prototype = { /** * Responsible for retrieving, and caching the * image metadata pertinent to this TileSources implementation. + * There are three scenarios of opening a tile source: + * 1) if it is a string parseable as XML or JSON, the string is converted to an object + * 2) if it is a string, then + * internally, this method + * else + * * @function * @param {String} url * @throws {Error} @@ -560,7 +570,7 @@ $.TileSource.prototype = { * @property {String} message * @property {String} source * @property {String} postData - HTTP POST data (usually but not necessarily in k=v&k2=v2... form, - * see TileSource::getPostData) or null + * see TileSource::getTilePostData) or null * @property {?Object} userData - Arbitrary subscriber-defined object. */ _this.raiseEvent( 'open-failed', { @@ -777,7 +787,7 @@ $.TileSource.prototype = { * @param {Boolean} [context.ajaxWithCredentials] - Whether to set withCredentials on AJAX requests. * @param {String} [context.crossOriginPolicy] - CORS policy to use for downloads * @param {?String|?Object} [context.postData] - HTTP POST data (usually but not necessarily - * in k=v&k2=v2... form, see TileSource::getPostData) or null + * in k=v&k2=v2... form, see TileSource::getTilePostData) or null * @param {*} [context.userData] - Empty object to attach your own data and helper variables to. * @param {Function} [context.finish] - Should be called unless abort() was executed upon successful * data retrieval. diff --git a/test/modules/tilecache.js b/test/modules/tilecache.js index 477c48ab..754d36b3 100644 --- a/test/modules/tilecache.js +++ b/test/modules/tilecache.js @@ -252,15 +252,15 @@ //load data const tile00 = createFakeTile('foo.jpg', fakeTiledImage0); - tile00.addCache(tile00.cacheKey, 0, T_A, false); + tile00.addCache(tile00.cacheKey, 0, T_A, false, false); const tile01 = createFakeTile('foo2.jpg', fakeTiledImage0); - tile01.addCache(tile01.cacheKey, 0, T_B, false); + tile01.addCache(tile01.cacheKey, 0, T_B, false, false); const tile10 = createFakeTile('foo3.jpg', fakeTiledImage1); - tile10.addCache(tile10.cacheKey, 0, T_C, false); + tile10.addCache(tile10.cacheKey, 0, T_C, false, false); const tile11 = createFakeTile('foo3.jpg', fakeTiledImage1); - tile11.addCache(tile11.cacheKey, 0, T_C, false); + tile11.addCache(tile11.cacheKey, 0, T_C, false, false); const tile12 = createFakeTile('foo.jpg', fakeTiledImage1); - tile12.addCache(tile12.cacheKey, 0, T_A, false); + tile12.addCache(tile12.cacheKey, 0, T_A, false, false); const collideGetSet = async (tile, type) => { const value = await tile.getData(type, false); @@ -446,15 +446,15 @@ //load data const tile00 = createFakeTile('foo.jpg', fakeTiledImage0); - tile00.addCache(tile00.cacheKey, 0, T_A, false); + tile00.addCache(tile00.cacheKey, 0, T_A, false, false); const tile01 = createFakeTile('foo2.jpg', fakeTiledImage0); - tile01.addCache(tile01.cacheKey, 0, T_B, false); + tile01.addCache(tile01.cacheKey, 0, T_B, false, false); const tile10 = createFakeTile('foo3.jpg', fakeTiledImage1); - tile10.addCache(tile10.cacheKey, 0, T_C, false); + tile10.addCache(tile10.cacheKey, 0, T_C, false, false); const tile11 = createFakeTile('foo3.jpg', fakeTiledImage1); - tile11.addCache(tile11.cacheKey, 0, T_C, false); + tile11.addCache(tile11.cacheKey, 0, T_C, false, false); const tile12 = createFakeTile('foo.jpg', fakeTiledImage1); - tile12.addCache(tile12.cacheKey, 0, T_A, false); + tile12.addCache(tile12.cacheKey, 0, T_A, false, false); //test set/get data in async env (async function() {