Source: xxe/app/AutoSave.js

/**
 * Implements the autosave feature of {@link XMLEditorApp}.
 * <p>This class may be used independently from <code>XMLEditorApp</code>.
 */
export class AutoSave {
    /**
     * Constructs an <code>AutoSave</code> object.
     * 
     * @param {XMLEditor} xmlEditor - the XML editor.
     * @param {string} mode - autosave mode: 
     * <code>"local"</code> (autosave local files), 
     * <code>"remote"</code> (autosave remote files) or 
     * <code>"both"</code> (autosave both local and remotefiles).
     * @param {number} intervalMillis - autosave interval in milliseconds;
     * must be at least 10000 (10s).
     */
    constructor(xmlEditor, mode, intervalMillis) {
        this._xmlEditor = xmlEditor;
        
        if (intervalMillis < 10000) {
            intervalMillis = 10000;
        }
        this._intervalMillis = intervalMillis;
        this._saveTimer = null;

        if (!mode ||
            !(mode === "local" || mode === "remote" || mode === "both")) {
            mode = "remote";
        }
        if (!FSAccess.isAvailable() &&
            (mode === "both" || mode === "local")) {
            mode = (mode === "local")? null : "remote";
        }
        this._mode = mode;

        this._docOpenedHandler = this._docClosedHandler = null;
        if (this._mode === null) {
            // AutoSave not activable.
            return;
        }

        // ---
        
        this._docOpenedHandler = this.onDocumentOpened.bind(this);
        xmlEditor.addEventListener("documentCreated", this._docOpenedHandler);
        xmlEditor.addEventListener("documentOpened", this._docOpenedHandler);
        xmlEditor.addEventListener("documentRecovered", this._docOpenedHandler);
        
        this._docClosedHandler = this.onDocumentClosed.bind(this);
        xmlEditor.addEventListener("documentClosed", this._docClosedHandler);

        // ---
        
        if (this._xmlEditor.documentIsOpened) {
            this.onDocumentOpened(/*event*/ null);
        }
    }

    /**
     * Dispose this <code>AutoSave</code> object when it is not needed anymore.
     */
    dispose() {
        if (this._docOpenedHandler !== null) {
            this._xmlEditor.removeEventListener("documentCreated",
                                                this._docOpenedHandler);
            this._xmlEditor.removeEventListener("documentOpened",
                                                this._docOpenedHandler);
            this._xmlEditor.removeEventListener("documentRecovered",
                                                this._docOpenedHandler);
            this._docOpenedHandler = null;
        }
        
        if (this._docClosedHandler !== null) {
            this._xmlEditor.removeEventListener("documentClosed",
                                                this._docClosedHandler);
            this._docClosedHandler = null;
        }
            
        this.cancelSaveTimer();
    }
    
    cancelSaveTimer() {
        if (this._saveTimer !== null) {
            clearInterval(this._saveTimer);
            this._saveTimer = null;
        }
    }
    
    /**
     * Get the <code>xmlEditor</code> property of this <code>AutoSave</code> 
     * object. See constructor.
     *
     * @type {XMLEditor}
     */
    get xmlEditor() {
        return this._xmlEditor;
    }
    
    /**
     * Get the <code>mode</code> property of this <code>AutoSave</code> 
     * object. See constructor.
     *
     * @type {string}
     */
    get mode() {
        return this._mode;
    }
    
    /**
     * Get the <code>intervalMillis</code> property of 
     * this <code>AutoSave</code> object. See constructor.
     *
     * @type {number}
     */
    get intervalMillis() {
        return this._intervalMillis;
    }
    
    onDocumentOpened(event) {
        this.cancelSaveTimer();

        if (this.activable) {
            this._saveTimer = setInterval(() => {
                this.saveDocument();
            }, this._intervalMillis);
        }
    }

    /**
     * Get the <code>activable</code> property of 
     * this <code>AutoSave</code> object which may be used to test
     * whether it will be active after a document is created, opened 
     * or recovered.
     * <p>Typically invoked from a {@link DocumentOpenedEvent} (of any kind)
     * event handler to determine whether this <code>AutoSave</code> object 
     * will be active given opened document and chosen autosave mode.
     *
     * @type {boolean}
     */
    get activable() {
        return (this._mode !== null &&
                this._xmlEditor.documentIsOpened &&
                !this._xmlEditor.readOnlyDocument &&
                (this._xmlEditor.isRemoteFile &&
                 (this._mode === "both" || this._mode === "remote")) ||
                (!this._xmlEditor.isRemoteFile &&
                 (this._mode === "both" || this._mode === "local")));
    }
    
    saveDocument() {
        if (!this._xmlEditor.connected ||
            !this._xmlEditor.documentIsOpened ||
            !this._xmlEditor.saveNeeded) {
            // Not needed.
            return;
        }
        
        if (this._xmlEditor.saveAsNeeded) {
            // May be later.
            this._xmlEditor.showStatus(
                "No autosave: \"Save As\" needed first.");
            return;
        }
        
        const remote = this._xmlEditor.isRemoteFile;
        if (remote) {
            this.doSaveDocument(true);
            return;
        }
        
        if (this._xmlEditor.documentFileHandle === null) {
            // May be later.
            this._xmlEditor.showStatus(
                "No autosave: interactive \"Save\" needed first.");
            return;
        }

        // Local file having a handle but can it be used to save the document?
        
        this._xmlEditor.documentFileHandle.queryPermission({mode: "readwrite"})
            .then((status) => {
                if (status === "granted") {
                    this.doSaveDocument(false);
                } else {
                    this._xmlEditor.showStatus(
                        "No autosave: interactive \"Save\" needed first.");
                }
            })
            .catch((error) => {
                this._xmlEditor.showStatus(`Autosave error: ${error}`);
            });
    }
    
    doSaveDocument(remote) {
        XMLEditorApp.doSaveDocument(this._xmlEditor, remote)
            .then((saved) => {
                this._xmlEditor.showStatus(saved? "Autosave." :
                                           "No autosave: unknown reason.");
            })
            .catch((error) => {
                this._xmlEditor.showStatus(`Autosave error: ${error}`);
            });
    }

    onDocumentClosed(event) {
        this.cancelSaveTimer();
    }
}