mirror of
				https://github.com/NativeScript/NativeScript.git
				synced 2025-11-04 12:58:38 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			222 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			222 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import * as definition from '.';
 | 
						|
import { EventData, Observable } from '../../data/observable';
 | 
						|
import * as imageSource from '../../image-source';
 | 
						|
 | 
						|
export interface DownloadRequest {
 | 
						|
	url: string;
 | 
						|
	key: string;
 | 
						|
	completed?: (image: any, key: string) => void;
 | 
						|
	error?: (key: string) => void;
 | 
						|
}
 | 
						|
 | 
						|
export class Cache extends Observable implements definition.Cache {
 | 
						|
	public static downloadedEvent = 'downloaded';
 | 
						|
	public static downloadErrorEvent = 'downloadError';
 | 
						|
 | 
						|
	public placeholder: imageSource.ImageSource;
 | 
						|
	public maxRequests = 5;
 | 
						|
	private _enabled = true;
 | 
						|
 | 
						|
	private _pendingDownloads = {};
 | 
						|
	private _queue: Array<DownloadRequest> = [];
 | 
						|
	private _currentDownloads = 0;
 | 
						|
 | 
						|
	public enableDownload() {
 | 
						|
		if (this._enabled) {
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		// schedule all pending downloads
 | 
						|
		this._enabled = true;
 | 
						|
		let request: DownloadRequest;
 | 
						|
 | 
						|
		while (this._queue.length > 0 && this._currentDownloads < this.maxRequests) {
 | 
						|
			request = this._queue.pop();
 | 
						|
			if (!(request.key in this._pendingDownloads)) {
 | 
						|
				this._download(request);
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	public disableDownload() {
 | 
						|
		if (!this._enabled) {
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		this._enabled = false;
 | 
						|
	}
 | 
						|
 | 
						|
	public push(request: DownloadRequest): void {
 | 
						|
		this._addRequest(request, true);
 | 
						|
	}
 | 
						|
 | 
						|
	public enqueue(request: DownloadRequest): void {
 | 
						|
		this._addRequest(request, false);
 | 
						|
	}
 | 
						|
 | 
						|
	private _addRequest(request: DownloadRequest, onTop: boolean): void {
 | 
						|
		if (request.key in this._pendingDownloads) {
 | 
						|
			const existingRequest = <DownloadRequest>this._pendingDownloads[request.key];
 | 
						|
			this._mergeRequests(existingRequest, request);
 | 
						|
		} else {
 | 
						|
			// TODO: Potential performance bottleneck - traversing the whole queue on each download request.
 | 
						|
			let queueRequest: DownloadRequest;
 | 
						|
			for (let i = 0; i < this._queue.length; i++) {
 | 
						|
				if (this._queue[i].key === request.key) {
 | 
						|
					queueRequest = this._queue[i];
 | 
						|
					break;
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			if (queueRequest) {
 | 
						|
				this._mergeRequests(queueRequest, request);
 | 
						|
			} else {
 | 
						|
				if (this._shouldDownload(request, onTop)) {
 | 
						|
					this._download(request);
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	private _mergeRequests(existingRequest: DownloadRequest, newRequest: DownloadRequest) {
 | 
						|
		if (existingRequest.completed) {
 | 
						|
			if (newRequest.completed) {
 | 
						|
				const existingCompleted = existingRequest.completed;
 | 
						|
				const stackCompleted = function (result: imageSource.ImageSource, key: string) {
 | 
						|
					existingCompleted(result, key);
 | 
						|
					newRequest.completed(result, key);
 | 
						|
				};
 | 
						|
 | 
						|
				existingRequest.completed = stackCompleted;
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			existingRequest.completed = newRequest.completed;
 | 
						|
		}
 | 
						|
		if (existingRequest.error) {
 | 
						|
			if (newRequest.error) {
 | 
						|
				const existingError = existingRequest.error;
 | 
						|
				const stackError = function (key: string) {
 | 
						|
					existingError(key);
 | 
						|
					newRequest.error(key);
 | 
						|
				};
 | 
						|
 | 
						|
				existingRequest.error = stackError;
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			existingRequest.error = newRequest.error;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	public get(key: string): any {
 | 
						|
		// This method is intended to be overridden by the android and ios implementations
 | 
						|
		throw new Error('Abstract');
 | 
						|
	}
 | 
						|
 | 
						|
	public set(key: string, image: any): void {
 | 
						|
		// This method is intended to be overridden by the android and ios implementations
 | 
						|
		throw new Error('Abstract');
 | 
						|
	}
 | 
						|
 | 
						|
	public remove(key: string): void {
 | 
						|
		// This method is intended to be overridden by the android and ios implementations
 | 
						|
		throw new Error('Abstract');
 | 
						|
	}
 | 
						|
 | 
						|
	public clear() {
 | 
						|
		// This method is intended to be overridden by the android and ios implementations
 | 
						|
		throw new Error('Abstract');
 | 
						|
	}
 | 
						|
 | 
						|
	/* tslint:disable:no-unused-variable */
 | 
						|
	public _downloadCore(request: definition.DownloadRequest) {
 | 
						|
		// This method is intended to be overridden by the android and ios implementations
 | 
						|
		throw new Error('Abstract');
 | 
						|
	}
 | 
						|
	/* tslint:enable:no-unused-variable */
 | 
						|
 | 
						|
	public _onDownloadCompleted(key: string, image: any) {
 | 
						|
		const request = <DownloadRequest>this._pendingDownloads[key];
 | 
						|
 | 
						|
		this.set(request.key, image);
 | 
						|
		this._currentDownloads--;
 | 
						|
 | 
						|
		if (request.completed) {
 | 
						|
			request.completed(image, request.key);
 | 
						|
		}
 | 
						|
 | 
						|
		if (this.hasListeners(Cache.downloadedEvent)) {
 | 
						|
			this.notify({
 | 
						|
				eventName: Cache.downloadedEvent,
 | 
						|
				object: this,
 | 
						|
				key: key,
 | 
						|
				image: image,
 | 
						|
			});
 | 
						|
		}
 | 
						|
 | 
						|
		delete this._pendingDownloads[request.key];
 | 
						|
 | 
						|
		this._updateQueue();
 | 
						|
	}
 | 
						|
 | 
						|
	public _onDownloadError(key: string, err: Error) {
 | 
						|
		const request = <DownloadRequest>this._pendingDownloads[key];
 | 
						|
		this._currentDownloads--;
 | 
						|
 | 
						|
		if (request.error) {
 | 
						|
			request.error(request.key);
 | 
						|
		}
 | 
						|
 | 
						|
		if (this.hasListeners(Cache.downloadErrorEvent)) {
 | 
						|
			this.notify({
 | 
						|
				eventName: Cache.downloadErrorEvent,
 | 
						|
				object: this,
 | 
						|
				key: key,
 | 
						|
				error: err,
 | 
						|
			});
 | 
						|
		}
 | 
						|
 | 
						|
		delete this._pendingDownloads[request.key];
 | 
						|
 | 
						|
		this._updateQueue();
 | 
						|
	}
 | 
						|
 | 
						|
	private _shouldDownload(request: definition.DownloadRequest, onTop: boolean): boolean {
 | 
						|
		if (this.get(request.key) || request.key in this._pendingDownloads) {
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
 | 
						|
		if (this._currentDownloads >= this.maxRequests || !this._enabled) {
 | 
						|
			if (onTop) {
 | 
						|
				this._queue.push(request);
 | 
						|
			} else {
 | 
						|
				this._queue.unshift(request);
 | 
						|
			}
 | 
						|
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	private _download(request: definition.DownloadRequest) {
 | 
						|
		this._currentDownloads++;
 | 
						|
		this._pendingDownloads[request.key] = request;
 | 
						|
 | 
						|
		this._downloadCore(request);
 | 
						|
	}
 | 
						|
 | 
						|
	private _updateQueue() {
 | 
						|
		if (!this._enabled || this._queue.length === 0 || this._currentDownloads === this.maxRequests) {
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		const request = this._queue.pop();
 | 
						|
		this._download(request);
 | 
						|
	}
 | 
						|
}
 | 
						|
export interface Cache {
 | 
						|
	on(eventNames: string, callback: (args: EventData) => void, thisArg?: any): void;
 | 
						|
	on(event: 'downloaded', callback: (args: definition.DownloadedData) => void, thisArg?: any): void;
 | 
						|
	on(event: 'downloadError', callback: (args: definition.DownloadError) => void, thisArg?: any): void;
 | 
						|
}
 |