Skip to content

Commit c646c19

Browse files
authored
Change hot exit to create new files for each backup (#12377)
For #12376 For #10496 Changes due to the way VSC works. After VSC calls the backup method, the previous backup is deleted. This means each backup is unique (hence the backup id must be unique) Fix for VSC Notebooks and Custom Editor
1 parent 1cd6bb7 commit c646c19

File tree

5 files changed

+69
-41
lines changed

5 files changed

+69
-41
lines changed

src/client/datascience/interactive-ipynb/nativeEditorProvider.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ export class NativeEditorProvider implements INotebookEditorProvider, CustomEdit
8989
context: CustomDocumentOpenContext, // This has info about backups. right now we use our own data.
9090
_cancellation: CancellationToken
9191
): Promise<CustomDocument> {
92-
const model = await this.loadModel(uri, undefined, context.backupId ? false : true);
92+
const model = await this.loadModel(uri, undefined, context.backupId);
9393
return {
9494
uri,
9595
dispose: () => model.dispose()
@@ -116,11 +116,11 @@ export class NativeEditorProvider implements INotebookEditorProvider, CustomEdit
116116
cancellation: CancellationToken
117117
): Promise<CustomDocumentBackup> {
118118
const model = await this.loadModel(document.uri);
119-
const id = this.storage.getBackupId(model);
120-
this.storage.backup(model, cancellation).ignoreErrors();
119+
const id = this.storage.generateBackupId(model);
120+
await this.storage.backup(model, cancellation, id);
121121
return {
122122
id,
123-
delete: () => this.storage.deleteBackup(model).ignoreErrors() // This cleans up after save has happened.
123+
delete: () => this.storage.deleteBackup(model, id).ignoreErrors() // This cleans up after save has happened.
124124
};
125125
}
126126

@@ -175,12 +175,16 @@ export class NativeEditorProvider implements INotebookEditorProvider, CustomEdit
175175
return this.open(uri);
176176
}
177177

