Skip to content

Commit

Permalink
Merge pull request #581 from mre/backup-system
Browse files Browse the repository at this point in the history
Backup system
  • Loading branch information
VladBrok authored Mar 16, 2024
2 parents c5a78fb + 0f696a0 commit fe36ace
Show file tree
Hide file tree
Showing 17 changed files with 572 additions and 74 deletions.
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ A Visual Studio Code extension for [cht.sh](https://cht.sh/).
- ${index} - the index of the snippet (e.g. 2 for the third answer)
- `insertWithDoubleClick`: insert snippet with double click.
- `showCopySuccessNotification`: Whether to show a notification after the snippet is copied to the clipboard.
- `saveBackups`: Whether to create backups of the snippets.

## Installation

Expand Down Expand Up @@ -132,6 +133,41 @@ Saved snippets are displayed in IntelliSense

![Preview](https://raw.githubusercontent.com/mre/vscode-snippet/master/contrib/snippets-storage/search.gif)

## Restoring snippets from backups

### Restoring with the built-in backup mechanism

vscode-snippet creates backups of your snippets when you delete, rename, move or save snippets. These backups are stored **locally** on your computer.

To restore a backup:

1. Open the Snippets section
2. Click on the ![History icon](https://raw.githubusercontent.com/mre/vscode-snippet/master/contrib/snippets-storage/history.png) icon (alternatively, you can run the "Restore backups" command)
3. Select one of the backups from the list

![Demo of restoring backups](https://raw.githubusercontent.com/mre/vscode-snippet/master/contrib/snippets-storage/restore-backups.gif)

### Restoring with the VSCode settings sync

If you have [VSCode settings sync](https://code.visualstudio.com/docs/editor/settings-sync) enabled, you can restore snippets by using VSCode's built-in backup mechanisms: [https://code.visualstudio.com/docs/editor/settings-sync#\_restoring-data](https://code.visualstudio.com/docs/editor/settings-sync#_restoring-data)

## Exporting snippets

VSCode stores snippets in the `state.vscdb` file in a `JSON` format.

To export the snippets:

1. Find the `state.vscdb` file
- On Ubuntu Linux: `~/.config/Code/User/globalStorage/state.vscdb`
- On Windows: `AppData\Roaming\Code\User\globalStorage\state.vscdb`
- On macOS: `~/Library/Application Support/Code/User/globalStorage/state.vscdb`
2. Inspect the content of this file using some tool that can open SQLite files, for example: [https://inloop.github.io/sqlite-viewer](https://inloop.github.io/sqlite-viewer)
1. On this website, upload the `state.vscdb` file and run the following command:
```sql
SELECT * FROM 'ItemTable' WHERE key like 'vscode-snippet.snippet'
```
![SQLite Viewer](https://raw.githubusercontent.com/mre/vscode-snippet/master/contrib/snippets-storage/vscdb.png) 2. Then click "Execute". You should get a single row with the key `vscode-snippet.snippet` and a `JSON` value. This `JSON` contains all of your snippets.

## Contributing

See [CONTRIBUTING.md](./CONTRIBUTING.md)
3 changes: 3 additions & 0 deletions assets/icons/history-dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions assets/icons/history-light.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added contrib/snippets-storage/history.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added contrib/snippets-storage/restore-backups.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added contrib/snippets-storage/vscdb.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 20 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "snippet",
"displayName": "Snippet",
"description": "Insert a snippet from cht.sh for Python, JavaScript, Ruby, C#, Go, Rust (and any other language)",
"version": "1.1.5",
"version": "1.1.6",
"publisher": "vscode-snippet",
"engines": {
"vscode": "^1.74.0"
Expand Down Expand Up @@ -123,6 +123,15 @@
"light": "assets/icons/add-light.svg",
"dark": "assets/icons/add-dark.svg"
}
},
{
"title": "Restore backups",
"command": "snippet.restoreBackups",
"category": "Snippet",
"icon": {
"light": "assets/icons/history-light.svg",
"dark": "assets/icons/history-dark.svg"
}
}
],
"configuration": {
Expand Down Expand Up @@ -162,6 +171,11 @@
"type": "boolean",
"default": true,
"description": "Whether to show a notification after the snippet is copied to the clipboard."
},
"snippet.saveBackups": {
"type": "boolean",
"default": true,
"description": "Whether to create backups of the snippets."
}
}
},
Expand All @@ -171,6 +185,11 @@
"command": "snippet.createFolder",
"when": "view == snippetsView",
"group": "navigation"
},
{
"command": "snippet.restoreBackups",
"when": "view == snippetsView",
"group": "navigation"
}
],
"view/item/context": [
Expand Down
119 changes: 119 additions & 0 deletions src/backupManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { randomUUID } from "crypto";
import * as vscode from "vscode";
import { getConfig } from "./config";
import { formatUnixTime } from "./date";
import SnippetsStorage, {
StorageOperation,
TreeElement,
} from "./snippetsStorage";

export interface Backup {
id: string;
dateUnix: number;
elements: TreeElement[];
beforeOperation?: string;
}

export interface BackupItem extends vscode.QuickPickItem {
item: Backup;
}

const STORAGE_KEY = "snippet.snippetBackupsStorageKey";
const MAX_BACKUPS = 10;

export class BackupManager {
private backups: Backup[] = [];
private elementsBeforeRestore: TreeElement[] | null = null;

constructor(
private readonly context: vscode.ExtensionContext,
private readonly snippets: SnippetsStorage
) {
this.load();
snippets.onBeforeSave = (elements, operation) =>
this.makeBackup(elements, operation);
}

getBackupItems(): BackupItem[] {
const items = this.backups.map((backup) => {
const time = `${formatUnixTime(backup.dateUnix)}`;
const detail = backup.beforeOperation
? `before "${backup.beforeOperation}"`
: undefined;
const description = `${this.snippets.getSnippetCount(
backup.elements
)} snippet${
this.snippets.getSnippetCount(backup.elements) === 1 ? "" : "s"
}`;

return {
label: time,
item: backup,
description,
detail,
};
});

items.sort((a, b) => b.item.dateUnix - a.item.dateUnix);

return items;
}

async restoreBackup(id: string) {
const backup = this.backups.find((backup) => backup.id === id);

if (!backup) {
console.error(`Backup with id ${id} not found.`);
return;
}

this.elementsBeforeRestore = this.snippets.getElements();
await this.snippets.replaceElements(backup.elements);
}

async undoLastRestore() {
if (this.elementsBeforeRestore === null) {
return;
}

await this.snippets.replaceElements(this.elementsBeforeRestore);
this.elementsBeforeRestore = null;
}

private load(): void {
this.backups = JSON.parse(
this.context.globalState.get(STORAGE_KEY) || "[]"
) as Backup[];
}

private async makeBackup(
elements: TreeElement[],
operation?: StorageOperation
) {
if (!getConfig("saveBackups")) {
return;
}

const backup: Backup = {
id: randomUUID(),
dateUnix: Date.now(),
elements,
beforeOperation: operation,
};

this.backups.push(backup);

if (this.backups.length > MAX_BACKUPS) {
this.backups.shift();
}

await this.save();
}

private async save() {
await this.context.globalState.update(
STORAGE_KEY,
JSON.stringify(this.backups)
);
}
}
2 changes: 0 additions & 2 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ export async function pickLanguage() {
const languages = await vscode.languages.getLanguages();
const disposables: Disposable[] = [];

// return await vscode.window.showQuickPick(languages);

try {
return await new Promise<string | undefined>((resolve) => {
const input = vscode.window.createQuickPick<LanguageItem>();
Expand Down
7 changes: 7 additions & 0 deletions src/date.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function formatUnixTime(ms: number): string {
const date = new Date(ms);
return `${date.toDateString()}, ${date.toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
})}`;
}
37 changes: 33 additions & 4 deletions src/endpoints.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import * as vscode from "vscode";
import { BackupManager } from "./backupManager";
import * as clipboard from "./clipboard";
import { pickLanguage, getLanguage, getConfig } from "./config";
import { query } from "./query";
import { getConfig, getLanguage, pickLanguage } from "./config";
import { formatUnixTime } from "./date";
import languages from "./languages";
import { encodeRequest } from "./provider";
import { query } from "./query";
import snippet from "./snippet";
import { SnippetsTreeProvider, SnippetsTreeItem } from "./snippetsTreeProvider";
import SnippetsStorage from "./snippetsStorage";
import languages from "./languages";
import { SnippetsTreeItem, SnippetsTreeProvider } from "./snippetsTreeProvider";

export interface Request {
language: string;
Expand Down Expand Up @@ -383,3 +385,30 @@ export function createFolder(treeProvider: SnippetsTreeProvider) {
await treeProvider.storage.createFolder(folderName, item?.id);
};
}

export function showBackups(backupManager: BackupManager) {
return async () => {
const backups = backupManager.getBackupItems();
const selectedBackup = await vscode.window.showQuickPick(backups, {
placeHolder:
"Select a backup to restore. You will be able to undo this operation.",
title: "Select a backup",
});

if (!selectedBackup) {
return;
}

await backupManager.restoreBackup(selectedBackup.item.id);
await vscode.commands.executeCommand("snippetsView.focus");
const answer = await vscode.window.showInformationMessage(
`Restored backup from ${formatUnixTime(selectedBackup.item.dateUnix)}`,
"Ok",
"Undo"
);

if (answer === "Undo") {
await backupManager.undoLastRestore();
}
};
}
Loading

0 comments on commit fe36ace

Please sign in to comment.