file-replicator.ts 5.37 KB
import { FileHandler } from './api/file-handler';
import { File } from './api/file';
import { Util } from './util';
import { EventEmitter } from 'events';

export class FileReplicator extends EventEmitter {

    constructor() {
        super();
    }

    protected _files: Array<File> = [];

    protected _itemValidator: (item: any) => boolean = null;
    protected _fileHandler: FileHandler = null;
    protected _retryTimeout: number = 100;
    protected _retries: number = 10;

    protected _itemKey = "type";
    protected _itemValue = "asset";
    protected _itemSourceAttribute = "source";
    protected _itemTargetAttribute = "target";
    protected _targetDirectory = "";

    get files(): Array<File> {
        return this._files;
    }

    set fileHandler(handler: FileHandler) {
        this._fileHandler = handler;
    }

    get fileHandler() : FileHandler {
        return this._fileHandler;
    }

    set retryTimeout(timeout: number) {
        this._retryTimeout = timeout;
    }

    set itemValidator(validator: (item: any) => boolean) {
        this._itemValidator = validator;
    }

    set itemKey(key: string) {
        this._itemKey = key;
    }

    set itemValue(value: string) {
        this._itemValue = value;
    }

    set itemSourceAttribute(sourceAttribute: string) {
        this._itemSourceAttribute = sourceAttribute;
    }

    set itemTargetAttribute(targetAttribute: string) {
        this._itemTargetAttribute = targetAttribute;
    }

    get itemKey() {
        return this._itemKey;
    }

    get itemValue() {
        return this._itemValue;
    }

    get itemSourceAttribute() {
        return this._itemSourceAttribute;
    }

    get itemTargetAttribute() {
        return this._itemTargetAttribute;
    }

    set targetDirectory(targetDirectory: string) {
        this._targetDirectory = targetDirectory;
    }

    get targetDirectory() {
        return this._targetDirectory;
    }

    clear(files: Array<File> = []) {
        this._files = files;
    }

    /**
     * change from pouchdb replicate
     */
    pushChanges(docs: Array<any>) {
        let items: Array<any> = [];

        if (docs && docs.length > 0) {
            for (let item of docs) {
                if (item[this._itemKey] && item[this._itemKey] === this._itemValue) {
                    items.push(item);
                }
            }
        }

        let files = this.prepareFiles(items);

        for (let file of files) {
            this._files.push(file);
        }
    }

    downloadFiles(files: Array<File>, fileHandler: FileHandler, index: number = 0) {
        if (index >= files.length) {
            return;
        }

        this.emit('start', { progress: 0, index: index, length: files.length });
        
        fileHandler
            .on('progress', (progress) => {
                this.emit('file-progress', { progress: progress, index: index, length: files.length })
            })
            .once('error', error => {
                this.emit('file-error', { progress: 0, index: index, length: files.length, error: error });
                fileHandler.removeAllListeners();
            })
            .once('complete', () => {
                this.emit('file-complete', { progress: 100, index: index, length: files.length });
                fileHandler.removeAllListeners();
                this.downloadFiles(files, fileHandler, index + 1);
            })
            .download(files[index].source, this.targetDirectory + files[index].target);        
    }

    prepareFiles(items: Array<any>): Array<File> {
        let output = [];

        for (let item of items) {
            if (item[this._itemSourceAttribute] && (!this._itemValidator || this._itemValidator(item))) {
                let file = { source: item[this._itemSourceAttribute], target: '' };

                if (item[this._itemTargetAttribute]) {
                    file.target = item[this._itemTargetAttribute];
                } else {
                    file.target = Util.getNameHash(file.source);
                }

                output.push(file);
            }
        }

        return output;
    }

    start(retries:number = 10) {
        this._retries = retries;

        this.on('file-complete', (event: any) => {
            if ((event.index + 1) >= event.length) {
                this.replicationFinalized(event.index);
            }
        });

        this.on('file-error', (event: any) => {
            this.replicationFinalized(event.index);
        });

        if (this._fileHandler && this._files.length > 0) {
            this.downloadFiles(this._files, this._fileHandler);
        } else {
            this.emit('complete');
        }        
    }

    cleanup() {
        this.fileHandler
            .cleanup(this._files, this._targetDirectory)
            .then(() => {
                this.emit('cleanup-complete');
            }).catch(() => {
                this.emit('cleanup-error');
            });
    }

    replicationFinalized(lastIndex: number) {
        if (lastIndex + 1 >= this._files.length) { // all finished
            this.emit('complete');
        } else if (this._retries > 0) { // restart after last success            
            this._files.splice(0, lastIndex);
            setTimeout(() => {
                this._retries =- 1;
                this.downloadFiles(this._files, this._fileHandler);
            }, this._retryTimeout);
        } else {
            this.emit('error');
        }
    }

}