joplin-plugin-backup 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,14 @@
1
+ name: Issue assignment
2
+
3
+ on:
4
+ issues:
5
+ types: [opened]
6
+
7
+ jobs:
8
+ auto-assign:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - name: "Auto-assign issue"
12
+ uses: pozil/auto-assign-issue@v1.4.0
13
+ with:
14
+ assignees: JackGruber
package/README.md CHANGED
@@ -34,7 +34,6 @@ Go to `Tools > Options > Backup`
34
34
  | Option | Description | Default |
35
35
  | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------- |
36
36
  | `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! | |
37
- | `Single JEX` | Create only one JEX file for all notebooks | `false` |
38
37
  | `Keep x backups` | How many backups should be kept | `1` |
39
38
  | `Backups interval in hours` | Create a backup every X hours | `24` |
40
39
  | `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` |
@@ -44,6 +43,9 @@ Go to `Tools > Options > Backup`
44
43
  | `Zip compression Level` | Compression level for zip archive archive | `Copy (no compression)` |
45
44
  | `Temporary export path` | The data is first exported into this path before it is copied to the backup `Backup path`. | `` |
46
45
  | `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}` |
46
+ | `Single JEX` | Create only one JEX file for all, this option is recommended to prevent the loss of internal note links or folder structure during a restore! | `true` |
47
+ | `Create Subfolder` | Create a sub folder `JoplinBackup` in the configured `Backup path`. Deactivate only if there is no other data in the `Backup path`! | `true` |
48
+ | `Backup plugins` | Backup the plugin folder from the Joplin profile with all installed plugin jpl files. | `true` |
47
49
 
48
50
  ## Keyboard Shortcuts
49
51
 
@@ -53,19 +55,20 @@ Under `Options > Keyboard Shortcuts` you can assign a keyboard shortcut for the
53
55
 
54
56
  ## What is backed up
55
57
 
56
- - Notebooks as JEX export (Empty notbooks are not backed up)
58
+ - Notebooks as JEX export (Empty notebooks are not backed up)
57
59
  - The `settings.json` (Joplin settings)
58
60
  - The `keymap-desktop.json` (Keyboard shortcuts)
59
61
  - The `userchrome.css` (Your Joplin customization)
60
62
  - The `userstyle.css` (Your Joplin customization)
61
63
  - The `templates` folder (Note templates)
64
+ - The `plugin` folder (All installed plugins, no plugin settings!)
62
65
 
63
66
  ## Restore
64
67
 
65
68
  ### Settings
66
69
 
67
70
  To restore the Settings, copy the desired files from `<Backup Path>\Profile` to the Joplin directory `.config\joplin-desktop`.
68
- The exact path can be found in Joplin under `Tools > Options > Generla`:
71
+ The exact path can be found in Joplin under `Tools > Options > General`:
69
72
 
70
73
  <img src=img/joplin_path_in_gui.jpg>
71
74
 
@@ -78,6 +81,27 @@ The notes are imported via `File > Import > JEX - Joplin Export File`.
78
81
  The notes are imported additionally, no check for duplicates is performed.
79
82
  If the folder in which the note was located already exists in you Joplin, than the folder name is extended by one (1).
80
83
 
84
+ ## FAQ
85
+
86
+ ### Internal Joplin links betwen notes are lost
87
+
88
+ If several JEX files are imported and the notes have links to each other, these links will be lost.
89
+ Therefore it is recommended to create a Single JEX Backup!
90
+
91
+ ### Compine multiple JEX Files to one
92
+
93
+ By combining the JEX files into one, the Joplin internal links will work again after the import.
94
+
95
+ 1. Open one of the JEX files in a ZIP program like 7-Zip
96
+ 2. Open a second JEX and add all files to the first JEX
97
+ 3. Repeat step 2 for all files
98
+ 4. Import first JEX which now contains all notes
99
+
100
+ ## Open a JEX Backup file
101
+
102
+ A Joplin JEX Backup file is a tar archive which can be opened with any zip program that supports TAR archive.
103
+ The file names in the archive correspond to the Joplin internal IDs.
104
+
81
105
  ## Changelog
82
106
 
83
107
  See [CHANGELOG.md](CHANGELOG.md)
@@ -1,9 +1,9 @@
1
1
  import { Backup } from "../src/Backup";
2
2
  import * as fs from "fs-extra";
3
3
  import * as path from "path";
4
- import { joplinWrapper } from "../src/joplinWrapper";
5
4
  import { when } from "jest-when";
6
5
  import { sevenZip } from "../src/sevenZip";
6
+ import joplin from "api";
7
7
 
8
8
  function getTestPaths(): any {
9
9
  const testPath: any = {};
@@ -24,12 +24,13 @@ let spyOnLogVerbose = null;
24
24
  let spyOnLogInfo = null;
25
25
  let spyOnLogWarn = null;
26
26
  let spyOnLogError = null;
27
+ let spyOnShowError = null;
27
28
  let spyOnSaveBackupInfo = null;
28
29
 
29
- const spyOnsSettingsValue = jest.spyOn(joplinWrapper, "settingsValue");
30
- const spyOnGlobalValue = jest.spyOn(joplinWrapper, "settingsGlobalValue");
30
+ const spyOnsSettingsValue = jest.spyOn(joplin.settings, "value");
31
+ const spyOnGlobalValue = jest.spyOn(joplin.settings, "globalValue");
31
32
  const spyOnSettingsSetValue = jest
32
- .spyOn(joplinWrapper, "settingsSetValue")
33
+ .spyOn(joplin.settings, "setValue")
33
34
  .mockImplementation();
34
35
 
35
36
  async function createTestStructure() {
@@ -73,6 +74,10 @@ describe("Backup", function () {
73
74
  spyOnLogError = jest
74
75
  .spyOn(backup.log, "error")
75
76
  .mockImplementation(() => {});
77
+
78
+ spyOnShowError = jest
79
+ .spyOn(backup, "showError")
80
+ .mockImplementation(() => {});
76
81
  });
77
82
 
78
83
  afterEach(async () => {
@@ -80,6 +85,7 @@ describe("Backup", function () {
80
85
  spyOnLogInfo.mockReset();
81
86
  spyOnLogWarn.mockReset();
82
87
  spyOnLogError.mockReset();
88
+ spyOnShowError.mockReset();
83
89
  spyOnsSettingsValue.mockReset();
84
90
  spyOnGlobalValue.mockReset();
85
91
  spyOnSaveBackupInfo.mockReset();
@@ -88,29 +94,72 @@ describe("Backup", function () {
88
94
  afterAll(async () => {
89
95
  fs.removeSync(testPath.base);
90
96
  });
91
-
92
97
  describe("Backup path", function () {
93
- it(`Backup path != Profile`, async () => {
94
- await backup.loadBackupPath();
95
- expect(backup.backupBasePath).toBe(testPath.backupBasePath);
96
- expect(backup.backupBasePath).not.toBe(testPath.joplinProfile);
97
-
98
- /* prettier-ignore */
99
- when(spyOnsSettingsValue)
100
- .calledWith("path").mockImplementation(() => Promise.resolve(""));
101
- await backup.loadBackupPath();
102
- expect(backup.backupBasePath).not.toBe(testPath.joplinProfile);
103
- expect(backup.backupBasePath).toBe(null);
98
+ it(`Path tests`, async () => {
99
+ const testCases = [
100
+ {
101
+ backupPath: testPath.joplinProfile,
102
+ createSubfolder: false,
103
+ expectedBackupPath: null,
104
+ expectedBackupPathExist: null,
105
+ },
106
+ {
107
+ backupPath: testPath.joplinProfile,
108
+ createSubfolder: true,
109
+ expectedBackupPath: path.join(testPath.joplinProfile, "JoplinBackup"),
110
+ expectedBackupPathExist: true,
111
+ },
112
+ {
113
+ backupPath: testPath.backupBasePath,
114
+ createSubfolder: false,
115
+ expectedBackupPath: testPath.backupBasePath,
116
+ expectedBackupPathExist: true,
117
+ },
118
+ {
119
+ backupPath: testPath.backupBasePath,
120
+ createSubfolder: true,
121
+ expectedBackupPath: path.join(
122
+ testPath.backupBasePath,
123
+ "JoplinBackup"
124
+ ),
125
+ expectedBackupPathExist: true,
126
+ },
127
+ {
128
+ backupPath: path.join(testPath.backupBasePath, "NotExisting"),
129
+ createSubfolder: false,
130
+ expectedBackupPath: path.join(testPath.backupBasePath, "NotExisting"),
131
+ expectedBackupPathExist: false,
132
+ },
133
+ {
134
+ backupPath: path.join(testPath.backupBasePath, "NotExisting"),
135
+ createSubfolder: true,
136
+ expectedBackupPath: path.join(
137
+ testPath.backupBasePath,
138
+ "NotExisting",
139
+ "JoplinBackup"
140
+ ),
141
+ expectedBackupPathExist: false,
142
+ },
143
+ ];
104
144
 
105
- /* prettier-ignore */
106
- when(spyOnsSettingsValue)
107
- .calledWith("path").mockImplementation(() => Promise.resolve(testPath.joplinProfile));
108
- await backup.loadBackupPath();
109
- expect(backup.backupBasePath).not.toBe(testPath.joplinProfile);
110
- expect(backup.backupBasePath).toBe(null);
145
+ for (const testCase of testCases) {
146
+ when(spyOnsSettingsValue)
147
+ .calledWith("path")
148
+ .mockImplementation(() => Promise.resolve(testCase.backupPath));
149
+ backup.createSubfolder = testCase.createSubfolder;
150
+ await backup.loadBackupPath();
151
+ expect(backup.backupBasePath).toBe(testCase.expectedBackupPath);
152
+ expect(backup.backupBasePath).not.toBe(testPath.joplinProfile);
153
+
154
+ if (testCase.expectedBackupPathExist !== null) {
155
+ expect(fs.existsSync(backup.backupBasePath)).toBe(
156
+ testCase.expectedBackupPathExist
157
+ );
158
+ }
111
159
 
112
- expect(backup.log.error).toHaveBeenCalledTimes(0);
113
- expect(backup.log.warn).toHaveBeenCalledTimes(0);
160
+ expect(backup.log.error).toHaveBeenCalledTimes(0);
161
+ expect(backup.log.warn).toHaveBeenCalledTimes(0);
162
+ }
114
163
  });
115
164
 
116
165
  it(`relative paths`, async () => {
@@ -0,0 +1,74 @@
1
+ import { helper } from "../src/helper";
2
+
3
+ describe("Test helper", function () {
4
+ it(`validFileName`, async () => {
5
+ const testCases = [
6
+ {
7
+ fileName: "some test file.txt",
8
+ expected: true,
9
+ },
10
+ {
11
+ fileName: "some ^test file.txt",
12
+ expected: true,
13
+ },
14
+ {
15
+ fileName: "some :test file.txt",
16
+ expected: false,
17
+ },
18
+ {
19
+ fileName: "some \\test file.txt",
20
+ expected: false,
21
+ },
22
+ {
23
+ fileName: "some |test file.txt",
24
+ expected: false,
25
+ },
26
+ {
27
+ fileName: "some /test file.txt",
28
+ expected: false,
29
+ },
30
+ {
31
+ fileName: "some *test file.txt",
32
+ expected: false,
33
+ },
34
+ {
35
+ fileName: "some ?test file.txt",
36
+ expected: false,
37
+ },
38
+ {
39
+ fileName: "some <test file.txt",
40
+ expected: false,
41
+ },
42
+ {
43
+ fileName: "some >test file.txt",
44
+ expected: false,
45
+ },
46
+ {
47
+ fileName: "com9.txt",
48
+ expected: false,
49
+ },
50
+ {
51
+ fileName: "nul.txt",
52
+ expected: false,
53
+ },
54
+ {
55
+ fileName: "prn.txt",
56
+ expected: false,
57
+ },
58
+ {
59
+ fileName: "con.txt",
60
+ expected: false,
61
+ },
62
+ {
63
+ fileName: "lpt5.txt",
64
+ expected: false,
65
+ },
66
+ ];
67
+
68
+ for (const testCase of testCases) {
69
+ expect(await helper.validFileName(testCase.fileName)).toBe(
70
+ testCase.expected
71
+ );
72
+ }
73
+ });
74
+ });
@@ -0,0 +1,82 @@
1
+ import { Backup } from "../src/Backup";
2
+ import joplin from "api";
3
+ import { when } from "jest-when";
4
+
5
+ let backup = null;
6
+
7
+ describe("Password", function () {
8
+ beforeEach(async () => {
9
+ backup = new Backup() as any;
10
+ });
11
+
12
+ it(`Check`, async () => {
13
+ const spyOnsSettingsValue = jest.spyOn(joplin.settings, "value");
14
+ const spyOnsSettingsSetValue = jest.spyOn(joplin.settings, "setValue");
15
+
16
+ const testCases = [
17
+ {
18
+ usePassword: false,
19
+ password: "",
20
+ passwordRepeat: "",
21
+ expected: 0,
22
+ called: 2,
23
+ },
24
+ {
25
+ usePassword: false,
26
+ password: "test",
27
+ passwordRepeat: "test",
28
+ expected: 0,
29
+ called: 2,
30
+ },
31
+ {
32
+ usePassword: false,
33
+ password: "testA",
34
+ passwordRepeat: "testB",
35
+ expected: 0,
36
+ called: 2,
37
+ },
38
+ {
39
+ usePassword: true,
40
+ password: "test",
41
+ passwordRepeat: "test",
42
+ expected: 1,
43
+ called: 0,
44
+ },
45
+ {
46
+ usePassword: true,
47
+ password: "testA",
48
+ passwordRepeat: "testB",
49
+ expected: -1,
50
+ called: 2,
51
+ },
52
+ {
53
+ usePassword: true,
54
+ password: " ",
55
+ passwordRepeat: " ",
56
+ expected: -1,
57
+ called: 2,
58
+ },
59
+ {
60
+ usePassword: true,
61
+ password: "",
62
+ passwordRepeat: " ",
63
+ expected: -1,
64
+ called: 2,
65
+ },
66
+ ];
67
+
68
+ for (const testCase of testCases) {
69
+ /* prettier-ignore */
70
+ when(spyOnsSettingsValue)
71
+ .mockImplementation(() => Promise.resolve("no mockImplementation"))
72
+ .calledWith("usePassword").mockImplementation(() => Promise.resolve(testCase.usePassword))
73
+ .calledWith("password").mockImplementation(() => Promise.resolve(testCase.password))
74
+ .calledWith("passwordRepeat").mockImplementation(() => Promise.resolve(testCase.passwordRepeat));
75
+ expect(await backup.checkPassword()).toBe(testCase.expected);
76
+
77
+ await backup.enablePassword();
78
+ expect(spyOnsSettingsSetValue).toBeCalledTimes(testCase.called);
79
+ spyOnsSettingsSetValue.mockReset();
80
+ }
81
+ });
82
+ });
@@ -32,7 +32,9 @@ describe("Test sevenZip", function () {
32
32
  expect(fs.existsSync(file3)).toBe(true);
33
33
  expect(fs.existsSync(zip)).toBe(false);
34
34
 
35
- expect(await sevenZip.add(zip, testBaseDir + "\\*", "secret")).toBe(true);
35
+ expect(await sevenZip.add(zip, path.join(testBaseDir, "*"), "secret")).toBe(
36
+ true
37
+ );
36
38
  expect(fs.existsSync(zip)).toBe(true);
37
39
 
38
40
  expect(await sevenZip.passwordProtected(zip)).toBe(true);
@@ -0,0 +1,16 @@
1
+ import { sevenZip, pathTo7zip } from "../src/sevenZip";
2
+ import * as path from "path";
3
+ import joplin from "api";
4
+
5
+ it(`Set bin from joplin`, async () => {
6
+ const pathBevor = pathTo7zip;
7
+ const pathAdd = "addJoplinPath";
8
+ const pathAfter = path.join(pathAdd, "7zip-bin", pathTo7zip);
9
+
10
+ jest.spyOn(joplin.plugins, "installationDir").mockImplementation(async () => {
11
+ return pathAdd;
12
+ });
13
+
14
+ await sevenZip.updateBinPath();
15
+ expect(pathTo7zip).toBe(pathAfter);
16
+ });
package/package.json CHANGED
@@ -1,10 +1,14 @@
1
1
  {
2
2
  "name": "joplin-plugin-backup",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "scripts": {
5
5
  "dist": "webpack --joplin-plugin-config buildMain && webpack --joplin-plugin-config buildExtraScripts && webpack --joplin-plugin-config createArchive",
6
6
  "prepare": "npm run dist && husky install",
7
7
  "update": "npm install -g generator-joplin && yo joplin --update",
8
+ "release": "npm test && node ./node_modules/joplinplugindevtools/dist/createRelease.js",
9
+ "preRelease": "npm test && node ./node_modules/joplinplugindevtools/dist/createRelease.js --prerelease",
10
+ "gitRelease": "node ./node_modules/joplinplugindevtools/dist/createRelease.js --upload",
11
+ "gitPreRelease": "node ./node_modules/joplinplugindevtools/dist/createRelease.js --upload --prerelease",
8
12
  "test": "jest"
9
13
  },
10
14
  "license": "MIT",
@@ -14,14 +18,18 @@
14
18
  "devDependencies": {
15
19
  "@types/jest": "^26.0.23",
16
20
  "@types/node": "^14.0.14",
21
+ "axios": "^0.21.1",
17
22
  "chalk": "^4.1.0",
18
23
  "copy-webpack-plugin": "^6.1.0",
24
+ "dotenv": "^10.0.0",
19
25
  "fs-extra": "^9.0.1",
20
26
  "glob": "^7.1.6",
21
27
  "husky": "^6.0.0",
22
28
  "jest": "^27.0.4",
23
29
  "jest-when": "^3.3.1",
30
+ "joplinplugindevtools": "^1.0.14",
24
31
  "lint-staged": "^11.0.0",
32
+ "mime": "^2.5.2",
25
33
  "on-build-webpack": "^0.1.0",
26
34
  "prettier": "2.3.0",
27
35
  "tar": "^6.0.5",
@@ -56,11 +64,8 @@
56
64
  "js"
57
65
  ],
58
66
  "moduleNameMapper": {
59
- "^api$": "<rootDir>/api",
67
+ "^api$": "<rootDir>/node_modules/joplinplugindevtools/dist/apiMock.js",
60
68
  "^api/types$": "<rootDir>/api/types"
61
- },
62
- "globals": {
63
- "joplin": true
64
69
  }
65
70
  }
66
71
  }
@@ -2,7 +2,7 @@
2
2
  "manifest_version": 1,
3
3
  "id": "io.github.jackgruber.backup",
4
4
  "app_min_version": "2.1.3",
5
- "version": "1.0.2",
5
+ "version": "1.1.0",
6
6
  "name": "Simple Backup",
7
7
  "description": "Plugin to create manual and automatic backups.",
8
8
  "author": "JackGruber",
@@ -16,6 +16,6 @@
16
16
  "7zip",
17
17
  "encrypted"
18
18
  ],
19
- "_publish_hash": "sha256:89923c42b44e161c6d0cd703f0cb2d6cb55e8d4e3f2c5b9efb8e1038c16448d9",
20
- "_publish_commit": "master:022ef0dbc87ce71ee911a38c53e394d7e16e2d4a"
19
+ "_publish_hash": "sha256:8d8c6a3bb92fafc587269aea58b623b05242d42c0766a05bbe25c3ba2bbdf8ee",
20
+ "_publish_commit": "master:00ed52133c659e0f3ac1a55f70b776c42fca0a6d"
21
21
  }
package/CHANGELOG.md DELETED
@@ -1,80 +0,0 @@
1
- # Changelog
2
-
3
- ## not released
4
-
5
- ## v1.0.2 (2021-07-19)
6
-
7
- - Improved: Use of moments token
8
- - Fix: #16 Prevent multiple simultaneous backup runs
9
- - Add: #11 Make zip compression level selectable
10
- - Fix: Delete old backup set information, if the backup set no longer exists
11
-
12
- ## v1.0.1 (2021-07-03)
13
-
14
- Release for Joplin plugin manager
15
-
16
- ## v1.0.0 [pre-release] (2021-06-20)
17
-
18
- - Add: Option for encrypted backups
19
-
20
- > ❗️ Requires at least Joplin `2.1.3` ❗️
21
-
22
- ## v0.9.0 [pre-release] (2021-06-19)
23
-
24
- - Add: Relative path could be used for `Backup Path`
25
- - Fix: An error with `Only on change`, which was not working properly
26
- - Add: Option to create zip archive
27
- - Add: Option to specify the `Backup set name` if multiple backups are to be keep.
28
-
29
- ## v0.5.3 (2021-04-03)
30
-
31
- - Add: Backup settings.json
32
- - Optimize: #7 Better error message when a backup is created twice in a row in the same minute
33
-
34
- ## v0.5.2 (2021-02-13)
35
-
36
- - Only internal changes
37
-
38
- ## v0.5.1 (2021-02-07)
39
-
40
- - Fix: Incomplete backup.log with only one backup set
41
-
42
- ## v0.5.0 (2021-02-07)
43
-
44
- - Add: Option for Backuplogfile
45
- - Add: Option to create a backup only if something has changed #3
46
-
47
- ## v0.4.1 (2021-01-23)
48
-
49
- - Optimize: Store profile data in `profile` folder
50
-
51
- ## v0.4.0 (2021-01-21)
52
-
53
- - Add `templates` to backup
54
- - Optimize: Delete old backups at the end of the backup job
55
-
56
- ## v0.3.1 (2021-01-21)
57
-
58
- - Fix #1: Unsupported characters in filename
59
-
60
- ## v0.3.0 (2021-01-17)
61
-
62
- - Fix: Backup not only the last 50 notebooks
63
- - Add: Backup userchrome.css and userstyle.css
64
- - Add: Option to create single file JEX for all notebooks
65
-
66
- ## v0.2.2 (2021-01-17)
67
-
68
- - Fix: Check if keymap-desktop.json exists
69
-
70
- ## v0.2.1 (2021-01-16)
71
-
72
- - remove seconds from folder
73
-
74
- ## v0.2.0 (2021-01-14)
75
-
76
- - Add: Automatic backups every X hours
77
-
78
- ## v0.1.0 (2021-01-14)
79
-
80
- - First version