Skip to content

Commit b1a1327

Browse files
committed
Merge branch 'develop'
2 parents 41b7657 + b4640d1 commit b1a1327

12 files changed

Lines changed: 200 additions & 36 deletions

File tree

.husky/pre-commit

100644100755
File mode changed.

.husky/pre-push

100644100755
File mode changed.

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
## not released
44

5+
## v1.5.0 (2025-12-28)
6+
7+
- Fix: Add woraround for "ENOTSUP: operation not supported on socket" #98
8+
- Fix: Backup is overwritten by secondary instances #95
9+
510
## v1.4.5 (2025-12-13)
611

712
- Update: Slovak translation (sk_SK) #90 #94

__test__/backup.test.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,42 +224,83 @@ describe("Backup", function () {
224224
describe("backups per profile", function () {
225225
test.each([
226226
{
227+
altInstanceId: "",
227228
rootProfileDir: testPath.joplinProfile,
228229
profileDir: testPath.joplinProfile,
229230
joplinEnv: "prod",
230231
expectedProfileName: "default",
231232
},
232233
{
234+
altInstanceId: "",
233235
rootProfileDir: testPath.joplinProfile,
234236
profileDir: testPath.joplinProfile,
235237
joplinEnv: "dev",
236238
expectedProfileName: "default-dev",
237239
},
238240
{
241+
altInstanceId: "",
239242
rootProfileDir: testPath.joplinProfile,
240243
profileDir: path.join(testPath.joplinProfile, "profile-test"),
241244
joplinEnv: "prod",
242245
expectedProfileName: "profile-test",
243246
},
244247
{
248+
altInstanceId: "",
245249
rootProfileDir: testPath.joplinProfile,
246250
profileDir: path.join(testPath.joplinProfile, "profile-idhere"),
247251
joplinEnv: "prod",
248252
expectedProfileName: "profile-idhere",
249253
},
250254
{
255+
altInstanceId: "",
251256
rootProfileDir: testPath.joplinProfile,
252257
profileDir: path.join(testPath.joplinProfile, "profile-idhere"),
253258
joplinEnv: "dev",
254259
expectedProfileName: "profile-idhere-dev",
255260
},
261+
{
262+
altInstanceId: "alt1",
263+
rootProfileDir: testPath.joplinProfile,
264+
profileDir: testPath.joplinProfile,
265+
joplinEnv: "prod",
266+
expectedProfileName: "alt1_default",
267+
},
268+
{
269+
altInstanceId: "alt1",
270+
rootProfileDir: testPath.joplinProfile,
271+
profileDir: testPath.joplinProfile,
272+
joplinEnv: "dev",
273+
expectedProfileName: "alt1_default-dev",
274+
},
275+
{
276+
altInstanceId: "alt1",
277+
rootProfileDir: testPath.joplinProfile,
278+
profileDir: path.join(testPath.joplinProfile, "profile-test"),
279+
joplinEnv: "prod",
280+
expectedProfileName: "alt1_profile-test",
281+
},
282+
{
283+
altInstanceId: "alt1",
284+
rootProfileDir: testPath.joplinProfile,
285+
profileDir: path.join(testPath.joplinProfile, "profile-idhere"),
286+
joplinEnv: "prod",
287+
expectedProfileName: "alt1_profile-idhere",
288+
},
289+
{
290+
altInstanceId: "alt1",
291+
rootProfileDir: testPath.joplinProfile,
292+
profileDir: path.join(testPath.joplinProfile, "profile-idhere"),
293+
joplinEnv: "dev",
294+
expectedProfileName: "alt1_profile-idhere-dev",
295+
},
256296
])(
257297
"should correctly set backupBasePath based on the current profile name (case %#)",
258298
async ({
259299
profileDir,
260300
rootProfileDir,
261301
joplinEnv,
262302
expectedProfileName,
303+
altInstanceId,
263304
}) => {
264305
when(spyOnsSettingsValue)
265306
.calledWith("path")
@@ -273,6 +314,9 @@ describe("Backup", function () {
273314
when(spyOnGlobalValue)
274315
.calledWith("env")
275316
.mockImplementation(async () => joplinEnv);
317+
when(spyOnGlobalValue)
318+
.calledWith("altInstanceId")
319+
.mockImplementation(async () => altInstanceId);
276320

277321
// Should use the folder named "default" for the default profile
278322
backup.createSubfolderPerProfile = true;
@@ -1043,6 +1087,8 @@ describe("Backup", function () {
10431087
fs.writeFileSync(file1, "template1");
10441088
fs.writeFileSync(file2, "template2");
10451089

1090+
backup.fsWorkaroundLinux = true;
1091+
10461092
expect(await backup.backupFolder(testPath.templates, dst)).toBe(true);
10471093
expect(fs.existsSync(checkFile1)).toBe(true);
10481094
expect(fs.existsSync(checkFile2)).toBe(true);
@@ -1105,6 +1151,7 @@ describe("Backup", function () {
11051151

11061152
backup.activeBackupPath = testPath.activeBackupJob;
11071153
backup.backupPlugins = true;
1154+
backup.fsWorkaroundLinux = true;
11081155
await backup.backupProfileData();
11091156

11101157
expect(fs.existsSync(backupTemplate)).toBe(true);

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "joplin-plugin-backup",
3-
"version": "1.4.5",
3+
"version": "1.5.0",
44
"scripts": {
55
"dist": "webpack --env joplin-plugin-config=buildMain && webpack --env joplin-plugin-config=buildExtraScripts && webpack --env joplin-plugin-config=createArchive",
66
"prepare": "npm run dist && husky install",

src/Backup.ts

Lines changed: 85 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class Backup {
3737
private execFinishCmd: string;
3838
private suppressErrorMsgUntil: number;
3939
private execPromise = promisify(exec);
40+
private fsWorkaroundLinux: boolean;
4041

4142
constructor() {
4243
this.log = backupLogging;
@@ -263,10 +264,56 @@ class Backup {
263264
);
264265
}
265266

267+
private async getProfileName(profileRootDir: string, rootProfileDir: string) {
268+
// We assume that Joplin's profile structure is the following
269+
// rootProfileDir/
270+
// | profileDir/
271+
// | | [[profile content]]
272+
// or, if using the default,
273+
// rootProfileDir/
274+
// | [[profile content]]
275+
276+
let profileName = path.basename(rootProfileDir);
277+
if (rootProfileDir === profileRootDir) {
278+
profileName = "default";
279+
}
280+
281+
return profileName;
282+
}
283+
284+
private async getInstanceInfo() {
285+
this.log.verbose("getInstanceInfo");
286+
287+
const altInstanceId = await joplin.settings.globalValue("altInstanceId");
288+
289+
const profileDir = await joplin.settings.globalValue("profileDir");
290+
const rootProfileDir = await joplin.settings.globalValue("rootProfileDir");
291+
292+
let env = "";
293+
if ((await joplin.settings.globalValue("env")) === "dev") {
294+
env = "dev";
295+
}
296+
297+
const profileName = await this.getProfileName(rootProfileDir, profileDir);
298+
299+
const data = {
300+
env: env,
301+
altInstanceId: altInstanceId,
302+
profileName: profileName,
303+
rootProfileDir: rootProfileDir,
304+
profileDir: profileDir,
305+
};
306+
for (const [key, value] of Object.entries(data)) {
307+
this.log.verbose(`${key}:`, value);
308+
}
309+
return data;
310+
}
311+
266312
private async loadBackupPath() {
267313
this.log.verbose("loadBackupPath");
314+
const instanceInfo = await this.getInstanceInfo();
268315
const pathSetting = await joplin.settings.value("path");
269-
const profileDir = await joplin.settings.globalValue("profileDir");
316+
const profileDir = instanceInfo["profileDir"];
270317

271318
if (path.isAbsolute(pathSetting)) {
272319
this.backupBasePath = path.normalize(pathSetting);
@@ -302,28 +349,15 @@ class Backup {
302349
this.readmeOutputDirectory = this.backupBasePath;
303350

304351
if (this.createSubfolderPerProfile) {
305-
this.log.verbose("append profile subfolder");
306-
// We assume that Joplin's profile structure is the following
307-
// rootProfileDir/
308-
// | profileDir/
309-
// | | [[profile content]]
310-
// or, if using the default,
311-
// rootProfileDir/
312-
// | [[profile content]]
313-
const profileRootDir = await joplin.settings.globalValue(
314-
"rootProfileDir"
315-
);
316-
const profileCurrentDir = await joplin.settings.globalValue("profileDir");
352+
this.log.verbose("append profile / instance subfolder");
317353

318-
let profileName = path.basename(profileCurrentDir);
319-
if (profileCurrentDir === profileRootDir) {
320-
profileName = "default";
354+
let profileName = "";
355+
if (instanceInfo["altInstanceId"] !== "") {
356+
profileName += instanceInfo["altInstanceId"] + "_";
321357
}
322-
323-
// Appending a -dev to the profile name prevents a devmode default Joplin
324-
// profile from overwriting a non-devmode Joplin profile.
325-
if ((await joplin.settings.globalValue("env")) === "dev") {
326-
profileName += "-dev";
358+
profileName += instanceInfo["profileName"];
359+
if (instanceInfo["env"] !== "") {
360+
profileName += "-" + instanceInfo["env"];
327361
}
328362

329363
this.backupBasePath = path.join(this.backupBasePath, profileName);
@@ -372,6 +406,7 @@ class Backup {
372406
this.singleJex = await joplin.settings.value("singleJexV2");
373407
this.exportFormat = await joplin.settings.value("exportFormat");
374408
this.execFinishCmd = (await joplin.settings.value("execFinishCmd")).trim();
409+
this.fsWorkaroundLinux = await joplin.settings.value("fsWorkaroundLinux");
375410

376411
this.backupPlugins = await joplin.settings.value("backupPlugins");
377412

@@ -488,8 +523,8 @@ class Backup {
488523
"backupVersion",
489524
"backupPlugins",
490525
"createSubfolder",
491-
"createSubfolder",
492526
"exportFormat",
527+
"fsWorkaroundLinux",
493528
];
494529

495530
this.log.verbose("Plugin settings:");
@@ -613,6 +648,7 @@ class Backup {
613648
}
614649

615650
private async makeBackupSet(): Promise<string> {
651+
this.log.verbose("makeBackupSet");
616652
let backupDst = "";
617653
if (this.zipArchive === "no" && this.passwordEnabled === false) {
618654
if (this.backupRetention > 1) {
@@ -626,6 +662,7 @@ class Backup {
626662
backupDst = await this.moveFinishedBackup();
627663
}
628664
} else {
665+
this.log.verbose("Bakupset as zip");
629666
const zipFile = await this.createZipArchive();
630667
if (this.backupRetention > 1) {
631668
backupDst = await this.moveFinishedBackup(zipFile);
@@ -746,7 +783,11 @@ class Backup {
746783
await this.addToZipArchive(logDst, logFile, this.password, ["-sdel"]);
747784
} else {
748785
try {
749-
fs.moveSync(logFile, path.join(logDst, logfileName));
786+
await helper.WorkaroundMove(
787+
logFile,
788+
path.join(logDst, logfileName),
789+
this.fsWorkaroundLinux
790+
);
750791
} catch (e) {
751792
await this.showError("moveLogFile: " + e.message);
752793
throw e;
@@ -1058,7 +1099,7 @@ class Backup {
10581099
if (fs.existsSync(src)) {
10591100
this.log.verbose("Copy " + src);
10601101
try {
1061-
fs.copySync(src, dst);
1102+
await helper.WorkaroundCopyFile(src, dst, this.fsWorkaroundLinux);
10621103
return true;
10631104
} catch (e) {
10641105
await this.showError(
@@ -1076,10 +1117,12 @@ class Backup {
10761117
if (fs.existsSync(src)) {
10771118
this.log.verbose("Copy " + src);
10781119
try {
1079-
fs.copyFileSync(src, dest);
1120+
await helper.WorkaroundCopyFile(src, dest, this.fsWorkaroundLinux);
10801121
return true;
10811122
} catch (e) {
1082-
this.log.error("backupFile: " + e.message);
1123+
await this.showError(
1124+
i18n.__("msg.error.fileCopy", "backupFile", e.message)
1125+
);
10831126
throw e;
10841127
}
10851128
} else {
@@ -1120,7 +1163,11 @@ class Backup {
11201163
}
11211164

11221165
try {
1123-
fs.moveSync(src, backupDestination);
1166+
await helper.WorkaroundMove(
1167+
src,
1168+
backupDestination,
1169+
this.fsWorkaroundLinux
1170+
);
11241171
} catch (e) {
11251172
await this.showError(
11261173
i18n.__("msg.error.fileCopy", "moveFinishedBackup", e.message)
@@ -1136,7 +1183,11 @@ class Backup {
11361183
if (zipFile) {
11371184
backupDestination = path.join(this.backupBasePath, "JoplinBackup.7z");
11381185
try {
1139-
fs.moveSync(zipFile, backupDestination);
1186+
await helper.WorkaroundMove(
1187+
zipFile,
1188+
backupDestination,
1189+
this.fsWorkaroundLinux
1190+
);
11401191
} catch (e) {
11411192
await this.showError(
11421193
i18n.__("msg.error.fileCopy", "moveFinishedBackup", e.message)
@@ -1151,9 +1202,12 @@ class Backup {
11511202
for (const file of backupData) {
11521203
let dst = path.join(backupDestination, file);
11531204
try {
1154-
fs.moveSync(path.join(this.activeBackupPath, file), dst, {
1155-
overwrite: true,
1156-
});
1205+
await helper.WorkaroundMove(
1206+
path.join(this.activeBackupPath, file),
1207+
dst,
1208+
this.fsWorkaroundLinux,
1209+
true
1210+
);
11571211
} catch (e) {
11581212
await this.showError(
11591213
i18n.__("msg.error.fileCopy", "moveFinishedBackup", e.message)

0 commit comments

Comments
 (0)