Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
JackGruber committed Jul 19, 2021
2 parents 010744b + 8e4a133 commit 022ef0d
Show file tree
Hide file tree
Showing 9 changed files with 159 additions and 59 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

## not released

## v1.0.2 (2021-07-19)

- Improved: Use of moments token
- Fix: #16 Prevent multiple simultaneous backup runs
- Add: #11 Make zip compression level selectable
- Fix: Delete old backup set information, if the backup set no longer exists

## v1.0.1 (2021-07-03)

Release for Joplin plugin manager
Expand Down
25 changes: 13 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,19 @@ The backup started manually by `Create backup` respects all the settings except

Go to `Tools > Options > Backup`

| Option | Description | Default |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------- |
| `Backup path` | Where to save the backups to. <br>This path is exclusive for the Joplin backups, there should be no other data in it! | |
| `Single JEX` | Create only one JEX file for all notebooks | `false` |
| `Keep x backups` | How many backups should be kept | `1` |
| `Backups interval in hours` | Create a backup every X hours | `24` |
| `Only on change` | Creates a backup at the specified backup interval only if there was a change to a `note`, `tag`, `resource` or `notebook` | `false` |
| `Password protected backups` | Protect the backups via encrypted Zip archive. | `false` |
| `Logfile` | Loglevel for backup.log | `error` |
| `Create zip archive` | Save backup data in a Zip archive | `No` |
| `Temporary export path` | The data is first exported into this path before it is copied to the backup `Backup path`. | `` |
| `Backup set name` | Name of the backup set if multiple backups are to be keep. [Available moment tokens](https://momentjs.com/docs/#/displaying/format/), which can be used with `{<TOKEN>}` | `{YYYYMMDDHHmm}` |
| Option | Description | Default |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------- |
| `Backup path` | Where to save the backups to. <br>This path is exclusive for the Joplin backups, there should be no other data in it! | |
| `Single JEX` | Create only one JEX file for all notebooks | `false` |
| `Keep x backups` | How many backups should be kept | `1` |
| `Backups interval in hours` | Create a backup every X hours | `24` |
| `Only on change` | Creates a backup at the specified backup interval only if there was a change to a `note`, `tag`, `resource` or `notebook` | `false` |
| `Password protected backups` | Protect the backups via encrypted Zip archive. | `false` |
| `Logfile` | Loglevel for backup.log | `error` |
| `Create zip archive` | Save backup data in a Zip archive | `No` |
| `Zip compression Level` | Compression level for zip archive archive | `Copy (no compression)` |
| `Temporary export path` | The data is first exported into this path before it is copied to the backup `Backup path`. | `` |
| `Backup set name` | Name of the backup set if multiple backups are to be keep. [Available moment tokens](https://momentjs.com/docs/#/displaying/format/), which can be used with `{<TOKEN>}` | `{YYYYMMDDHHmm}` |

## Keyboard Shortcuts

Expand Down
4 changes: 4 additions & 0 deletions __test__/backup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,10 @@ describe("Backup", function () {
backupSetName: "{YYYYMMDDHHmm}",
expected: "202101021630",
},
{
backupSetName: "{YYYY-MM-DD HH:mm}",
expected: "2021-01-02 16:30",
},
{
backupSetName: "Joplinbackup_{YYYYMMDDHHmm}",
expected: "Joplinbackup_202101021630",
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "joplin-plugin-backup",
"version": "1.0.1",
"version": "1.0.2",
"scripts": {
"dist": "webpack --joplin-plugin-config buildMain && webpack --joplin-plugin-config buildExtraScripts && webpack --joplin-plugin-config createArchive",
"prepare": "npm run dist && husky install",
Expand Down
155 changes: 114 additions & 41 deletions src/Backup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class Backup {
private password: string;
private backupStartTime: Date;
private zipArchive: string;
private compressionLevel: number;
private singleJex: boolean;
private backupSetName: string;

Expand All @@ -43,6 +44,7 @@ class Backup {
await this.upgradeBackupTargetVersion();
await sevenZip.updateBinPath();
await sevenZip.setExecutionFlag();
this.backupStartTime = null;
}

private async upgradeBackupTargetVersion() {
Expand Down Expand Up @@ -201,6 +203,9 @@ class Backup {
this.backupRetention = await joplinWrapper.settingsValue("backupRetention");

this.zipArchive = await joplinWrapper.settingsValue("zipArchive");
this.compressionLevel = await joplinWrapper.settingsValue(
"compressionLevel"
);
this.singleJex = await joplin.settings.value("singleJex");

this.backupSetName = await joplinWrapper.settingsValue("backupSetName");
Expand Down Expand Up @@ -239,6 +244,7 @@ class Backup {
await joplin.views.dialogs.setButtons(this.errorDialog, [{ id: "ok" }]);
await joplin.views.dialogs.setHtml(this.errorDialog, html.join("\n"));
await joplin.views.dialogs.open(this.errorDialog);
this.backupStartTime = null;
}

public async setActiveBackupPath() {
Expand Down Expand Up @@ -271,62 +277,103 @@ class Backup {
}
}

public async start(showDoneMsg: boolean = false) {
this.log.verbose("start");

await this.stopTimer();

this.backupStartTime = new Date();
await this.loadSettings();
private async logSettings(showDoneMsg: boolean) {
const settings = [
"path",
"singleJex",
"backupRetention",
"backupInterval",
"onlyOnChange",
"usePassword",
"lastBackup",
"fileLogLevel",
"zipArchive",
"compressionLevel",
"exportPath",
"backupSetName",
"backupInfo",
];

this.log.verbose("Plugin settings:");
for (let setting of settings) {
this.log.verbose(setting + ": " + (await joplin.settings.value(setting)));
}
this.log.verbose("activeBackupPath: " + this.activeBackupPath);
this.log.verbose("backupBasePath: " + this.backupBasePath);
this.log.verbose("logFile: " + this.logFile);
this.log.verbose("showDoneMsg: " + showDoneMsg);
this.log.verbose(
"installationDir: " + (await joplin.plugins.installationDir())
);
}

if (this.backupBasePath === null) {
await this.showError(
"Please configure backup path in Joplin Tools > Options > Backup"
);
return;
}
public async start(showDoneMsg: boolean = false) {
if (this.backupStartTime === null) {
this.backupStartTime = new Date();

if (fs.existsSync(this.backupBasePath)) {
await this.deleteLogFile();
await this.fileLogging(true);
this.log.info("Backup started");

if ((await this.checkPassword()) === -1) {
await this.showError("Passwords do not match!");
await this.stopTimer();

await this.loadSettings();

await this.logSettings(showDoneMsg);

if (this.backupBasePath === null) {
await this.showError(
"Please configure backup path in Joplin Tools > Options > Backup"
);
return;
} else {
this.log.info("Enable password protection: " + this.passwordEnabled);
}
this.log.verbose(`Backup path: ${this.backupBasePath}`);
this.log.verbose(
`Active backup path (export path): ${this.activeBackupPath}`
);
await this.createEmptyFolder(this.activeBackupPath, "");

await this.backupProfileData();
if (fs.existsSync(this.backupBasePath)) {
if ((await this.checkPassword()) === -1) {
await this.showError("Passwords do not match!");
return;
} else {
this.log.info("Enable password protection: " + this.passwordEnabled);
}
this.log.verbose(`Backup path: ${this.backupBasePath}`);
this.log.verbose(
`Active backup path (export path): ${this.activeBackupPath}`
);
await this.createEmptyFolder(this.activeBackupPath, "");

await this.backupProfileData();

await this.backupNotebooks();
await this.backupNotebooks();

const backupDst = await this.makeBackupSet();
const backupDst = await this.makeBackupSet();

await joplin.settings.setValue(
"lastBackup",
this.backupStartTime.getTime()
);
this.log.info("Backup finished to: " + backupDst);
await joplin.settings.setValue(
"lastBackup",
this.backupStartTime.getTime()
);
this.log.info("Backup finished to: " + backupDst);

this.log.info("Backup completed");
await this.fileLogging(false);
this.log.info("Backup completed");
await this.fileLogging(false);

this.moveLogFile(backupDst);
this.moveLogFile(backupDst);
} else {
await this.showError(
`The Backup path '${this.backupBasePath}' does not exist!`
);
}

this.backupStartTime = null;
await this.startTimer();
} else {
await this.showError(
`The Backup path '${this.backupBasePath}' does not exist!`
this.log.warn(
"Backup already running since " +
moment(this.backupStartTime).format("YYYY-MM-DD HH:MM:SS") +
" (" +
this.backupStartTime.getTime() +
")"
);
}

this.backupStartTime = null;
await this.startTimer();
}

private async makeBackupSet(): Promise<string> {
Expand Down Expand Up @@ -412,7 +459,19 @@ class Backup {
options: string[] = null
): Promise<string> {
this.log.verbose(`Add ${addFile} to zip ${zipFile}`);
const status = await sevenZip.add(zipFile, addFile, password, options);

let zipOptions: any = {};
if (options) {
zipOptions = { ...zipOptions, ...options };
}
zipOptions.method = [];
if (this.compressionLevel) {
zipOptions.method.push("x" + this.compressionLevel);
} else {
zipOptions.method.push("x0");
}

const status = await sevenZip.add(zipFile, addFile, password, zipOptions);
if (status !== true) {
await this.showError("createZipArchive: " + status);
throw new Error("createZipArchive: " + status);
Expand Down Expand Up @@ -629,7 +688,7 @@ class Backup {
}

private async getBackupSetFolderName(folder: string = null): Promise<string> {
return this.backupSetName.replace(/{(\w+)}/g, (match, groups) => {
return this.backupSetName.replace(/{([^}]+)}/g, (match, groups) => {
const now = new Date(Date.now());
return moment(now.getTime()).format(groups);
});
Expand Down Expand Up @@ -846,7 +905,19 @@ class Backup {
backupPath: string,
backupRetention: number
) {
this.log.verbose("deleteOldBackupSets");
let info = JSON.parse(await joplinWrapper.settingsValue("backupInfo"));
let setOk = [];
for (let check of info) {
const folder = path.join(backupPath, check.name);
if (fs.existsSync(folder)) {
setOk.push(check);
} else {
this.log.verbose("Backup set " + folder + " no longer exist");
}
}
await joplinWrapper.settingsSetValue("backupInfo", JSON.stringify(setOk));
info = JSON.parse(await joplinWrapper.settingsValue("backupInfo"));
if (info.length > backupRetention) {
info.sort((a, b) => b.date - a.date);
}
Expand All @@ -855,6 +926,8 @@ class Backup {
const del = info.splice(backupRetention, 1);
const folder = path.join(backupPath, del[0].name);
if (fs.existsSync(folder)) {
this.log.verbose("Remove backup set " + folder);

try {
fs.rmdirSync(folder, {
recursive: true,
Expand Down
2 changes: 1 addition & 1 deletion src/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"manifest_version": 1,
"id": "io.github.jackgruber.backup",
"app_min_version": "2.1.3",
"version": "1.0.1",
"version": "1.0.2",
"name": "Simple Backup",
"description": "Plugin to create manual and automatic backups.",
"author": "JackGruber",
Expand Down
18 changes: 18 additions & 0 deletions src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,24 @@ export namespace Settings {
description:
"If a password protected backups is set, a zip archive is always created.",
},
compressionLevel: {
value: 0,
type: SettingItemType.Int,
section: "backupSection",
isEnum: true,
public: true,
label: "Zip compression level",
advanced: true,
options: {
0: "Copy (no compression)",
1: "Fastest",
3: "Fast",
5: "Normal",
7: "Maximum",
9: "Ultra",
},
description: "Compression level for zip archive.",
},
exportPath: {
value: "",
type: SettingItemType.String,
Expand Down
3 changes: 0 additions & 3 deletions src/sevenZip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,6 @@ export namespace sevenZip {
_7zOptions = { ..._7zOptions, ...options };
}

_7zOptions.method = [];
_7zOptions.method.push("x0");

if (password !== null) {
_7zOptions = await addPassword(_7zOptions, password);
_7zOptions.method.push("he");
Expand Down

0 comments on commit 022ef0d

Please sign in to comment.