178-
public async loadModel(file: Uri, contents?: string, skipDirtyContents?: boolean) {
178+
public async loadModel(file: Uri, contents?: string, skipDirtyContents?: boolean): Promise<INotebookModel>;
179+
// tslint:disable-next-line: unified-signatures
180+
public async loadModel(file: Uri, contents?: string, backupId?: string): Promise<INotebookModel>;
181+
// tslint:disable-next-line: no-any
182+
public async loadModel(file: Uri, contents?: string, options?: any): Promise<INotebookModel> {
179183
// Every time we load a new untitled file, up the counter past the max value for this counter
180184
this.untitledCounter = getNextUntitledCounter(file, this.untitledCounter);
181185

182186
// Load our model from our storage object.
183-
const model = await this.storage.load(file, contents, skipDirtyContents);
187+
const model = await this.storage.load(file, contents, options);
184188

185189
// Make sure to listen to events on the model
186190
this.trackModel(model);

src/client/datascience/interactive-ipynb/nativeEditorStorage.ts

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -519,13 +519,16 @@ export class NativeEditorStorage implements INotebookStorage {
519519
return isUntitledFile(file);
520520
}
521521

522-
public getBackupId(model: INotebookModel): string {
523-
const key = this.getStorageKey(model.file);
524-
return this.getHashedFileName(key);
522+
public generateBackupId(model: INotebookModel): string {
523+
return `${path.basename(model.file.fsPath)}-${uuid()}`;
525524
}
526525

527-
public load(file: Uri, possibleContents?: string, skipDirtyContents?: boolean): Promise<INotebookModel> {
528-
return this.loadFromFile(file, possibleContents, skipDirtyContents);
526+
public load(file: Uri, possibleContents?: string, backupId?: string): Promise<INotebookModel>;
527+
// tslint:disable-next-line: unified-signatures
528+
public load(file: Uri, possibleContents?: string, skipDirtyContents?: boolean): Promise<INotebookModel>;
529+
// tslint:disable-next-line: no-any
530+
public load(file: Uri, possibleContents?: string, options?: any): Promise<INotebookModel> {
531+
return this.loadFromFile(file, possibleContents, options);
529532
}
530533
public async save(model: INotebookModel, _cancellation: CancellationToken): Promise<void> {
531534
const contents = model.getContent();
@@ -552,15 +555,15 @@ export class NativeEditorStorage implements INotebookStorage {
552555
});
553556
this.savedAs.fire({ new: file, old });
554557
}
555-
public async backup(model: INotebookModel, cancellation: CancellationToken): Promise<void> {
558+
public async backup(model: INotebookModel, cancellation: CancellationToken, backupId?: string): Promise<void> {
556559
// If we are already backing up, save this request replacing any other previous requests
557560
if (this.backingUp) {
558561
this.backupRequested = { model, cancellation };
559562
return;
560563
}
561564
this.backingUp = true;
562565
// Should send to extension context storage path
563-
return this.storeContentsInHotExitFile(model, cancellation).finally(() => {
566+
return this.storeContentsInHotExitFile(model, cancellation, backupId).finally(() => {
564567
this.backingUp = false;
565568

566569
// If there is a backup request waiting, then clear and start it
@@ -579,17 +582,21 @@ export class NativeEditorStorage implements INotebookStorage {
579582
await this.loadFromFile(model.file);
580583
}
581584

582-
public async deleteBackup(model: INotebookModel): Promise<void> {
583-
return this.clearHotExit(model.file);
585+
public async deleteBackup(model: INotebookModel, backupId: string): Promise<void> {
586+
return this.clearHotExit(model.file, backupId);
584587
}
585588
/**
586589
* Stores the uncommitted notebook changes into a temporary location.
587590
* Also keep track of the current time. This way we can check whether changes were
588591
* made to the file since the last time uncommitted changes were stored.
589592
*/
590-
private async storeContentsInHotExitFile(model: INotebookModel, cancelToken?: CancellationToken): Promise<void> {
593+
private async storeContentsInHotExitFile(
594+
model: INotebookModel,
595+
cancelToken?: CancellationToken,
596+
backupId?: string
597+
): Promise<void> {
591598
const contents = model.getContent();
592-
const key = this.getStorageKey(model.file);
599+
const key = backupId || this.getStaticStorageKey(model.file);
593600
const filePath = this.getHashedFileName(key);
594601

595602
// Keep track of the time when this data was saved.
@@ -599,8 +606,8 @@ export class NativeEditorStorage implements INotebookStorage {
599606
return this.writeToStorage(filePath, specialContents, cancelToken);
600607
}
601608

602-
private async clearHotExit(file: Uri): Promise<void> {
603-
const key = this.getStorageKey(file);
609+
private async clearHotExit(file: Uri, backupId?: string): Promise<void> {
610+
const key = backupId || this.getStaticStorageKey(file);
604611
const filePath = this.getHashedFileName(key);
605612
await this.writeToStorage(filePath, undefined);
606613
}
@@ -613,8 +620,10 @@ export class NativeEditorStorage implements INotebookStorage {
613620
if (!cancelToken?.isCancellationRequested) {
614621
await this.fileSystem.writeFile(filePath, contents);
615622
}
616-
} else if (await this.fileSystem.fileExists(filePath)) {
617-
await this.fileSystem.deleteFile(filePath);
623+
} else {
624+
await this.fileSystem
625+
.deleteFile(filePath)
626+
.catch((ex) => traceError('Failed to delete hotExit file. Possible it does not exist', ex));
618627
}
619628
}
620629
} catch (exc) {
@@ -658,24 +667,32 @@ export class NativeEditorStorage implements INotebookStorage {
658667
noop();
659668
}
660669
}
670+
private loadFromFile(file: Uri, possibleContents?: string, backupId?: string): Promise<INotebookModel>;
671+
// tslint:disable-next-line: unified-signatures
672+
private loadFromFile(file: Uri, possibleContents?: string, skipDirtyContents?: boolean): Promise<INotebookModel>;
661673
private async loadFromFile(
662674
file: Uri,
663675
possibleContents?: string,
664-
skipDirtyContents?: boolean
676+
options?: boolean | string
665677
): Promise<INotebookModel> {
666678
try {
667679
// Attempt to read the contents if a viable file
668680
const contents = NativeEditorStorage.isUntitledFile(file)
669681
? possibleContents
670682
: await this.fileSystem.readFile(file.fsPath);
671683

684+
const skipDirtyContents = typeof options === 'boolean' ? options : !!options;
685+
// Use backupId provided, else use static storage key.
686+
const backupId =
687+
typeof options === 'string' ? options : skipDirtyContents ? undefined : this.getStaticStorageKey(file);
688+
672689
// If skipping dirty contents, delete the dirty hot exit file now
673690
if (skipDirtyContents) {
674-
await this.clearHotExit(file);
691+
await this.clearHotExit(file, backupId);
675692
}
676693

677694
// See if this file was stored in storage prior to shutdown
678-
const dirtyContents = skipDirtyContents ? undefined : await this.getStoredContents(file);
695+
const dirtyContents = skipDirtyContents ? undefined : await this.getStoredContents(file, backupId);
679696
if (dirtyContents) {
680697
// This means we're dirty. Indicate dirty and load from this content
681698
return this.loadContents(file, dirtyContents, true);
@@ -748,7 +765,7 @@ export class NativeEditorStorage implements INotebookStorage {
748765
);
749766
}
750767

751-
private getStorageKey(file: Uri): string {
768+
private getStaticStorageKey(file: Uri): string {
752769
return `${KeyPrefix}${file.toString()}`;
753770
}
754771

@@ -760,8 +777,8 @@ export class NativeEditorStorage implements INotebookStorage {
760777
* @returns {(Promise<string | undefined>)}
761778
* @memberof NativeEditor
762779
*/
763-
private async getStoredContents(file: Uri): Promise<string | undefined> {
764-
const key = this.getStorageKey(file);
780+
private async getStoredContents(file: Uri, backupId?: string): Promise<string | undefined> {
781+
const key = backupId || this.getStaticStorageKey(file);
765782

766783
// First look in the global storage file location
767784
let result = await this.getStoredContentsFromFile(file, key);

src/client/datascience/interactive-ipynb/notebookStorageProvider.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,27 +44,32 @@ export class NotebookStorageProvider implements INotebookStorageProvider {
4444
this.storageAndModels.delete(oldUri.toString());
4545
this.storageAndModels.set(targetResource.toString(), Promise.resolve(model));
4646
}
47-
public getBackupId(model: INotebookModel): string {
48-
return this.storage.getBackupId(model);
47+
public generateBackupId(model: INotebookModel): string {
48+
return this.storage.generateBackupId(model);
4949
}
50-
public backup(model: INotebookModel, cancellation: CancellationToken) {
51-
return this.storage.backup(model, cancellation);
50+
public backup(model: INotebookModel, cancellation: CancellationToken, backupId?: string) {
51+
return this.storage.backup(model, cancellation, backupId);
5252
}
5353
public revert(model: INotebookModel, cancellation: CancellationToken) {
5454
return this.storage.revert(model, cancellation);
5555
}
56-
public deleteBackup(model: INotebookModel) {
57-
return this.storage.deleteBackup(model);
56+
public deleteBackup(model: INotebookModel, backupId?: string) {
57+
return this.storage.deleteBackup(model, backupId);
5858
}
59-
public load(file: Uri, contents?: string | undefined, skipDirtyContents?: boolean): Promise<INotebookModel> {
59+
public load(file: Uri, contents?: string, backupId?: string): Promise<INotebookModel>;
60+
// tslint:disable-next-line: unified-signatures
61+
public load(file: Uri, contents?: string, skipDirtyContents?: boolean): Promise<INotebookModel>;
62+
63+
// tslint:disable-next-line: no-any
64+
public load(file: Uri, contents?: string, options?: any): Promise<INotebookModel> {
6065
const key = file.toString();
6166
if (!this.storageAndModels.has(key)) {
6267
// Every time we load a new untitled file, up the counter past the max value for this counter
6368
NotebookStorageProvider.untitledCounter = getNextUntitledCounter(
6469
file,
6570
NotebookStorageProvider.untitledCounter
6671
);
67-
const promise = this.storage.load(file, contents, skipDirtyContents);
72+
const promise = this.storage.load(file, contents, options);
6873
this.storageAndModels.set(key, promise.then(this.trackModel.bind(this)));
6974
}
7075
return this.storageAndModels.get(key)!;

src/client/datascience/notebook/contentProvider.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,11 @@ export class NotebookContentProvider implements VSCodeNotebookContentProvider {
7474
cancellation: CancellationToken
7575
): Promise<NotebookDocumentBackup> {
7676
const model = await this.notebookStorage.load(document.uri);
77-
const id = this.notebookStorage.getBackupId(model);
78-
this.notebookStorage.backup(model, cancellation).ignoreErrors();
77+
const id = this.notebookStorage.generateBackupId(model);
78+
await this.notebookStorage.backup(model, cancellation, id);
7979
return {
8080
id,
81-
delete: () => this.notebookStorage.deleteBackup(model).ignoreErrors() // This cleans up after save has happened.
81+
delete: () => this.notebookStorage.deleteBackup(model, id).ignoreErrors()
8282
};
8383
}
8484
}

src/client/datascience/types.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1031,13 +1031,15 @@ export const INotebookStorage = Symbol('INotebookStorage');
10311031

10321032
export interface INotebookStorage {
10331033
readonly onSavedAs: Event<{ new: Uri; old: Uri }>;
1034-
getBackupId(model: INotebookModel): string;
1034+
generateBackupId(model: INotebookModel): string;
10351035
save(model: INotebookModel, cancellation: CancellationToken): Promise<void>;
10361036
saveAs(model: INotebookModel, targetResource: Uri): Promise<void>;
1037-
backup(model: INotebookModel, cancellation: CancellationToken): Promise<void>;
1037+
backup(model: INotebookModel, cancellation: CancellationToken, backupId?: string): Promise<void>;
1038+
load(file: Uri, contents?: string, backupId?: string): Promise<INotebookModel>;
1039+
// tslint:disable-next-line: unified-signatures
10381040
load(file: Uri, contents?: string, skipDirtyContents?: boolean): Promise<INotebookModel>;
10391041
revert(model: INotebookModel, cancellation: CancellationToken): Promise<void>;
1040-
deleteBackup(model: INotebookModel): Promise<void>;
1042+
deleteBackup(model: INotebookModel, backupId?: string): Promise<void>;
10411043
}
10421044
type WebViewViewState = {
10431045
readonly visible: boolean;

0 commit comments

Comments
 (0)