Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fde4c9a294 | ||
|
|
7fc38a942a | ||
|
|
cc0c4fe224 | ||
|
|
2ab5640c12 | ||
|
|
b7a0376a0f | ||
|
|
d7d1c47e2a | ||
|
|
a65f5eb004 | ||
|
|
2ce34379ad |
+1
-1
@@ -15,7 +15,7 @@ Assuming all sources are reliable.
|
|||||||
We list all combinations mutually exclusive and collectively exhaustive.
|
We list all combinations mutually exclusive and collectively exhaustive.
|
||||||
|
|
||||||
| ID | Remote Files | Local files | Local delete rename history | Extra | Decision |
|
| ID | Remote Files | Local files | Local delete rename history | Extra | Decision |
|
||||||
| ---- | ------------ | ----------- | --------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
|
| --- | ------------ | ----------- | --------------------------- | ----------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
|
||||||
| 1 | exist | exist | ignore | mtime_remote > mtime_local | download remote file, create local folder if not exists, clear local history if exists |
|
| 1 | exist | exist | ignore | mtime_remote > mtime_local | download remote file, create local folder if not exists, clear local history if exists |
|
||||||
| 2 | exist | exist | ignore | mtime_remote < mtime_local | upload local file, create remote folder if not exists, clear local history if exists |
|
| 2 | exist | exist | ignore | mtime_remote < mtime_local | upload local file, create remote folder if not exists, clear local history if exists |
|
||||||
| 3 | exist | exist | ignore | mtime_remote === mtime_local && password === "" && size_remote === size_local | clear local history if exists (the file was synced and no changes after last sync) |
|
| 3 | exist | exist | ignore | mtime_remote === mtime_local && password === "" && size_remote === size_local | clear local history if exists (the file was synced and no changes after last sync) |
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "obsdian-save-remote",
|
"id": "obsdian-save-remote",
|
||||||
"name": "Save remote",
|
"name": "Save remote",
|
||||||
"version": "0.0.10",
|
"version": "0.0.11",
|
||||||
"minAppVersion": "0.12.15",
|
"minAppVersion": "0.12.15",
|
||||||
"description": "This is yet another plugin allowing users to sync notes between local device and the cloud.",
|
"description": "This is yet another plugin allowing users to sync notes between local device and the cloud.",
|
||||||
"author": "fyears",
|
"author": "fyears",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "obsidian-save-remote",
|
"name": "obsidian-save-remote",
|
||||||
"version": "0.0.10",
|
"version": "0.0.11",
|
||||||
"description": "This is yet another sync plugin for Obsidian app.",
|
"description": "This is yet another sync plugin for Obsidian app.",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "webpack --mode development --watch",
|
"dev": "webpack --mode development --watch",
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ import {
|
|||||||
|
|
||||||
const DEFAULT_ITER = 10000;
|
const DEFAULT_ITER = 10000;
|
||||||
|
|
||||||
|
// base32.stringify(Buffer.from('Salted__'))
|
||||||
|
export const MAGIC_ENCRYPTED_PREFIX_BASE32 = "KNQWY5DFMRPV";
|
||||||
|
|
||||||
const getKeyIVFromPassword = async (
|
const getKeyIVFromPassword = async (
|
||||||
salt: Uint8Array,
|
salt: Uint8Array,
|
||||||
password: string,
|
password: string,
|
||||||
|
|||||||
@@ -276,6 +276,11 @@ export const getSyncMetaMappingByRemoteKeyS3 = async (
|
|||||||
throw Error("something bad in sync meta mapping!");
|
throw Error("something bad in sync meta mapping!");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const clearAllSyncMetaMapping = async (db: lf.DatabaseConnection) => {
|
||||||
|
const tbl = db.getSchema().table(DEFAULT_TBL_SYNC_MAPPING);
|
||||||
|
await db.delete().from(tbl).exec();
|
||||||
|
};
|
||||||
|
|
||||||
export const insertSyncPlanRecord = async (
|
export const insertSyncPlanRecord = async (
|
||||||
db: lf.DatabaseConnection,
|
db: lf.DatabaseConnection,
|
||||||
syncPlan: SyncPlanType
|
syncPlan: SyncPlanType
|
||||||
|
|||||||
+143
-12
@@ -11,7 +11,11 @@ import {
|
|||||||
TFolder,
|
TFolder,
|
||||||
} from "obsidian";
|
} from "obsidian";
|
||||||
import * as CodeMirror from "codemirror";
|
import * as CodeMirror from "codemirror";
|
||||||
import { clearAllSyncPlanRecords, DatabaseConnection } from "./localdb";
|
import {
|
||||||
|
clearAllSyncPlanRecords,
|
||||||
|
clearAllSyncMetaMapping,
|
||||||
|
DatabaseConnection,
|
||||||
|
} from "./localdb";
|
||||||
import {
|
import {
|
||||||
prepareDBs,
|
prepareDBs,
|
||||||
destroyDBs,
|
destroyDBs,
|
||||||
@@ -22,9 +26,15 @@ import {
|
|||||||
insertSyncPlanRecord,
|
insertSyncPlanRecord,
|
||||||
} from "./localdb";
|
} from "./localdb";
|
||||||
|
|
||||||
import type { SyncStatusType } from "./sync";
|
import type { SyncStatusType, PasswordCheckType } from "./sync";
|
||||||
import { getSyncPlan, doActualSync } from "./sync";
|
import { isPasswordOk, getSyncPlan, doActualSync } from "./sync";
|
||||||
import { DEFAULT_S3_CONFIG, getS3Client, listFromRemote, S3Config } from "./s3";
|
import {
|
||||||
|
DEFAULT_S3_CONFIG,
|
||||||
|
getS3Client,
|
||||||
|
listFromRemote,
|
||||||
|
S3Config,
|
||||||
|
checkS3Connectivity,
|
||||||
|
} from "./s3";
|
||||||
import { exportSyncPlansToFiles } from "./debugMode";
|
import { exportSyncPlansToFiles } from "./debugMode";
|
||||||
|
|
||||||
interface SaveRemotePluginSettings {
|
interface SaveRemotePluginSettings {
|
||||||
@@ -71,6 +81,7 @@ export default class SaveRemotePlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
//console.log(`huh ${this.settings.password}`)
|
||||||
new Notice("1/6 Save Remote Sync Preparing");
|
new Notice("1/6 Save Remote Sync Preparing");
|
||||||
this.syncStatus = "preparing";
|
this.syncStatus = "preparing";
|
||||||
|
|
||||||
@@ -87,7 +98,17 @@ export default class SaveRemotePlugin extends Plugin {
|
|||||||
// console.log(local);
|
// console.log(local);
|
||||||
// console.log(localHistory);
|
// console.log(localHistory);
|
||||||
|
|
||||||
new Notice("4/6 Starting to generate sync plan.");
|
new Notice("4/7 Checking password correct or not.");
|
||||||
|
this.syncStatus = "checking_password";
|
||||||
|
const passwordCheckResult = await isPasswordOk(
|
||||||
|
remoteRsp.Contents,
|
||||||
|
this.settings.password
|
||||||
|
);
|
||||||
|
if (!passwordCheckResult.ok) {
|
||||||
|
throw Error(passwordCheckResult.reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
new Notice("5/7 Starting to generate sync plan.");
|
||||||
this.syncStatus = "generating_plan";
|
this.syncStatus = "generating_plan";
|
||||||
const syncPlan = await getSyncPlan(
|
const syncPlan = await getSyncPlan(
|
||||||
remoteRsp.Contents,
|
remoteRsp.Contents,
|
||||||
@@ -102,7 +123,7 @@ export default class SaveRemotePlugin extends Plugin {
|
|||||||
// The operations above are read only and kind of safe.
|
// The operations above are read only and kind of safe.
|
||||||
// The operations below begins to write or delete (!!!) something.
|
// The operations below begins to write or delete (!!!) something.
|
||||||
|
|
||||||
new Notice("5/6 Save Remote Sync data exchanging!");
|
new Notice("6/7 Save Remote Sync data exchanging!");
|
||||||
|
|
||||||
this.syncStatus = "syncing";
|
this.syncStatus = "syncing";
|
||||||
await doActualSync(
|
await doActualSync(
|
||||||
@@ -114,7 +135,7 @@ export default class SaveRemotePlugin extends Plugin {
|
|||||||
this.settings.password
|
this.settings.password
|
||||||
);
|
);
|
||||||
|
|
||||||
new Notice("6/6 Save Remote finish!");
|
new Notice("7/7 Save Remote finish!");
|
||||||
this.syncStatus = "finish";
|
this.syncStatus = "finish";
|
||||||
this.syncStatus = "idle";
|
this.syncStatus = "idle";
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -122,7 +143,7 @@ export default class SaveRemotePlugin extends Plugin {
|
|||||||
console.log(msg);
|
console.log(msg);
|
||||||
console.log(error);
|
console.log(error);
|
||||||
new Notice(msg);
|
new Notice(msg);
|
||||||
new Notice(error);
|
new Notice(error.message);
|
||||||
this.syncStatus = "idle";
|
this.syncStatus = "idle";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -165,6 +186,59 @@ export default class SaveRemotePlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class PasswordModal extends Modal {
|
||||||
|
plugin: SaveRemotePlugin;
|
||||||
|
newPassword: string;
|
||||||
|
constructor(app: App, plugin: SaveRemotePlugin, newPassword: string) {
|
||||||
|
super(app);
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.newPassword = newPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
onOpen() {
|
||||||
|
let { contentEl } = this;
|
||||||
|
// contentEl.setText("Add Or change password.");
|
||||||
|
contentEl.createEl("h2", { text: "Hold on and PLEASE READ ON..." });
|
||||||
|
contentEl.createEl("p", {
|
||||||
|
text: "This password allows you encrypt your files before sending to remote services.",
|
||||||
|
});
|
||||||
|
contentEl.createEl("p", { text: "Empty means no password." });
|
||||||
|
|
||||||
|
contentEl.createEl("p", {
|
||||||
|
text: "Attention 1/3: The password setting itself is stored in PLAIN TEXT LOCALLY (because the plugin needs to use the password to encrypt the files) (and the password would not be sent to remote by this plugin).",
|
||||||
|
});
|
||||||
|
contentEl.createEl("p", {
|
||||||
|
text: "Attention 2/3: If you change the password. You should make sure the remote service (s3/webdav/...) IS EMPTY, or REMOTE FILES WERE ENCRYPTED BY THAT NEW PASSWORD. OTHERWISE SOMETHING BAD WOULD HAPPEN!",
|
||||||
|
});
|
||||||
|
contentEl.createEl("p", {
|
||||||
|
text: "Attention 3/3: The longer the password, the better.",
|
||||||
|
});
|
||||||
|
|
||||||
|
new Setting(contentEl)
|
||||||
|
.addButton((button) => {
|
||||||
|
button.setButtonText("The Second Confirm to change password.");
|
||||||
|
button.onClick(async () => {
|
||||||
|
this.plugin.settings.password = this.newPassword;
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
new Notice("New password saved!");
|
||||||
|
this.close();
|
||||||
|
});
|
||||||
|
button.setClass("password_second_confirm");
|
||||||
|
})
|
||||||
|
.addButton((button) => {
|
||||||
|
button.setButtonText("Cancel (password not changed.)");
|
||||||
|
button.onClick(() => {
|
||||||
|
this.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onClose() {
|
||||||
|
let { contentEl } = this;
|
||||||
|
contentEl.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class SaveRemoteSettingTab extends PluginSettingTab {
|
class SaveRemoteSettingTab extends PluginSettingTab {
|
||||||
plugin: SaveRemotePlugin;
|
plugin: SaveRemotePlugin;
|
||||||
|
|
||||||
@@ -247,22 +321,59 @@ class SaveRemoteSettingTab extends PluginSettingTab {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName("check connectivity")
|
||||||
|
.setDesc("check connectivity")
|
||||||
|
.addButton(async (button) => {
|
||||||
|
button.setButtonText("Check");
|
||||||
|
button.onClick(async () => {
|
||||||
|
new Notice("Checking...");
|
||||||
|
const s3Client = getS3Client(this.plugin.settings.s3);
|
||||||
|
const res = await checkS3Connectivity(
|
||||||
|
s3Client,
|
||||||
|
this.plugin.settings.s3
|
||||||
|
);
|
||||||
|
if (res) {
|
||||||
|
new Notice("Great! The bucket can be accessed.");
|
||||||
|
} else {
|
||||||
|
new Notice("The S3 bucket cannot be reached.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
containerEl.createEl("h2", { text: "General" });
|
containerEl.createEl("h2", { text: "General" });
|
||||||
|
|
||||||
|
let newPassword = `${this.plugin.settings.password}`;
|
||||||
new Setting(containerEl)
|
new Setting(containerEl)
|
||||||
.setName("encryption password")
|
.setName("encryption password")
|
||||||
.setDesc("Password for E2E encryption. Empty for no password.")
|
.setDesc(
|
||||||
|
'Password for E2E encryption. Empty for no password. You need to click "Confirm".'
|
||||||
|
)
|
||||||
.addText((text) =>
|
.addText((text) =>
|
||||||
text
|
text
|
||||||
.setPlaceholder("")
|
.setPlaceholder("")
|
||||||
.setValue(`${this.plugin.settings.password}`)
|
.setValue(`${this.plugin.settings.password}`)
|
||||||
.onChange(async (value) => {
|
.onChange(async (value) => {
|
||||||
this.plugin.settings.password = value.trim();
|
newPassword = value.trim();
|
||||||
await this.plugin.saveSettings();
|
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
|
.addButton(async (button) => {
|
||||||
|
button.setButtonText("Confirm");
|
||||||
|
button.onClick(async () => {
|
||||||
|
new PasswordModal(this.app, this.plugin, newPassword).open();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
containerEl.createEl("h2", { text: "Debug" });
|
containerEl.createEl("h2", { text: "Debug" });
|
||||||
|
|
||||||
|
const syncPlanDiv = containerEl.createEl("div");
|
||||||
|
syncPlanDiv.createEl("p", {
|
||||||
|
text: "Sync plans are created every time after you trigger sync and before the actual sync.",
|
||||||
|
});
|
||||||
|
syncPlanDiv.createEl("p", {
|
||||||
|
text: "They are useful to know what would actually happen in those sync.",
|
||||||
|
});
|
||||||
|
|
||||||
new Setting(containerEl)
|
new Setting(containerEl)
|
||||||
.setName("export sync plans")
|
.setName("export sync plans")
|
||||||
.setDesc("export sync plans")
|
.setDesc("export sync plans")
|
||||||
@@ -284,5 +395,25 @@ class SaveRemoteSettingTab extends PluginSettingTab {
|
|||||||
new Notice("sync plans history (in db) deleted");
|
new Notice("sync plans history (in db) deleted");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const syncMappingDiv = containerEl.createEl("div");
|
||||||
|
syncMappingDiv.createEl("p", {
|
||||||
|
text: "Sync mappings history stores the actual LOCAL last modified time of the REMOTE objects.",
|
||||||
|
});
|
||||||
|
|
||||||
|
syncMappingDiv.createEl("p", {
|
||||||
|
text: "If the sync mappings history are deleted, unnecessary data exchanges may occur in next-time syncing, because whether a remote object and local object with same name are equivalent or not could not be determined correctly by comparing last modified times.",
|
||||||
|
});
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName("delete sync mappings history in db")
|
||||||
|
.setDesc("delete sync mappings history in db")
|
||||||
|
.addButton(async (button) => {
|
||||||
|
button.setButtonText("Delete Sync Mappings");
|
||||||
|
button.onClick(async () => {
|
||||||
|
await clearAllSyncMetaMapping(this.plugin.db);
|
||||||
|
new Notice("sync mappings history (in local db) deleted");
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ import {
|
|||||||
GetObjectCommand,
|
GetObjectCommand,
|
||||||
DeleteObjectCommand,
|
DeleteObjectCommand,
|
||||||
HeadObjectCommand,
|
HeadObjectCommand,
|
||||||
|
HeadBucketCommand,
|
||||||
|
ListObjectsV2CommandInput,
|
||||||
|
ListObjectsV2CommandOutput,
|
||||||
} from "@aws-sdk/client-s3";
|
} from "@aws-sdk/client-s3";
|
||||||
|
|
||||||
import type { _Object } from "@aws-sdk/client-s3";
|
import type { _Object } from "@aws-sdk/client-s3";
|
||||||
@@ -129,17 +132,42 @@ export const listFromRemote = async (
|
|||||||
s3Config: S3Config,
|
s3Config: S3Config,
|
||||||
prefix?: string
|
prefix?: string
|
||||||
) => {
|
) => {
|
||||||
if (prefix !== undefined) {
|
const confCmd = {
|
||||||
return await s3Client.send(
|
|
||||||
new ListObjectsV2Command({
|
|
||||||
Bucket: s3Config.s3BucketName,
|
Bucket: s3Config.s3BucketName,
|
||||||
Prefix: prefix,
|
} as ListObjectsV2CommandInput;
|
||||||
})
|
if (prefix !== undefined) {
|
||||||
);
|
confCmd.Prefix = prefix;
|
||||||
}
|
}
|
||||||
return await s3Client.send(
|
|
||||||
new ListObjectsV2Command({ Bucket: s3Config.s3BucketName })
|
const contents = [] as _Object[];
|
||||||
);
|
|
||||||
|
let isTruncated = true;
|
||||||
|
let continuationToken = "";
|
||||||
|
do {
|
||||||
|
const rsp = await s3Client.send(new ListObjectsV2Command(confCmd));
|
||||||
|
|
||||||
|
if (rsp.$metadata.httpStatusCode !== 200) {
|
||||||
|
throw Error("some thing bad while listing remote!");
|
||||||
|
}
|
||||||
|
contents.push(...rsp.Contents);
|
||||||
|
|
||||||
|
isTruncated = rsp.IsTruncated;
|
||||||
|
confCmd.ContinuationToken = rsp.NextContinuationToken;
|
||||||
|
if (
|
||||||
|
isTruncated &&
|
||||||
|
(continuationToken === undefined || continuationToken === "")
|
||||||
|
) {
|
||||||
|
throw Error("isTruncated is true but no continuationToken provided");
|
||||||
|
}
|
||||||
|
} while (isTruncated);
|
||||||
|
|
||||||
|
// ensemble fake rsp
|
||||||
|
return {
|
||||||
|
"$.metadata": {
|
||||||
|
httpStatusCode: 200,
|
||||||
|
},
|
||||||
|
Contents: contents,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -266,3 +294,31 @@ export const deleteFromRemote = async (
|
|||||||
// pass
|
// pass
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the config of S3 by heading bucket
|
||||||
|
* https://stackoverflow.com/questions/50842835
|
||||||
|
* @param s3Client
|
||||||
|
* @param s3Config
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const checkS3Connectivity = async (
|
||||||
|
s3Client: S3Client,
|
||||||
|
s3Config: S3Config
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const results = await s3Client.send(
|
||||||
|
new HeadBucketCommand({ Bucket: s3Config.s3BucketName })
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
results === undefined ||
|
||||||
|
results.$metadata === undefined ||
|
||||||
|
results.$metadata.httpStatusCode === undefined
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return results.$metadata.httpStatusCode === 200;
|
||||||
|
} catch (err) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
+66
-1
@@ -17,13 +17,18 @@ import {
|
|||||||
downloadFromRemote,
|
downloadFromRemote,
|
||||||
} from "./s3";
|
} from "./s3";
|
||||||
import { mkdirpInVault, SUPPORTED_SERVICES_TYPE, isHiddenPath } from "./misc";
|
import { mkdirpInVault, SUPPORTED_SERVICES_TYPE, isHiddenPath } from "./misc";
|
||||||
import { decryptBase32ToString, encryptStringToBase32 } from "./encrypt";
|
import {
|
||||||
|
decryptBase32ToString,
|
||||||
|
encryptStringToBase32,
|
||||||
|
MAGIC_ENCRYPTED_PREFIX_BASE32,
|
||||||
|
} from "./encrypt";
|
||||||
|
|
||||||
export type SyncStatusType =
|
export type SyncStatusType =
|
||||||
| "idle"
|
| "idle"
|
||||||
| "preparing"
|
| "preparing"
|
||||||
| "getting_remote_meta"
|
| "getting_remote_meta"
|
||||||
| "getting_local_meta"
|
| "getting_local_meta"
|
||||||
|
| "checking_password"
|
||||||
| "generating_plan"
|
| "generating_plan"
|
||||||
| "syncing"
|
| "syncing"
|
||||||
| "finish";
|
| "finish";
|
||||||
@@ -61,6 +66,66 @@ export interface SyncPlanType {
|
|||||||
mixedStates: Record<string, FileOrFolderMixedState>;
|
mixedStates: Record<string, FileOrFolderMixedState>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PasswordCheckType {
|
||||||
|
ok: boolean;
|
||||||
|
reason:
|
||||||
|
| "ok"
|
||||||
|
| "empty_remote"
|
||||||
|
| "remote_encrypted_local_no_password"
|
||||||
|
| "password_matched"
|
||||||
|
| "password_not_matched"
|
||||||
|
| "remote_not_encrypted_local_has_password"
|
||||||
|
| "no_password_both_sides";
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isPasswordOk = async (
|
||||||
|
remote: S3ObjectType[],
|
||||||
|
password: string = ""
|
||||||
|
) => {
|
||||||
|
if (remote === undefined || remote.length === 0) {
|
||||||
|
// remote empty
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
reason: "empty_remote",
|
||||||
|
} as PasswordCheckType;
|
||||||
|
}
|
||||||
|
const santyCheckKey = remote[0].Key;
|
||||||
|
if (santyCheckKey.startsWith(MAGIC_ENCRYPTED_PREFIX_BASE32)) {
|
||||||
|
// this is encrypted!
|
||||||
|
// try to decrypt it using the provided password.
|
||||||
|
if (password === "") {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
reason: "remote_encrypted_local_no_password",
|
||||||
|
} as PasswordCheckType;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await decryptBase32ToString(santyCheckKey, password);
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
reason: "password_matched",
|
||||||
|
} as PasswordCheckType;
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
reason: "password_not_matched",
|
||||||
|
} as PasswordCheckType;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// it is not encrypted!
|
||||||
|
if (password !== "") {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
reason: "remote_not_encrypted_local_has_password",
|
||||||
|
} as PasswordCheckType;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
reason: "no_password_both_sides",
|
||||||
|
} as PasswordCheckType;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const ensembleMixedStates = async (
|
const ensembleMixedStates = async (
|
||||||
remote: S3ObjectType[],
|
remote: S3ObjectType[],
|
||||||
local: TAbstractFile[],
|
local: TAbstractFile[],
|
||||||
|
|||||||
@@ -1 +1,5 @@
|
|||||||
/* set the styles */
|
/* set the styles */
|
||||||
|
|
||||||
|
.password_second_confirm {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"0.0.10": "0.12.15"
|
"0.0.11": "0.12.15"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user