savehandler.ts 4.0 KB

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