UNPKG

4.45 kBPlain TextView Raw
1// Copyright (c) Jupyter Development Team.
2// Distributed under the terms of the Modified BSD License.
3
4import { DocumentRegistry } from '@jupyterlab/docregistry';
5import { IDisposable } from '@lumino/disposable';
6import { Signal } from '@lumino/signaling';
7
8/**
9 * A class that manages the auto saving of a document.
10 *
11 * #### Notes
12 * Implements https://github.com/ipython/ipython/wiki/IPEP-15:-Autosaving-the-IPython-Notebook.
13 */
14export class SaveHandler implements IDisposable {
15 /**
16 * Construct a new save handler.
17 */
18 constructor(options: SaveHandler.IOptions) {
19 this._context = options.context;
20 this._isConnectedCallback = options.isConnectedCallback || (() => true);
21 const interval = options.saveInterval || 120;
22 this._minInterval = interval * 1000;
23 this._interval = this._minInterval;
24 // Restart the timer when the contents model is updated.
25 this._context.fileChanged.connect(this._setTimer, this);
26 this._context.disposed.connect(this.dispose, this);
27 }
28
29 /**
30 * The save interval used by the timer (in seconds).
31 */
32 get saveInterval(): number {
33 return this._interval / 1000;
34 }
35 set saveInterval(value: number) {
36 this._minInterval = this._interval = value * 1000;
37 if (this._isActive) {
38 this._setTimer();
39 }
40 }
41
42 /**
43 * Get whether the handler is active.
44 */
45 get isActive(): boolean {
46 return this._isActive;
47 }
48
49 /**
50 * Get whether the save handler is disposed.
51 */
52 get isDisposed(): boolean {
53 return this._isDisposed;
54 }
55
56 /**
57 * Dispose of the resources used by the save handler.
58 */
59 dispose(): void {
60 if (this.isDisposed) {
61 return;
62 }
63 this._isDisposed = true;
64 clearTimeout(this._autosaveTimer);
65 Signal.clearData(this);
66 }
67
68 /**
69 * Start the autosaver.
70 */
71 start(): void {
72 this._isActive = true;
73 this._setTimer();
74 }
75
76 /**
77 * Stop the autosaver.
78 */
79 stop(): void {
80 this._isActive = false;
81 clearTimeout(this._autosaveTimer);
82 }
83
84 /**
85 * Set the timer.
86 */
87 private _setTimer(): void {
88 clearTimeout(this._autosaveTimer);
89 if (!this._isActive) {
90 return;
91 }
92 this._autosaveTimer = window.setTimeout(() => {
93 if (this._isConnectedCallback()) {
94 this._save();
95 }
96 }, this._interval);
97 }
98
99 /**
100 * Handle an autosave timeout.
101 */
102 private _save(): void {
103 const context = this._context;
104
105 // Trigger the next update.
106 this._setTimer();
107
108 if (!context) {
109 return;
110 }
111
112 // Bail if the model is not dirty or the file is not writable, or the dialog
113 // is already showing.
114 const writable = context.contentsModel && context.contentsModel.writable;
115 if (!writable || !context.model.dirty || this._inDialog) {
116 return;
117 }
118
119 const start = new Date().getTime();
120 context
121 .save()
122 .then(() => {
123 if (this.isDisposed) {
124 return;
125 }
126 const duration = new Date().getTime() - start;
127 // New save interval: higher of 10x save duration or min interval.
128 this._interval = Math.max(
129 this._multiplier * duration,
130 this._minInterval
131 );
132 // Restart the update to pick up the new interval.
133 this._setTimer();
134 })
135 .catch(err => {
136 // If the user canceled the save, do nothing.
137 const { name } = err;
138 if (name === 'ModalCancelError' || name === 'ModalDuplicateError') {
139 return;
140 }
141 // Otherwise, log the error.
142 console.error('Error in Auto-Save', err.message);
143 });
144 }
145
146 private _autosaveTimer = -1;
147 private _minInterval = -1;
148 private _interval = -1;
149 private _context: DocumentRegistry.Context;
150 private _isConnectedCallback: () => boolean;
151 private _isActive = false;
152 private _inDialog = false;
153 private _isDisposed = false;
154 private _multiplier = 10;
155}
156
157/**
158 * A namespace for `SaveHandler` statics.
159 */
160export namespace SaveHandler {
161 /**
162 * The options used to create a save handler.
163 */
164 export interface IOptions {
165 /**
166 * The context associated with the file.
167 */
168 context: DocumentRegistry.Context;
169
170 /**
171 * Autosaving should be paused while this callback function returns `false`.
172 * By default, it always returns `true`.
173 */
174 isConnectedCallback?: () => boolean;
175
176 /**
177 * The minimum save interval in seconds (default is two minutes).
178 */
179 saveInterval?: number;
180 }
181}