UNPKG

ionic-image-loader-v5

Version:
991 lines 90.3 kB
/** * @fileoverview added by tsickle * Generated from: lib/services/image-loader.service.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ import * as tslib_1 from "tslib"; import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { File } from '@ionic-native/file/ngx'; import { WebView } from '@ionic-native/ionic-webview/ngx'; import { Platform } from '@ionic/angular'; import { fromEvent, Subject } from 'rxjs'; import { filter, first, take } from 'rxjs/operators'; import { ImageLoaderConfigService } from './image-loader-config.service'; import * as i0 from "@angular/core"; import * as i1 from "./image-loader-config.service"; import * as i2 from "@ionic-native/file/ngx/index"; import * as i3 from "@angular/common/http"; import * as i4 from "@ionic/angular"; import * as i5 from "@ionic-native/ionic-webview/ngx/index"; /** * @record */ function IndexItem() { } if (false) { /** @type {?} */ IndexItem.prototype.name; /** @type {?} */ IndexItem.prototype.modificationTime; /** @type {?} */ IndexItem.prototype.size; } /** * @record */ function QueueItem() { } if (false) { /** @type {?} */ QueueItem.prototype.imageUrl; /** @type {?} */ QueueItem.prototype.resolve; /** @type {?} */ QueueItem.prototype.reject; } /** @type {?} */ const EXTENSIONS = ['jpg', 'png', 'jpeg', 'gif', 'svg', 'tiff']; export class ImageLoaderService { /** * @param {?} config * @param {?} file * @param {?} http * @param {?} platform * @param {?} webview */ constructor(config, file, http, platform, webview) { this.config = config; this.file = file; this.http = http; this.platform = platform; this.webview = webview; /** * Indicates if the cache service is ready. * When the cache service isn't ready, images are loaded via browser instead. */ this.isCacheReady = false; /** * Indicates if this service is initialized. * This service is initialized once all the setup is done. */ this.isInit = false; this.initPromise = new Promise((/** * @param {?} resolve * @return {?} */ resolve => this.initPromiseResolve = resolve)); this.lockSubject = new Subject(); this.lock$ = this.lockSubject.asObservable(); /** * Number of concurrent requests allowed */ this.concurrency = 5; /** * Queue items */ this.queue = []; this.processing = 0; /** * Fast accessible Object for currently processing items */ this.currentlyProcessing = {}; this.cacheIndex = []; this.currentCacheSize = 0; this.indexed = false; this.lockedCallsQueue = []; if (!platform.is('cordova')) { // we are running on a browser, or using livereload // plugin will not function in this case this.isInit = true; this.throwWarning('You are running on a browser or using livereload, IonicImageLoader will not function, falling back to browser loading.'); this.initPromiseResolve(); } else { fromEvent(document, 'deviceready') .pipe(first()) .subscribe((/** * @param {?} res * @return {?} */ res => { if (this.nativeAvailable) { this.initCache(); } else { // we are running on a browser, or using livereload // plugin will not function in this case this.isInit = true; this.initPromiseResolve(); this.throwWarning('You are running on a browser or using livereload, IonicImageLoader will not function, falling back to browser loading.'); } })); } } /** * @return {?} */ get nativeAvailable() { return File.installed(); } /** * @private * @return {?} */ get isCacheSpaceExceeded() { return (this.config.maxCacheSize > -1 && this.currentCacheSize > this.config.maxCacheSize); } /** * @private * @return {?} */ get isWKWebView() { return (this.platform.is('ios') && ((/** @type {?} */ (window))).webkit && ((/** @type {?} */ (window))).webkit.messageHandlers); } /** * @private * @return {?} */ get isIonicWKWebView() { return ( // Important: isWKWebview && isIonicWKWebview must be mutually excluse. // Otherwise the logic for copying to tmp under IOS will fail. (this.platform.is('android') && this.webview) || (this.platform.is('android')) && (location.host === 'localhost:8080') || ((/** @type {?} */ (window))).LiveReload); } /** * @private * @return {?} */ get isDevServer() { return window['IonicDevServer'] !== undefined; } /** * Check if we can process more items in the queue * @private * @return {?} */ get canProcess() { return this.queue.length > 0 && this.processing < this.concurrency; } /** * @return {?} */ ready() { return this.initPromise; } /** * Preload an image * @param {?} imageUrl Image URL * @return {?} returns a promise that resolves with the cached image URL */ preload(imageUrl) { return this.getImagePath(imageUrl); } /** * @return {?} */ getFileCacheDirectory() { if (this.config.cacheDirectoryType === 'data') { return this.file.dataDirectory; } else if (this.config.cacheDirectoryType === 'external') { return this.platform.is('android') ? this.file.externalDataDirectory : this.file.documentsDirectory; } return this.file.cacheDirectory; } /** * Clears cache of a single image * @param {?} imageUrl Image URL * @return {?} */ clearImageCache(imageUrl) { return tslib_1.__awaiter(this, void 0, void 0, function* () { if (!this.platform.is('cordova')) { return; } yield this.ready(); this.runLocked((/** * @return {?} */ () => tslib_1.__awaiter(this, void 0, void 0, function* () { /** @type {?} */ const fileName = this.createFileName(imageUrl); /** @type {?} */ const route = this.getFileCacheDirectory() + this.config.cacheDirectoryName; // pause any operations this.isInit = false; try { yield this.file.removeFile(route, fileName); if (this.isWKWebView && !this.isIonicWKWebView) { yield this.file.removeFile(this.file.tempDirectory + this.config.cacheDirectoryName, fileName); } } catch (err) { this.throwError(err); } return this.initCache(true); }))); }); } /** * Clears the cache * @return {?} */ clearCache() { return tslib_1.__awaiter(this, void 0, void 0, function* () { if (!this.platform.is('cordova')) { return; } yield this.ready(); this.runLocked((/** * @return {?} */ () => tslib_1.__awaiter(this, void 0, void 0, function* () { try { yield this.file.removeRecursively(this.getFileCacheDirectory(), this.config.cacheDirectoryName); if (this.isWKWebView && !this.isIonicWKWebView) { // also clear the temp files try { this.file.removeRecursively(this.file.tempDirectory, this.config.cacheDirectoryName); } catch (err) { // Noop catch. Removing the tempDirectory might fail, // as it is not persistent. } } } catch (err) { this.throwError(err); } return this.initCache(true); }))); }); } /** * Gets the filesystem path of an image. * This will return the remote path if anything goes wrong or if the cache service isn't ready yet. * @param {?} imageUrl The remote URL of the image * @return {?} Returns a promise that will always resolve with an image URL */ getImagePath(imageUrl) { return tslib_1.__awaiter(this, void 0, void 0, function* () { if (typeof imageUrl !== 'string' || imageUrl.length <= 0) { throw new Error('The image url provided was empty or invalid.'); } yield this.ready(); if (!this.isCacheReady) { this.throwWarning('The cache system is not running. Images will be loaded by your browser instead.'); return imageUrl; } if (this.isImageUrlRelative(imageUrl)) { return imageUrl; } try { return yield this.getCachedImagePath(imageUrl); } catch (err) { // image doesn't exist in cache, lets fetch it and save it return this.addItemToQueue(imageUrl); } }); } /** * @private * @return {?} */ processLockedQueue() { return tslib_1.__awaiter(this, void 0, void 0, function* () { if (yield this.getLockedState()) { return; } if (this.lockedCallsQueue.length > 0) { yield this.setLockedState(true); try { yield this.lockedCallsQueue.slice(0, 1)[0](); } catch (err) { console.log('Error running locked function: ', err); } yield this.setLockedState(false); return this.processLockedQueue(); } }); } /** * @private * @return {?} */ getLockedState() { return this.lock$ .pipe(take(1)) .toPromise(); } /** * @private * @return {?} */ awaitUnlocked() { return this.lock$ .pipe(filter((/** * @param {?} locked * @return {?} */ locked => !!locked)), take(1)) .toPromise(); } /** * @private * @param {?} locked * @return {?} */ setLockedState(locked) { return tslib_1.__awaiter(this, void 0, void 0, function* () { this.lockSubject.next(locked); }); } /** * @private * @param {?} fn * @return {?} */ runLocked(fn) { this.lockedCallsQueue.push(fn); this.processLockedQueue(); } /** * Returns if an imageUrl is an relative path * @private * @param {?} imageUrl * @return {?} */ isImageUrlRelative(imageUrl) { return !/^(https?|file):\/\/\/?/i.test(imageUrl); } /** * Add an item to the queue * @private * @param {?} imageUrl * @param {?=} resolve * @param {?=} reject * @return {?} */ addItemToQueue(imageUrl, resolve, reject) { /** @type {?} */ let p; if (!resolve && !reject) { p = new Promise((/** * @param {?} res * @param {?} rej * @return {?} */ (res, rej) => { resolve = res; reject = rej; })); } else { resolve = resolve || ((/** * @return {?} */ () => { })); reject = reject || ((/** * @return {?} */ () => { })); } this.queue.push({ imageUrl, resolve, reject, }); this.processQueue(); return p; } /** * Processes one item from the queue * @private * @return {?} */ processQueue() { return tslib_1.__awaiter(this, void 0, void 0, function* () { // make sure we can process items first if (!this.canProcess) { return; } // increase the processing number this.processing++; // take the first item from queue /** @type {?} */ const currentItem = this.queue.splice(0, 1)[0]; // function to call when done processing this item // this will reduce the processing number // then will execute this function again to process any remaining items /** @type {?} */ const done = (/** * @return {?} */ () => { this.processing--; this.processQueue(); // only delete if it's the last/unique occurrence in the queue if (this.currentlyProcessing[currentItem.imageUrl] !== undefined && !this.currentlyInQueue(currentItem.imageUrl)) { delete this.currentlyProcessing[currentItem.imageUrl]; } }); /** @type {?} */ const error = (/** * @param {?} e * @return {?} */ (e) => { currentItem.reject(); this.throwError(e); done(); }); if (this.currentlyProcessing[currentItem.imageUrl] !== undefined) { try { // Prevented same Image from loading at the same time yield this.currentlyProcessing[currentItem.imageUrl]; /** @type {?} */ const localUrl = yield this.getCachedImagePath(currentItem.imageUrl); currentItem.resolve(localUrl); done(); } catch (err) { error(err); } return; } this.currentlyProcessing[currentItem.imageUrl] = ((/** * @return {?} */ () => tslib_1.__awaiter(this, void 0, void 0, function* () { // process more items concurrently if we can if (this.canProcess) { this.processQueue(); } /** @type {?} */ const localDir = this.getFileCacheDirectory() + this.config.cacheDirectoryName + '/'; /** @type {?} */ const fileName = this.createFileName(currentItem.imageUrl); try { /** @type {?} */ const data = yield this.http.get(currentItem.imageUrl, { responseType: 'blob', headers: this.config.httpHeaders, }).toPromise(); /** @type {?} */ const file = (/** @type {?} */ (yield this.file.writeFile(localDir, fileName, data, { replace: true }))); if (this.isCacheSpaceExceeded) { this.maintainCacheSize(); } yield this.addFileToIndex(file); /** @type {?} */ const localUrl = yield this.getCachedImagePath(currentItem.imageUrl); currentItem.resolve(localUrl); done(); this.maintainCacheSize(); } catch (err) { error(err); throw err; } })))(); }); } /** * Search if the url is currently in the queue * @private * @param {?} imageUrl Image url to search * @return {?} */ currentlyInQueue(imageUrl) { return this.queue.some((/** * @param {?} item * @return {?} */ item => item.imageUrl === imageUrl)); } /** * Initialize the cache service * @private * @param {?=} replace * @return {?} */ initCache(replace) { return tslib_1.__awaiter(this, void 0, void 0, function* () { this.concurrency = this.config.concurrency; // create cache directories if they do not exist try { yield this.createCacheDirectory(replace); yield this.indexCache(); this.isCacheReady = true; } catch (err) { this.throwError(err); } this.isInit = true; this.initPromiseResolve(); }); } /** * Adds a file to index. * Also deletes any files if they are older than the set maximum cache age. * @private * @param {?} file FileEntry to index * @return {?} */ addFileToIndex(file) { return tslib_1.__awaiter(this, void 0, void 0, function* () { /** @type {?} */ const metadata = yield new Promise((/** * @param {?} resolve * @param {?} reject * @return {?} */ (resolve, reject) => file.getMetadata(resolve, reject))); if (this.config.maxCacheAge > -1 && Date.now() - metadata.modificationTime.getTime() > this.config.maxCacheAge) { // file age exceeds maximum cache age return this.removeFile(file.name); } else { // file age doesn't exceed maximum cache age, or maximum cache age isn't set this.currentCacheSize += metadata.size; // add item to index this.cacheIndex.push({ name: file.name, modificationTime: metadata.modificationTime, size: metadata.size, }); } }); } /** * Indexes the cache if necessary * @private * @return {?} */ indexCache() { return tslib_1.__awaiter(this, void 0, void 0, function* () { this.cacheIndex = []; try { /** @type {?} */ const files = yield this.file.listDir(this.getFileCacheDirectory(), this.config.cacheDirectoryName); yield Promise.all(files.map(this.addFileToIndex.bind(this))); // Sort items by date. Most recent to oldest. this.cacheIndex = this.cacheIndex.sort((/** * @param {?} a * @param {?} b * @return {?} */ (a, b) => (a > b ? -1 : a < b ? 1 : 0))); this.indexed = true; } catch (err) { this.throwError(err); } }); } /** * This method runs every time a new file is added. * It checks the cache size and ensures that it doesn't exceed the maximum cache size set in the config. * If the limit is reached, it will delete old images to create free space. * @private * @return {?} */ maintainCacheSize() { return tslib_1.__awaiter(this, void 0, void 0, function* () { if (this.config.maxCacheSize > -1 && this.indexed) { /** @type {?} */ const maintain = (/** * @return {?} */ () => tslib_1.__awaiter(this, void 0, void 0, function* () { if (this.currentCacheSize > this.config.maxCacheSize) { // grab the first item in index since it's the oldest one /** @type {?} */ const file = this.cacheIndex.splice(0, 1)[0]; if (typeof file === 'undefined') { return maintain(); } // delete the file then process next file if necessary try { yield this.removeFile(file.name); } catch (err) { // ignore errors, nothing we can do about it } this.currentCacheSize -= file.size; return maintain(); } })); return maintain(); } }); } /** * Remove a file * @private * @param {?} file The name of the file to remove * @return {?} */ removeFile(file) { return tslib_1.__awaiter(this, void 0, void 0, function* () { yield this.file.removeFile(this.getFileCacheDirectory() + this.config.cacheDirectoryName, file); if (this.isWKWebView && !this.isIonicWKWebView) { try { return this.file.removeFile(this.file.tempDirectory + this.config.cacheDirectoryName, file); } catch (err) { // Noop catch. Removing the files from tempDirectory might fail, as it is not persistent. } } }); } /** * Get the local path of a previously cached image if exists * @private * @param {?} url The remote URL of the image * @return {?} Returns a promise that resolves with the local path if exists, or rejects if doesn't exist */ getCachedImagePath(url) { return tslib_1.__awaiter(this, void 0, void 0, function* () { yield this.ready(); if (!this.isCacheReady) { throw new Error('Cache is not ready'); } // if we're running with livereload, ignore cache and call the resource from it's URL if (this.isDevServer) { return url; } // get file name /** @type {?} */ const fileName = this.createFileName(url); // get full path /** @type {?} */ const dirPath = this.getFileCacheDirectory() + this.config.cacheDirectoryName; /** @type {?} */ const tempDirPath = this.file.tempDirectory + this.config.cacheDirectoryName; try { // check if exists /** @type {?} */ const fileEntry = (/** @type {?} */ (yield this.file.resolveLocalFilesystemUrl(dirPath + '/' + fileName))); // file exists in cache if (this.config.imageReturnType === 'base64') { // read the file as data url and return the base64 string. // should always be successful as the existence of the file // is already ensured /** @type {?} */ const base64 = yield this.file.readAsDataURL(dirPath, fileName); return base64.replace('data:null', 'data:*/*'); } else if (this.config.imageReturnType !== 'uri') { return; } // now check if iOS device & using WKWebView Engine. // in this case only the tempDirectory is accessible, // therefore the file needs to be copied into that directory first! if (this.isIonicWKWebView) { return this.normalizeUrl(fileEntry); } if (!this.isWKWebView) { // return native path return fileEntry.nativeURL; } // check if file already exists in temp directory try { /** @type {?} */ const tempFileEntry = (/** @type {?} */ (yield this.file.resolveLocalFilesystemUrl(tempDirPath + '/' + fileName))); // file exists in temp directory // return native path return this.normalizeUrl(tempFileEntry); } catch (err) { // file does not yet exist in the temp directory. // copy it! /** @type {?} */ const tempFileEntry = (/** @type {?} */ (yield this.file .copyFile(dirPath, fileName, tempDirPath, fileName))); // now the file exists in the temp directory // return native path return this.normalizeUrl(tempFileEntry); } } catch (err) { throw new Error('File does not exist'); } }); } /** * Normalizes the image uri to a version that can be loaded in the webview * @private * @param {?} fileEntry the FileEntry of the image file * @return {?} the normalized Url */ normalizeUrl(fileEntry) { // Use Ionic normalizeUrl to generate the right URL for Ionic WKWebView if (Ionic && typeof Ionic.normalizeURL === 'function') { return Ionic.normalizeURL(fileEntry.nativeURL); } // use new webview function to do the trick if (this.webview) { return this.webview.convertFileSrc(fileEntry.nativeURL); } return fileEntry.nativeURL; } /** * Throws a console error if debug mode is enabled * @private * @param {...?} args Error message * @return {?} */ throwError(...args) { if (this.config.debugMode) { args.unshift('ImageLoader Error: '); console.error.apply(console, args); } } /** * Throws a console warning if debug mode is enabled * @private * @param {...?} args Error message * @return {?} */ throwWarning(...args) { if (this.config.debugMode) { args.unshift('ImageLoader Warning: '); console.warn.apply(console, args); } } /** * Check if the cache directory exists * @private * @param {?} directory The directory to check. Either this.file.tempDirectory or this.getFileCacheDirectory() * @return {?} Returns a promise that resolves if exists, and rejects if it doesn't */ cacheDirectoryExists(directory) { return this.file.checkDir(directory, this.config.cacheDirectoryName); } /** * Create the cache directories * @private * @param {?=} replace override directory if exists * @return {?} Returns a promise that resolves if the directories were created, and rejects on error */ createCacheDirectory(replace = false) { /** @type {?} */ let cacheDirectoryPromise; /** @type {?} */ let tempDirectoryPromise; if (replace) { // create or replace the cache directory cacheDirectoryPromise = this.file.createDir(this.getFileCacheDirectory(), this.config.cacheDirectoryName, replace); } else { // check if the cache directory exists. // if it does not exist create it! cacheDirectoryPromise = this.cacheDirectoryExists(this.getFileCacheDirectory()) .catch((/** * @return {?} */ () => this.file.createDir(this.getFileCacheDirectory(), this.config.cacheDirectoryName, false))); } if (this.isWKWebView && !this.isIonicWKWebView) { if (replace) { // create or replace the temp directory tempDirectoryPromise = this.file.createDir(this.file.tempDirectory, this.config.cacheDirectoryName, replace); } else { // check if the temp directory exists. // if it does not exist create it! tempDirectoryPromise = this.cacheDirectoryExists(this.file.tempDirectory).catch((/** * @return {?} */ () => this.file.createDir(this.file.tempDirectory, this.config.cacheDirectoryName, false))); } } else { tempDirectoryPromise = Promise.resolve(); } return Promise.all([cacheDirectoryPromise, tempDirectoryPromise]); } /** * Creates a unique file name out of the URL * @private * @param {?} url URL of the file * @return {?} Unique file name */ createFileName(url) { // hash the url to get a unique file name return (this.hashString(url).toString() + (this.config.fileNameCachedWithExtension ? this.getExtensionFromUrl(url) : '')); } /** * Converts a string to a unique 32-bit int * @private * @param {?} string string to hash * @return {?} 32-bit int */ hashString(string) { /** @type {?} */ let hash = 0; /** @type {?} */ let char; if (string.length === 0) { return hash; } for (let i = 0; i < string.length; i++) { char = string.charCodeAt(i); // tslint:disable-next-line hash = (hash << 5) - hash + char; // tslint:disable-next-line hash = hash & hash; } return hash; } /** * Extract extension from filename or url * * @private * @param {?} url * @return {?} * * Not always will url's contain a valid image extention. We'll check if any valid extention is supplied. * If not, we will use the default. */ getExtensionFromUrl(url) { /** @type {?} */ const urlWitoutParams = url.split(/\#|\?/)[0]; /** @type {?} */ const ext = (urlWitoutParams.substr((~-urlWitoutParams.lastIndexOf('.') >>> 0) + 1) || '').toLowerCase(); return (EXTENSIONS.indexOf(ext) >= 0 ? ext : this.config.fallbackFileNameCachedExtension); } } ImageLoaderService.decorators = [ { type: Injectable, args: [{ providedIn: 'root', },] } ]; /** @nocollapse */ ImageLoaderService.ctorParameters = () => [ { type: ImageLoaderConfigService }, { type: File }, { type: HttpClient }, { type: Platform }, { type: WebView } ]; /** @nocollapse */ ImageLoaderService.ngInjectableDef = i0.ɵɵdefineInjectable({ factory: function ImageLoaderService_Factory() { return new ImageLoaderService(i0.ɵɵinject(i1.ImageLoaderConfigService), i0.ɵɵinject(i2.File), i0.ɵɵinject(i3.HttpClient), i0.ɵɵinject(i4.Platform), i0.ɵɵinject(i5.WebView)); }, token: ImageLoaderService, providedIn: "root" }); if (false) { /** * Indicates if the cache service is ready. * When the cache service isn't ready, images are loaded via browser instead. * @type {?} * @private */ ImageLoaderService.prototype.isCacheReady; /** * Indicates if this service is initialized. * This service is initialized once all the setup is done. * @type {?} * @private */ ImageLoaderService.prototype.isInit; /** * @type {?} * @private */ ImageLoaderService.prototype.initPromiseResolve; /** * @type {?} * @private */ ImageLoaderService.prototype.initPromise; /** * @type {?} * @private */ ImageLoaderService.prototype.lockSubject; /** * @type {?} * @private */ ImageLoaderService.prototype.lock$; /** * Number of concurrent requests allowed * @type {?} * @private */ ImageLoaderService.prototype.concurrency; /** * Queue items * @type {?} * @private */ ImageLoaderService.prototype.queue; /** * @type {?} * @private */ ImageLoaderService.prototype.processing; /** * Fast accessible Object for currently processing items * @type {?} * @private */ ImageLoaderService.prototype.currentlyProcessing; /** * @type {?} * @private */ ImageLoaderService.prototype.cacheIndex; /** * @type {?} * @private */ ImageLoaderService.prototype.currentCacheSize; /** * @type {?} * @private */ ImageLoaderService.prototype.indexed; /** * @type {?} * @private */ ImageLoaderService.prototype.lockedCallsQueue; /** * @type {?} * @private */ ImageLoaderService.prototype.config; /** * @type {?} * @private */ ImageLoaderService.prototype.file; /** * @type {?} * @private */ ImageLoaderService.prototype.http; /** * @type {?} * @private */ ImageLoaderService.prototype.platform; /** * @type {?} * @private */ ImageLoaderService.prototype.webview; } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW1hZ2UtbG9hZGVyLnNlcnZpY2UuanMiLCJzb3VyY2VSb290Ijoibmc6Ly9pb25pYy1pbWFnZS1sb2FkZXItdjUvIiwic291cmNlcyI6WyJsaWIvc2VydmljZXMvaW1hZ2UtbG9hZGVyLnNlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBQUEsT0FBTyxFQUFDLFVBQVUsRUFBQyxNQUFNLHNCQUFzQixDQUFDO0FBQ2hELE9BQU8sRUFBQyxVQUFVLEVBQUMsTUFBTSxlQUFlLENBQUM7QUFDekMsT0FBTyxFQUFDLElBQUksRUFBWSxNQUFNLHdCQUF3QixDQUFDO0FBQ3ZELE9BQU8sRUFBQyxPQUFPLEVBQUMsTUFBTSxpQ0FBaUMsQ0FBQztBQUN4RCxPQUFPLEVBQUMsUUFBUSxFQUFDLE1BQU0sZ0JBQWdCLENBQUM7QUFDeEMsT0FBTyxFQUFDLFNBQVMsRUFBRSxPQUFPLEVBQUMsTUFBTSxNQUFNLENBQUM7QUFDeEMsT0FBTyxFQUFDLE1BQU0sRUFBRSxLQUFLLEVBQUUsSUFBSSxFQUFDLE1BQU0sZ0JBQWdCLENBQUM7QUFDbkQsT0FBTyxFQUFDLHdCQUF3QixFQUFDLE1BQU0sK0JBQStCLENBQUM7Ozs7Ozs7Ozs7QUFFdkUsd0JBSUM7OztJQUhHLHlCQUFhOztJQUNiLHFDQUF1Qjs7SUFDdkIseUJBQWE7Ozs7O0FBR2pCLHdCQUlDOzs7SUFIRyw2QkFBaUI7O0lBQ2pCLDRCQUFrQjs7SUFDbEIsMkJBQWlCOzs7TUFLZixVQUFVLEdBQUcsQ0FBQyxLQUFLLEVBQUUsS0FBSyxFQUFFLE1BQU0sRUFBRSxLQUFLLEVBQUUsS0FBSyxFQUFFLE1BQU0sQ0FBQztBQUsvRCxNQUFNLE9BQU8sa0JBQWtCOzs7Ozs7OztJQWtDM0IsWUFDWSxNQUFnQyxFQUNoQyxJQUFVLEVBQ1YsSUFBZ0IsRUFDaEIsUUFBa0IsRUFDbEIsT0FBZ0I7UUFKaEIsV0FBTSxHQUFOLE1BQU0sQ0FBMEI7UUFDaEMsU0FBSSxHQUFKLElBQUksQ0FBTTtRQUNWLFNBQUksR0FB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