Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb6f7e572c | ||
|
|
83e0073134 | ||
|
|
cfe316f690 | ||
|
|
c472654060 | ||
|
|
46cbfcc3aa | ||
|
|
ce94a6d79c | ||
|
|
0fc0dcad64 |
@@ -0,0 +1,56 @@
|
|||||||
|
# How to receive `obsidian://` in Linux
|
||||||
|
|
||||||
|
## Background
|
||||||
|
|
||||||
|
For example, when we are authorizing OneDrive, we have to jump back to Obsidian automatically using `obsidian://`.
|
||||||
|
|
||||||
|
## Short Desc From Official Obsidian Doc
|
||||||
|
|
||||||
|
Official doc has some explanation:
|
||||||
|
|
||||||
|
<https://help.obsidian.md/Extending+Obsidian/Obsidian+URI#Register+Obsidian+URI>
|
||||||
|
|
||||||
|
# Long Desc
|
||||||
|
|
||||||
|
Assuming the username is `somebody`, and the `.AppImage` file is downloaded to `~/Desktop`.
|
||||||
|
|
||||||
|
1. Download and **extract** the app image file in terminal
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/somebody/Desktop
|
||||||
|
chmod +x Obsidian-x.y.z.AppImage
|
||||||
|
./Obsidian-x.y.z.AppImage --appimage-extract
|
||||||
|
|
||||||
|
# you should have the folder squashfs-root
|
||||||
|
# we want to rename it
|
||||||
|
mv squashfs-root Obsidian
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Create a `.desktop` file
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# copy and paste the follow MULTI LINE command
|
||||||
|
# you might need to input your password because it requires root privilege
|
||||||
|
# remember to adjust the path
|
||||||
|
cat > ~/Desktop/obsidian.desktop <<EOF
|
||||||
|
[Desktop Entry]
|
||||||
|
Name=Obsidian
|
||||||
|
Comment=obsidian
|
||||||
|
Exec=/home/somebody/Desktop/Obsidian/obsidian %u
|
||||||
|
Keywords=obsidian
|
||||||
|
StartupNotify=true
|
||||||
|
Terminal=false
|
||||||
|
Type=Application
|
||||||
|
Icon=/home/somebody/Desktop/Obsidian/obsidian.png
|
||||||
|
MimeType=x-scheme-handler/obsidian;
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# yeah we can check out the output
|
||||||
|
cat ~/Desktop/obsidian.desktop
|
||||||
|
## [Desktop Entry]
|
||||||
|
## ...
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Right click the `obsidian.desktop` file on the Desktop, and click "Allow launching"
|
||||||
|
|
||||||
|
4. Double click the `obsidian.desktop` file.
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "remotely-save",
|
"id": "remotely-save",
|
||||||
"name": "Remotely Save",
|
"name": "Remotely Save",
|
||||||
"version": "0.4.14",
|
"version": "0.4.15",
|
||||||
"minAppVersion": "0.13.21",
|
"minAppVersion": "0.13.21",
|
||||||
"description": "Yet another unofficial plugin allowing users to synchronize notes between local device and the cloud service.",
|
"description": "Yet another unofficial plugin allowing users to synchronize notes between local device and the cloud service.",
|
||||||
"author": "fyears",
|
"author": "fyears",
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "remotely-save",
|
"id": "remotely-save",
|
||||||
"name": "Remotely Save",
|
"name": "Remotely Save",
|
||||||
"version": "0.4.14",
|
"version": "0.4.15",
|
||||||
"minAppVersion": "0.13.21",
|
"minAppVersion": "0.13.21",
|
||||||
"description": "Yet another unofficial plugin allowing users to synchronize notes between local device and the cloud service.",
|
"description": "Yet another unofficial plugin allowing users to synchronize notes between local device and the cloud service.",
|
||||||
"author": "fyears",
|
"author": "fyears",
|
||||||
|
|||||||
+1
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "remotely-save",
|
"name": "remotely-save",
|
||||||
"version": "0.4.14",
|
"version": "0.4.15",
|
||||||
"description": "This is yet another sync plugin for Obsidian app.",
|
"description": "This is yet another sync plugin for Obsidian app.",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev2": "node esbuild.config.mjs --watch",
|
"dev2": "node esbuild.config.mjs --watch",
|
||||||
@@ -72,7 +72,6 @@
|
|||||||
"aws-crt": "^1.20.0",
|
"aws-crt": "^1.20.0",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"crypto-browserify": "^3.12.0",
|
"crypto-browserify": "^3.12.0",
|
||||||
"delay": "^6.0.0",
|
|
||||||
"dropbox": "^10.34.0",
|
"dropbox": "^10.34.0",
|
||||||
"emoji-regex": "^10.3.0",
|
"emoji-regex": "^10.3.0",
|
||||||
"http-status-codes": "^2.3.0",
|
"http-status-codes": "^2.3.0",
|
||||||
|
|||||||
@@ -90,6 +90,8 @@ export type SyncDirectionType =
|
|||||||
|
|
||||||
export type CipherMethodType = "rclone-base64" | "openssl-base64" | "unknown";
|
export type CipherMethodType = "rclone-base64" | "openssl-base64" | "unknown";
|
||||||
|
|
||||||
|
export type QRExportType = "all_but_oauth2" | "dropbox" | "onedrive";
|
||||||
|
|
||||||
export interface RemotelySavePluginSettings {
|
export interface RemotelySavePluginSettings {
|
||||||
s3: S3Config;
|
s3: S3Config;
|
||||||
webdav: WebdavConfig;
|
webdav: WebdavConfig;
|
||||||
@@ -269,6 +271,8 @@ export const DEFAULT_DEBUG_FOLDER = "_debug_remotely_save/";
|
|||||||
export const DEFAULT_SYNC_PLANS_HISTORY_FILE_PREFIX =
|
export const DEFAULT_SYNC_PLANS_HISTORY_FILE_PREFIX =
|
||||||
"sync_plans_hist_exported_on_";
|
"sync_plans_hist_exported_on_";
|
||||||
export const DEFAULT_LOG_HISTORY_FILE_PREFIX = "log_hist_exported_on_";
|
export const DEFAULT_LOG_HISTORY_FILE_PREFIX = "log_hist_exported_on_";
|
||||||
|
export const DEFAULT_PROFILER_RESULT_FILE_PREFIX =
|
||||||
|
"profiler_results_exported_on_";
|
||||||
|
|
||||||
export type SyncTriggerSourceType =
|
export type SyncTriggerSourceType =
|
||||||
| "manual"
|
| "manual"
|
||||||
|
|||||||
+32
-66
@@ -1,82 +1,24 @@
|
|||||||
import { TAbstractFile, TFolder, TFile, Vault } from "obsidian";
|
import { TAbstractFile, TFolder, TFile, Vault } from "obsidian";
|
||||||
|
|
||||||
import type { SyncPlanType } from "./sync";
|
import {
|
||||||
import { readAllSyncPlanRecordTextsByVault } from "./localdb";
|
readAllProfilerResultsByVault,
|
||||||
|
readAllSyncPlanRecordTextsByVault,
|
||||||
|
} from "./localdb";
|
||||||
import type { InternalDBs } from "./localdb";
|
import type { InternalDBs } from "./localdb";
|
||||||
import { mkdirpInVault } from "./misc";
|
import { mkdirpInVault, unixTimeToStr } from "./misc";
|
||||||
import {
|
import {
|
||||||
DEFAULT_DEBUG_FOLDER,
|
DEFAULT_DEBUG_FOLDER,
|
||||||
DEFAULT_LOG_HISTORY_FILE_PREFIX,
|
DEFAULT_PROFILER_RESULT_FILE_PREFIX,
|
||||||
DEFAULT_SYNC_PLANS_HISTORY_FILE_PREFIX,
|
DEFAULT_SYNC_PLANS_HISTORY_FILE_PREFIX,
|
||||||
FileOrFolderMixedState,
|
|
||||||
} from "./baseTypes";
|
} from "./baseTypes";
|
||||||
|
|
||||||
const turnSyncPlanToTable = (record: string) => {
|
|
||||||
const syncPlan: SyncPlanType = JSON.parse(record);
|
|
||||||
const { ts, tsFmt, remoteType, mixedStates } = syncPlan;
|
|
||||||
|
|
||||||
type allowedHeadersType = keyof FileOrFolderMixedState;
|
|
||||||
const headers: allowedHeadersType[] = [
|
|
||||||
"key",
|
|
||||||
"remoteEncryptedKey",
|
|
||||||
"existLocal",
|
|
||||||
"sizeLocal",
|
|
||||||
"sizeLocalEnc",
|
|
||||||
"mtimeLocal",
|
|
||||||
"deltimeLocal",
|
|
||||||
"changeLocalMtimeUsingMapping",
|
|
||||||
"existRemote",
|
|
||||||
"sizeRemote",
|
|
||||||
"sizeRemoteEnc",
|
|
||||||
"mtimeRemote",
|
|
||||||
"deltimeRemote",
|
|
||||||
"changeRemoteMtimeUsingMapping",
|
|
||||||
"decision",
|
|
||||||
"decisionBranch",
|
|
||||||
];
|
|
||||||
|
|
||||||
const lines = [
|
|
||||||
`ts: ${ts}${tsFmt !== undefined ? " / " + tsFmt : ""}`,
|
|
||||||
`remoteType: ${remoteType}`,
|
|
||||||
`| ${headers.join(" | ")} |`,
|
|
||||||
`| ${headers.map((x) => "---").join(" | ")} |`,
|
|
||||||
];
|
|
||||||
for (const [k1, v1] of Object.entries(syncPlan.mixedStates)) {
|
|
||||||
const k = k1 as string;
|
|
||||||
const v = v1 as FileOrFolderMixedState;
|
|
||||||
const singleLine = [];
|
|
||||||
for (const h of headers) {
|
|
||||||
const field = v[h];
|
|
||||||
if (field === undefined) {
|
|
||||||
singleLine.push("");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
h === "mtimeLocal" ||
|
|
||||||
h === "deltimeLocal" ||
|
|
||||||
h === "mtimeRemote" ||
|
|
||||||
h === "deltimeRemote"
|
|
||||||
) {
|
|
||||||
const fmt = v[(h + "Fmt") as allowedHeadersType] as string;
|
|
||||||
const s = `${field}${fmt !== undefined ? " / " + fmt : ""}`;
|
|
||||||
singleLine.push(s);
|
|
||||||
} else {
|
|
||||||
singleLine.push(field);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lines.push(`| ${singleLine.join(" | ")} |`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return lines.join("\n");
|
|
||||||
};
|
|
||||||
|
|
||||||
export const exportVaultSyncPlansToFiles = async (
|
export const exportVaultSyncPlansToFiles = async (
|
||||||
db: InternalDBs,
|
db: InternalDBs,
|
||||||
vault: Vault,
|
vault: Vault,
|
||||||
vaultRandomID: string,
|
vaultRandomID: string,
|
||||||
howMany: number
|
howMany: number
|
||||||
) => {
|
) => {
|
||||||
console.info("exporting");
|
console.info("exporting sync plans");
|
||||||
await mkdirpInVault(DEFAULT_DEBUG_FOLDER, vault);
|
await mkdirpInVault(DEFAULT_DEBUG_FOLDER, vault);
|
||||||
const records = await readAllSyncPlanRecordTextsByVault(db, vaultRandomID);
|
const records = await readAllSyncPlanRecordTextsByVault(db, vaultRandomID);
|
||||||
let md = "";
|
let md = "";
|
||||||
@@ -101,5 +43,29 @@ export const exportVaultSyncPlansToFiles = async (
|
|||||||
await vault.create(filePath, md, {
|
await vault.create(filePath, md, {
|
||||||
mtime: ts,
|
mtime: ts,
|
||||||
});
|
});
|
||||||
console.info("finish exporting");
|
console.info("finish exporting sync plans");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const exportVaultProfilerResultsToFiles = async (
|
||||||
|
db: InternalDBs,
|
||||||
|
vault: Vault,
|
||||||
|
vaultRandomID: string
|
||||||
|
) => {
|
||||||
|
console.info("exporting profiler results");
|
||||||
|
await mkdirpInVault(DEFAULT_DEBUG_FOLDER, vault);
|
||||||
|
const records = await readAllProfilerResultsByVault(db, vaultRandomID);
|
||||||
|
let md = "";
|
||||||
|
if (records.length === 0) {
|
||||||
|
md = "No profiler results found";
|
||||||
|
} else {
|
||||||
|
md =
|
||||||
|
"Profiler results found:\n\n" +
|
||||||
|
records.map((x) => "```\n" + x + "\n```\n").join("\n");
|
||||||
|
}
|
||||||
|
const ts = Date.now();
|
||||||
|
const filePath = `${DEFAULT_DEBUG_FOLDER}${DEFAULT_PROFILER_RESULT_FILE_PREFIX}${ts}.md`;
|
||||||
|
await vault.create(filePath, md, {
|
||||||
|
mtime: ts,
|
||||||
|
});
|
||||||
|
console.info("finish exporting profiler results");
|
||||||
};
|
};
|
||||||
|
|||||||
+30
-4
@@ -5,16 +5,28 @@ import {
|
|||||||
COMMAND_URI,
|
COMMAND_URI,
|
||||||
UriParams,
|
UriParams,
|
||||||
RemotelySavePluginSettings,
|
RemotelySavePluginSettings,
|
||||||
|
QRExportType,
|
||||||
} from "./baseTypes";
|
} from "./baseTypes";
|
||||||
|
import { getShrinkedSettings } from "./remoteForOnedrive";
|
||||||
|
|
||||||
export const exportQrCodeUri = async (
|
export const exportQrCodeUri = async (
|
||||||
settings: RemotelySavePluginSettings,
|
settings: RemotelySavePluginSettings,
|
||||||
currentVaultName: string,
|
currentVaultName: string,
|
||||||
pluginVersion: string
|
pluginVersion: string,
|
||||||
|
exportFields: QRExportType
|
||||||
) => {
|
) => {
|
||||||
const settings2: Partial<RemotelySavePluginSettings> = cloneDeep(settings);
|
let settings2: Partial<RemotelySavePluginSettings> = {};
|
||||||
delete settings2.dropbox;
|
|
||||||
delete settings2.onedrive;
|
if (exportFields === "all_but_oauth2") {
|
||||||
|
settings2 = cloneDeep(settings);
|
||||||
|
delete settings2.dropbox;
|
||||||
|
delete settings2.onedrive;
|
||||||
|
} else if (exportFields === "dropbox") {
|
||||||
|
settings2 = { dropbox: cloneDeep(settings.dropbox) };
|
||||||
|
} else if (exportFields === "onedrive") {
|
||||||
|
settings2 = { onedrive: getShrinkedSettings(settings.onedrive) };
|
||||||
|
}
|
||||||
|
|
||||||
delete settings2.vaultRandomID;
|
delete settings2.vaultRandomID;
|
||||||
const data = encodeURIComponent(JSON.stringify(settings2));
|
const data = encodeURIComponent(JSON.stringify(settings2));
|
||||||
const vault = encodeURIComponent(currentVaultName);
|
const vault = encodeURIComponent(currentVaultName);
|
||||||
@@ -34,6 +46,20 @@ export interface ProcessQrCodeResultType {
|
|||||||
result?: RemotelySavePluginSettings;
|
result?: RemotelySavePluginSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* we also support directly parse the uri, instead of relying on web browser
|
||||||
|
* @param input
|
||||||
|
*/
|
||||||
|
export const parseUriByHand = (input: string) => {
|
||||||
|
if (!input.startsWith("obsidian://remotely-save?func=settings&")) {
|
||||||
|
throw Error(`not valid string`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const k = new URL(input);
|
||||||
|
const output = Object.fromEntries(k.searchParams);
|
||||||
|
return output;
|
||||||
|
};
|
||||||
|
|
||||||
export const importQrCodeUri = (
|
export const importQrCodeUri = (
|
||||||
inputParams: any,
|
inputParams: any,
|
||||||
currentVaultName: string
|
currentVaultName: string
|
||||||
|
|||||||
+14
-5
@@ -24,7 +24,7 @@
|
|||||||
"syncrun_shortstep2": "2/2 Remotely Save finished!",
|
"syncrun_shortstep2": "2/2 Remotely Save finished!",
|
||||||
"syncrun_abort": "{{manifestID}}-{{theDate}}: abort sync, triggerSource={{triggerSource}}, error while {{syncStatus}}",
|
"syncrun_abort": "{{manifestID}}-{{theDate}}: abort sync, triggerSource={{triggerSource}}, error while {{syncStatus}}",
|
||||||
"syncrun_abort_protectmodifypercentage": "Abort! you set changing files >= {{protectModifyPercentage}}% is not allowed but {{realModifyDeleteCount}}/{{allFilesCount}}={{percent}}% is going to be modified or deleted! If you are sure you want this sync, please adjust the allowed ratio in the settings.",
|
"syncrun_abort_protectmodifypercentage": "Abort! you set changing files >= {{protectModifyPercentage}}% is not allowed but {{realModifyDeleteCount}}/{{allFilesCount}}={{percent}}% is going to be modified or deleted! If you are sure you want this sync, please adjust the allowed ratio in the settings.",
|
||||||
"protocol_saveqr": "New not-oauth2 settings for {{manifestName}} is saved. Reopen the plugin settings to make it effective.",
|
"protocol_saveqr": "New settings for {{manifestName}} is imported and saved. Reopen the plugin settings to make it effective.",
|
||||||
"protocol_callbacknotsupported": "Your uri calls a callback that's not supported yet: {{params}}",
|
"protocol_callbacknotsupported": "Your uri calls a callback that's not supported yet: {{params}}",
|
||||||
"protocol_dropbox_connecting": "Connecting to Dropbox...\nPlease DO NOT close this modal.",
|
"protocol_dropbox_connecting": "Connecting to Dropbox...\nPlease DO NOT close this modal.",
|
||||||
"protocol_dropbox_connect_succ": "Good! We've connected to Dropbox as user {{username}}!",
|
"protocol_dropbox_connect_succ": "Good! We've connected to Dropbox as user {{username}}!",
|
||||||
@@ -93,6 +93,7 @@
|
|||||||
"modal_dropboxauth_maualinput_conn_succ_revoke": "You've connected as user {{username}}. If you want to disconnect, click this button.",
|
"modal_dropboxauth_maualinput_conn_succ_revoke": "You've connected as user {{username}}. If you want to disconnect, click this button.",
|
||||||
"modal_dropboxauth_maualinput_conn_fail": "Something goes wrong while connecting to Dropbox.",
|
"modal_dropboxauth_maualinput_conn_fail": "Something goes wrong while connecting to Dropbox.",
|
||||||
"modal_onedriveauth_shortdesc": "Currently only OneDrive for personal is supported. OneDrive for Business is NOT supported (yet).\nVisit the address in a browser, and follow the steps.\nFinally you should be redirected to Obsidian.",
|
"modal_onedriveauth_shortdesc": "Currently only OneDrive for personal is supported. OneDrive for Business is NOT supported (yet).\nVisit the address in a browser, and follow the steps.\nFinally you should be redirected to Obsidian.",
|
||||||
|
"modal_onedriveauth_shortdesc_linux": "It seems that you are using Obsidian on Linux, and you might not be able to jump back here properly. Please consider <a href=\"https://github.com/remotely-save/remotely-save/issues/415\">using</a> the flatpack version of Obsidian, or creating an <a href=\"https://github.com/remotely-save/remotely-save/blob/master/docs/linux.md\"><code>obsidian.desktop</code> file</a>.",
|
||||||
"modal_onedriveauth_copybutton": "Click to copy the auth url",
|
"modal_onedriveauth_copybutton": "Click to copy the auth url",
|
||||||
"modal_onedriveauth_copynotice": "The auth url is copied to the clipboard!",
|
"modal_onedriveauth_copynotice": "The auth url is copied to the clipboard!",
|
||||||
"modal_onedriverevokeauth_step1": "Step 1: Go to the following address, click the \"Edit\" button for the plugin, then click \"Remove these permissions\" button on the page.",
|
"modal_onedriverevokeauth_step1": "Step 1: Go to the following address, click the \"Edit\" button for the plugin, then click \"Remove these permissions\" button on the page.",
|
||||||
@@ -105,7 +106,7 @@
|
|||||||
"modal_syncconfig_attn": "Attention 1/2: This only syncs (copies) the whole Obsidian config dir, not other startting-with-dot folders or files. Except for ignoring folders .git and node_modules, it also doesn't understand the meaning of sub-files and sub-folders inside the config dir.\nAttention 2/2: After the config dir is synced, plugins settings might be corrupted, and Obsidian might need to be restarted to load the new settings.\nIf you are agreed to take your own risk, please click the following second confirm button.",
|
"modal_syncconfig_attn": "Attention 1/2: This only syncs (copies) the whole Obsidian config dir, not other startting-with-dot folders or files. Except for ignoring folders .git and node_modules, it also doesn't understand the meaning of sub-files and sub-folders inside the config dir.\nAttention 2/2: After the config dir is synced, plugins settings might be corrupted, and Obsidian might need to be restarted to load the new settings.\nIf you are agreed to take your own risk, please click the following second confirm button.",
|
||||||
"modal_syncconfig_secondconfirm": "The Second Confirm To Enable.",
|
"modal_syncconfig_secondconfirm": "The Second Confirm To Enable.",
|
||||||
"modal_syncconfig_notice": "You've enabled syncing config folder!",
|
"modal_syncconfig_notice": "You've enabled syncing config folder!",
|
||||||
"modal_qr_shortdesc": "This exports not-oauth2 settings. (It means that Dropbox, OneDrive info are NOT exported.)\nYou can use another device to scan this qrcode.\nOr, you can click the button to copy the special url.",
|
"modal_qr_shortdesc": "This exports (partial) settings.\nYou can use another device to scan this qrcode.\nOr, you can click the button to copy the special uri and paste it into another device's web browser or Remotely Save Import Setting.",
|
||||||
"modal_qr_button": "Click to copy the special URI",
|
"modal_qr_button": "Click to copy the special URI",
|
||||||
"modal_qr_button_notice": "The special uri is copied to the clipboard!",
|
"modal_qr_button_notice": "The special uri is copied to the clipboard!",
|
||||||
"modal_sizesconflict_title": "Remotely Save: Some conflict were found while skipping large files",
|
"modal_sizesconflict_title": "Remotely Save: Some conflict were found while skipping large files",
|
||||||
@@ -278,10 +279,14 @@
|
|||||||
"settings_enablemobilestatusbar_desc": "By default Obsidian mobile hides status bar. But some users want to show it up. So here is a hack.",
|
"settings_enablemobilestatusbar_desc": "By default Obsidian mobile hides status bar. But some users want to show it up. So here is a hack.",
|
||||||
"settings_importexport": "Import and Export Partial Settings",
|
"settings_importexport": "Import and Export Partial Settings",
|
||||||
"settings_export": "Export",
|
"settings_export": "Export",
|
||||||
"settings_export_desc": "Export not-oauth2 settings by generating a qrcode.",
|
"settings_export_desc": "Export settings by generating a QR code or URI.",
|
||||||
"settings_export_desc_button": "Get QR Code",
|
"settings_export_all_but_oauth2_button": "Export Non-Oauth2 Part",
|
||||||
|
"settings_export_dropbox_button": "Export Dropbox Part",
|
||||||
|
"settings_export_onedrive_button": "Export OneDrive Part",
|
||||||
"settings_import": "Import",
|
"settings_import": "Import",
|
||||||
"settings_import_desc": "You should open a camera or scan-qrcode app, to manually scan the QR code.",
|
"settings_import_desc": "Paste the exported URI into here and click \"Import\". Or, you can open a camera or scan-qrcode app to scan the QR code.",
|
||||||
|
"settings_import_button": "Import",
|
||||||
|
"settings_import_error_notice": "Your URI string is empty or not correct!",
|
||||||
"settings_debug": "Debug",
|
"settings_debug": "Debug",
|
||||||
"settings_debuglevel": "Alter Notice Level",
|
"settings_debuglevel": "Alter Notice Level",
|
||||||
"settings_debuglevel_desc": "By default the notice level is \"info\". You can change to \"debug\" to get verbose information while syncing.",
|
"settings_debuglevel_desc": "By default the notice level is \"info\". You can change to \"debug\" to get verbose information while syncing.",
|
||||||
@@ -307,6 +312,10 @@
|
|||||||
"settings_delprevsync_desc": "The sync algorithm keeps the previous successful sync information in DB to determine the file changes. If you want to ignore them so that all files are treated newly created, you can delete the prev sync info here.",
|
"settings_delprevsync_desc": "The sync algorithm keeps the previous successful sync information in DB to determine the file changes. If you want to ignore them so that all files are treated newly created, you can delete the prev sync info here.",
|
||||||
"settings_delprevsync_button": "Delete Prev Sync Details",
|
"settings_delprevsync_button": "Delete Prev Sync Details",
|
||||||
"settings_delprevsync_notice": "Previous sync history (in local DB) deleted",
|
"settings_delprevsync_notice": "Previous sync history (in local DB) deleted",
|
||||||
|
"settings_profiler_results": "Export Profiler Results",
|
||||||
|
"settings_profiler_results_desc": "The plugin records the time cost of each steps. Here you can export them to know which step is slow.",
|
||||||
|
"settings_profiler_results_notice": "Profiler results exported.",
|
||||||
|
"settings_profiler_results_button_all": "Export All",
|
||||||
"settings_outputbasepathvaultid": "Output Vault Base Path And Randomly Assigned ID",
|
"settings_outputbasepathvaultid": "Output Vault Base Path And Randomly Assigned ID",
|
||||||
"settings_outputbasepathvaultid_desc": "For debugging purposes.",
|
"settings_outputbasepathvaultid_desc": "For debugging purposes.",
|
||||||
"settings_outputbasepathvaultid_button": "Output",
|
"settings_outputbasepathvaultid_button": "Output",
|
||||||
|
|||||||
+14
-5
@@ -24,7 +24,7 @@
|
|||||||
"syncrun_shortstep2": "2/2 Remotely Save 已完成同步!",
|
"syncrun_shortstep2": "2/2 Remotely Save 已完成同步!",
|
||||||
"syncrun_abort": "{{manifestID}}-{{theDate}}:中断同步,同步来源={{triggerSource}},出错阶段={{syncStatus}}",
|
"syncrun_abort": "{{manifestID}}-{{theDate}}:中断同步,同步来源={{triggerSource}},出错阶段={{syncStatus}}",
|
||||||
"syncrun_abort_protectmodifypercentage": "中断同步!您设置了不允许 >= {{protectModifyPercentage}}% 的变更,但是现在 {{realModifyDeleteCount}}/{{allFilesCount}}={{percent}}% 的文件会被修改或删除!如果您确认这次同步是您想要的,那么请在设置里修改允许比例。",
|
"syncrun_abort_protectmodifypercentage": "中断同步!您设置了不允许 >= {{protectModifyPercentage}}% 的变更,但是现在 {{realModifyDeleteCount}}/{{allFilesCount}}={{percent}}% 的文件会被修改或删除!如果您确认这次同步是您想要的,那么请在设置里修改允许比例。",
|
||||||
"protocol_saveqr": " {{manifestName}} 新的非 oauth2 设置保存完成。请重启插件设置页使之生效。",
|
"protocol_saveqr": " {{manifestName}} 的新设置导入完成。请重启插件设置页使之生效。",
|
||||||
"protocol_callbacknotsupported": "您的 uri callback 暂不支持: {{params}}",
|
"protocol_callbacknotsupported": "您的 uri callback 暂不支持: {{params}}",
|
||||||
"protocol_dropbox_connecting": "正在连接 Dropbox……\n请不要关闭此弹窗。",
|
"protocol_dropbox_connecting": "正在连接 Dropbox……\n请不要关闭此弹窗。",
|
||||||
"protocol_dropbox_connect_succ": "好!我们作为用户 {{username}} 连接上了 Dropbox!",
|
"protocol_dropbox_connect_succ": "好!我们作为用户 {{username}} 连接上了 Dropbox!",
|
||||||
@@ -93,6 +93,7 @@
|
|||||||
"modal_dropboxauth_maualinput_conn_succ_revoke": "您已作为用户 {{username}} 连接到 Dropbox。如果您想断开连接,点击此按钮。",
|
"modal_dropboxauth_maualinput_conn_succ_revoke": "您已作为用户 {{username}} 连接到 Dropbox。如果您想断开连接,点击此按钮。",
|
||||||
"modal_dropboxauth_maualinput_conn_fail": "连接 Dropbox 途中出错了。",
|
"modal_dropboxauth_maualinput_conn_fail": "连接 Dropbox 途中出错了。",
|
||||||
"modal_onedriveauth_shortdesc": "现在只支持个人版 OneDrive,(暂)不支持企业版。\n在浏览器中访问以下地址,然后按照网页提示操作。\n到了最后,您应该会被自动重定向回来 Obsidian。",
|
"modal_onedriveauth_shortdesc": "现在只支持个人版 OneDrive,(暂)不支持企业版。\n在浏览器中访问以下地址,然后按照网页提示操作。\n到了最后,您应该会被自动重定向回来 Obsidian。",
|
||||||
|
"modal_onedriveauth_shortdesc_linux": "您正在用 Linux,有可能无法跳转回来。请考虑<a href=\"https://github.com/remotely-save/remotely-save/issues/415\">使用</a> flatpack 版本的 Obsidian,或创建 <a href=\"https://github.com/remotely-save/remotely-save/blob/master/docs/linux.md\"><code>obsidian.desktop</code> 文件</a>。",
|
||||||
"modal_onedriveauth_copybutton": "点击此按钮从而复制鉴权 url",
|
"modal_onedriveauth_copybutton": "点击此按钮从而复制鉴权 url",
|
||||||
"modal_onedriveauth_copynotice": "鉴权 url 已复制到剪贴板!",
|
"modal_onedriveauth_copynotice": "鉴权 url 已复制到剪贴板!",
|
||||||
"modal_onedriverevokeauth_step1": "第 1 步:用浏览器打开以下地址,点击本插件对应的“Edit”按钮,点击“Remove these permissions”按钮。",
|
"modal_onedriverevokeauth_step1": "第 1 步:用浏览器打开以下地址,点击本插件对应的“Edit”按钮,点击“Remove these permissions”按钮。",
|
||||||
@@ -105,7 +106,7 @@
|
|||||||
"modal_syncconfig_attn": "注意 1/2:此设置只同步(复制)整个 Obsidian 的配置文件夹,但是不会同步其它 . 开头的文件夹或文件。除了会忽略 .git 和 node_modules 文件夹之外,它也并不理解配置文件夹的里各个子文件或子文件夹的含义。\n注意 2/2:配置文件夹被同步之后,各插件的设置或许会出错,且 Obsidian 或许需要重启来重载各插件的新配置。\n如果您同意自行承受以上风险,您可以点击以下再次确认按钮。",
|
"modal_syncconfig_attn": "注意 1/2:此设置只同步(复制)整个 Obsidian 的配置文件夹,但是不会同步其它 . 开头的文件夹或文件。除了会忽略 .git 和 node_modules 文件夹之外,它也并不理解配置文件夹的里各个子文件或子文件夹的含义。\n注意 2/2:配置文件夹被同步之后,各插件的设置或许会出错,且 Obsidian 或许需要重启来重载各插件的新配置。\n如果您同意自行承受以上风险,您可以点击以下再次确认按钮。",
|
||||||
"modal_syncconfig_secondconfirm": "再次确认开启",
|
"modal_syncconfig_secondconfirm": "再次确认开启",
|
||||||
"modal_syncconfig_notice": "您已开启配置文件夹的同步!",
|
"modal_syncconfig_notice": "您已开启配置文件夹的同步!",
|
||||||
"modal_qr_shortdesc": "这里可导出非 oauth2 设置。(意味着:Dropbox 和 OneDrive 信息不会被导出。)\n您可以使用另一个设备来扫描此 QR 码。\n又或者,您可以点击以下按钮复制此特殊 URI。",
|
"modal_qr_shortdesc": "这里可导出(部分)设置。\n您可以使用另一个设备来扫描此 QR 码。\n又或者,您可以点击以下按钮复制此特殊 URI,然后粘贴到另一台设备的网络浏览器或 Remotely Save 设置里的导入部分。",
|
||||||
"modal_qr_button": "点击此按钮复制特殊 URI",
|
"modal_qr_button": "点击此按钮复制特殊 URI",
|
||||||
"modal_qr_button_notice": "特殊 URI 已被复制到剪贴板!",
|
"modal_qr_button_notice": "特殊 URI 已被复制到剪贴板!",
|
||||||
"modal_sizesconflict_title": "Remotely Save:跳过大文件的时候出现了一些冲突",
|
"modal_sizesconflict_title": "Remotely Save:跳过大文件的时候出现了一些冲突",
|
||||||
@@ -277,10 +278,14 @@
|
|||||||
"settings_enablemobilestatusbar_desc": "Obsidian 手机版默认隐藏了状态栏。有些用户希望展示它。这里提供了设置选项。",
|
"settings_enablemobilestatusbar_desc": "Obsidian 手机版默认隐藏了状态栏。有些用户希望展示它。这里提供了设置选项。",
|
||||||
"settings_importexport": "导入导出部分设置",
|
"settings_importexport": "导入导出部分设置",
|
||||||
"settings_export": "导出",
|
"settings_export": "导出",
|
||||||
"settings_export_desc": "用 QR 码导出非 oauth2 的设置信息。",
|
"settings_export_desc": "用 QR 码或 URI 导出设置信息。",
|
||||||
"settings_export_desc_button": "生成 QR 码",
|
"settings_export_all_but_oauth2_button": "导出非 Oauth2 部分",
|
||||||
|
"settings_export_dropbox_button": "导出 Dropbox 部分",
|
||||||
|
"settings_export_onedrive_button": "导出 OneDrive 部分",
|
||||||
"settings_import": "导入",
|
"settings_import": "导入",
|
||||||
"settings_import_desc": "您需要使用系统拍摄 app 或者扫描 QR 码的app,来扫描对应的 QR 码。",
|
"settings_import_desc": "粘贴之前导出的 URI 到这里然后点击“导入”。或,使用拍摄 app 或者扫描 QR 码的 app,来扫描对应的 QR 码。",
|
||||||
|
"settings_import_button": "导入",
|
||||||
|
"settings_import_error_notice": "您输入的 URI 是空的或者不准确的!",
|
||||||
"settings_debug": "调试",
|
"settings_debug": "调试",
|
||||||
"settings_debuglevel": "修改同步提示信息",
|
"settings_debuglevel": "修改同步提示信息",
|
||||||
"settings_debuglevel_desc": "默认值为 \"info\"。您可以改为 \"debug\" 从而在同步时候里获取更多信息。",
|
"settings_debuglevel_desc": "默认值为 \"info\"。您可以改为 \"debug\" 从而在同步时候里获取更多信息。",
|
||||||
@@ -306,6 +311,10 @@
|
|||||||
"settings_delprevsync_desc": "同步算法需要上次成功同步的信息来决定文件变更,这个信息保存在本地的数据库里。如果您想忽略这些信息从而所有文件都被视为新创建的话,可以在此删除之前的信息。",
|
"settings_delprevsync_desc": "同步算法需要上次成功同步的信息来决定文件变更,这个信息保存在本地的数据库里。如果您想忽略这些信息从而所有文件都被视为新创建的话,可以在此删除之前的信息。",
|
||||||
"settings_delprevsync_button": "删除上次同步明细",
|
"settings_delprevsync_button": "删除上次同步明细",
|
||||||
"settings_delprevsync_notice": "(本地数据库里的)上次同步明细已被删除。",
|
"settings_delprevsync_notice": "(本地数据库里的)上次同步明细已被删除。",
|
||||||
|
"settings_profiler_results": "导出性能数据记录",
|
||||||
|
"settings_profiler_results_desc": "插件记录了每次同步每一步的耗时。这里可以导出记录得知哪一步最慢。",
|
||||||
|
"settings_profiler_results_notice": "性能数据已导出",
|
||||||
|
"settings_profiler_results_button_all": "导出所有",
|
||||||
"settings_outputbasepathvaultid": "输出资料库对应的位置和随机分配的 ID",
|
"settings_outputbasepathvaultid": "输出资料库对应的位置和随机分配的 ID",
|
||||||
"settings_outputbasepathvaultid_desc": "用于调试。",
|
"settings_outputbasepathvaultid_desc": "用于调试。",
|
||||||
"settings_outputbasepathvaultid_button": "输出",
|
"settings_outputbasepathvaultid_button": "输出",
|
||||||
|
|||||||
+14
-5
@@ -24,7 +24,7 @@
|
|||||||
"syncrun_shortstep2": "2/2 Remotely Save 已完成同步!",
|
"syncrun_shortstep2": "2/2 Remotely Save 已完成同步!",
|
||||||
"syncrun_abort": "{{manifestID}}-{{theDate}}:中斷同步,同步來源={{triggerSource}},出錯階段={{syncStatus}}",
|
"syncrun_abort": "{{manifestID}}-{{theDate}}:中斷同步,同步來源={{triggerSource}},出錯階段={{syncStatus}}",
|
||||||
"syncrun_abort_protectmodifypercentage": "中斷同步!您設定了不允許 >= {{protectModifyPercentage}}% 的變更,但是現在 {{realModifyDeleteCount}}/{{allFilesCount}}={{percent}}% 的檔案會被修改或刪除!如果您確認這次同步是您想要的,那麼請在設定裡修改允許比例。",
|
"syncrun_abort_protectmodifypercentage": "中斷同步!您設定了不允許 >= {{protectModifyPercentage}}% 的變更,但是現在 {{realModifyDeleteCount}}/{{allFilesCount}}={{percent}}% 的檔案會被修改或刪除!如果您確認這次同步是您想要的,那麼請在設定裡修改允許比例。",
|
||||||
"protocol_saveqr": " {{manifestName}} 新的非 oauth2 設定儲存完成。請重啟外掛設定頁使之生效。",
|
"protocol_saveqr": " {{manifestName}} 的新設定匯入完成。請重啟外掛設定頁使之生效。",
|
||||||
"protocol_callbacknotsupported": "您的 uri callback 暫不支援: {{params}}",
|
"protocol_callbacknotsupported": "您的 uri callback 暫不支援: {{params}}",
|
||||||
"protocol_dropbox_connecting": "正在連線 Dropbox……\n請不要關閉此彈窗。",
|
"protocol_dropbox_connecting": "正在連線 Dropbox……\n請不要關閉此彈窗。",
|
||||||
"protocol_dropbox_connect_succ": "好!我們作為使用者 {{username}} 連線上了 Dropbox!",
|
"protocol_dropbox_connect_succ": "好!我們作為使用者 {{username}} 連線上了 Dropbox!",
|
||||||
@@ -92,6 +92,7 @@
|
|||||||
"modal_dropboxauth_maualinput_conn_succ_revoke": "您已作為使用者 {{username}} 連線到 Dropbox。如果您想斷開連線,點選此按鈕。",
|
"modal_dropboxauth_maualinput_conn_succ_revoke": "您已作為使用者 {{username}} 連線到 Dropbox。如果您想斷開連線,點選此按鈕。",
|
||||||
"modal_dropboxauth_maualinput_conn_fail": "連線 Dropbox 途中出錯了。",
|
"modal_dropboxauth_maualinput_conn_fail": "連線 Dropbox 途中出錯了。",
|
||||||
"modal_onedriveauth_shortdesc": "現在只支援個人版 OneDrive,(暫)不支援企業版。\n在瀏覽器中訪問以下地址,然後按照網頁提示操作。\n到了最後,您應該會被自動重定向回來 Obsidian。",
|
"modal_onedriveauth_shortdesc": "現在只支援個人版 OneDrive,(暫)不支援企業版。\n在瀏覽器中訪問以下地址,然後按照網頁提示操作。\n到了最後,您應該會被自動重定向回來 Obsidian。",
|
||||||
|
"modal_onedriveauth_shortdesc_linux": "您正在用 Linux,有可能無法跳轉回來。請考慮<a href=\"https://github.com/remotely-save/remotely-save/issues/415\">使用</a> flatpack 版本的 Obsidian,或建立 <a href=\"https://github.com/remotely-save/remotely-save/blob/master/docs/linux.md\"><code>obsidian.desktop</code> 檔案</a>。",
|
||||||
"modal_onedriveauth_copybutton": "點選此按鈕從而複製鑑權 url",
|
"modal_onedriveauth_copybutton": "點選此按鈕從而複製鑑權 url",
|
||||||
"modal_onedriveauth_copynotice": "鑑權 url 已複製到剪貼簿!",
|
"modal_onedriveauth_copynotice": "鑑權 url 已複製到剪貼簿!",
|
||||||
"modal_onedriverevokeauth_step1": "第 1 步:用瀏覽器開啟以下地址,點選本外掛對應的“Edit”按鈕,點選“Remove these permissions”按鈕。",
|
"modal_onedriverevokeauth_step1": "第 1 步:用瀏覽器開啟以下地址,點選本外掛對應的“Edit”按鈕,點選“Remove these permissions”按鈕。",
|
||||||
@@ -104,7 +105,7 @@
|
|||||||
"modal_syncconfig_attn": "注意 1/2:此設定只同步(複製)整個 Obsidian 的配置資料夾,但是不會同步其它 . 開頭的資料夾或檔案。除了會忽略 .git 和 node_modules 資料夾之外,它也並不理解配置資料夾的裡各個子檔案或子資料夾的含義。\n注意 2/2:配置資料夾被同步之後,各外掛的設定或許會出錯,且 Obsidian 或許需要重啟來過載各外掛的新配置。\n如果您同意自行承受以上風險,您可以點選以下再次確認按鈕。",
|
"modal_syncconfig_attn": "注意 1/2:此設定只同步(複製)整個 Obsidian 的配置資料夾,但是不會同步其它 . 開頭的資料夾或檔案。除了會忽略 .git 和 node_modules 資料夾之外,它也並不理解配置資料夾的裡各個子檔案或子資料夾的含義。\n注意 2/2:配置資料夾被同步之後,各外掛的設定或許會出錯,且 Obsidian 或許需要重啟來過載各外掛的新配置。\n如果您同意自行承受以上風險,您可以點選以下再次確認按鈕。",
|
||||||
"modal_syncconfig_secondconfirm": "再次確認開啟",
|
"modal_syncconfig_secondconfirm": "再次確認開啟",
|
||||||
"modal_syncconfig_notice": "您已開啟配置資料夾的同步!",
|
"modal_syncconfig_notice": "您已開啟配置資料夾的同步!",
|
||||||
"modal_qr_shortdesc": "這裡可匯出非 oauth2 設定。(意味著:Dropbox 和 OneDrive 資訊不會被匯出。)\n您可以使用另一個裝置來掃描此 QR 碼。\n又或者,您可以點選以下按鈕複製此特殊 URI。",
|
"modal_qr_shortdesc": "這裡可匯出(部分)設定。\n您可以使用另一個裝置來掃描此 QR 碼。\n又或者,您可以點選以下按鈕複製此特殊 URI,然後貼上到另一臺裝置的網路瀏覽器或 Remotely Save 設定裡的匯入部分。",
|
||||||
"modal_qr_button": "點選此按鈕複製特殊 URI",
|
"modal_qr_button": "點選此按鈕複製特殊 URI",
|
||||||
"modal_qr_button_notice": "特殊 URI 已被複制到剪貼簿!",
|
"modal_qr_button_notice": "特殊 URI 已被複制到剪貼簿!",
|
||||||
"modal_sizesconflict_title": "Remotely Save:跳過大檔案的時候出現了一些衝突",
|
"modal_sizesconflict_title": "Remotely Save:跳過大檔案的時候出現了一些衝突",
|
||||||
@@ -276,10 +277,14 @@
|
|||||||
"settings_enablemobilestatusbar_desc": "Obsidian 手機版預設隱藏了狀態列。有些使用者希望展示它。這裡提供了設定選項。",
|
"settings_enablemobilestatusbar_desc": "Obsidian 手機版預設隱藏了狀態列。有些使用者希望展示它。這裡提供了設定選項。",
|
||||||
"settings_importexport": "匯入匯出部分設定",
|
"settings_importexport": "匯入匯出部分設定",
|
||||||
"settings_export": "匯出",
|
"settings_export": "匯出",
|
||||||
"settings_export_desc": "用 QR 碼匯出非 oauth2 的設定資訊。",
|
"settings_export_desc": "用 QR 碼或 URI 匯出設定資訊。",
|
||||||
"settings_export_desc_button": "生成 QR 碼",
|
"settings_export_all_but_oauth2_button": "匯出非 Oauth2 部分",
|
||||||
|
"settings_export_dropbox_button": "匯出 Dropbox 部分",
|
||||||
|
"settings_export_onedrive_button": "匯出 OneDrive 部分",
|
||||||
"settings_import": "匯入",
|
"settings_import": "匯入",
|
||||||
"settings_import_desc": "您需要使用系統拍攝 app 或者掃描 QR 碼的app,來掃描對應的 QR 碼。",
|
"settings_import_desc": "貼上之前匯出的 URI 到這裡然後點選“匯入”。或,使用拍攝 app 或者掃描 QR 碼的 app,來掃描對應的 QR 碼。",
|
||||||
|
"settings_import_button": "匯入",
|
||||||
|
"settings_import_error_notice": "您輸入的 URI 是空的或者不準確的!",
|
||||||
"settings_debug": "除錯",
|
"settings_debug": "除錯",
|
||||||
"settings_debuglevel": "修改同步提示資訊",
|
"settings_debuglevel": "修改同步提示資訊",
|
||||||
"settings_debuglevel_desc": "預設值為 \"info\"。您可以改為 \"debug\" 從而在同步時候裡獲取更多資訊。",
|
"settings_debuglevel_desc": "預設值為 \"info\"。您可以改為 \"debug\" 從而在同步時候裡獲取更多資訊。",
|
||||||
@@ -305,6 +310,10 @@
|
|||||||
"settings_delprevsync_desc": "同步演算法需要上次成功同步的資訊來決定檔案變更,這個資訊儲存在本地的資料庫裡。如果您想忽略這些資訊從而所有檔案都被視為新建立的話,可以在此刪除之前的資訊。",
|
"settings_delprevsync_desc": "同步演算法需要上次成功同步的資訊來決定檔案變更,這個資訊儲存在本地的資料庫裡。如果您想忽略這些資訊從而所有檔案都被視為新建立的話,可以在此刪除之前的資訊。",
|
||||||
"settings_delprevsync_button": "刪除上次同步明細",
|
"settings_delprevsync_button": "刪除上次同步明細",
|
||||||
"settings_delprevsync_notice": "(本地資料庫裡的)上次同步明細已被刪除。",
|
"settings_delprevsync_notice": "(本地資料庫裡的)上次同步明細已被刪除。",
|
||||||
|
"settings_profiler_results": "匯出效能資料記錄",
|
||||||
|
"settings_profiler_results_desc": "外掛記錄了每次同步每一步的耗時。這裡可以匯出記錄得知哪一步最慢。",
|
||||||
|
"settings_profiler_results_notice": "效能資料已匯出",
|
||||||
|
"settings_profiler_results_button_all": "匯出所有",
|
||||||
"settings_outputbasepathvaultid": "輸出資料庫對應的位置和隨機分配的 ID",
|
"settings_outputbasepathvaultid": "輸出資料庫對應的位置和隨機分配的 ID",
|
||||||
"settings_outputbasepathvaultid_desc": "用於除錯。",
|
"settings_outputbasepathvaultid_desc": "用於除錯。",
|
||||||
"settings_outputbasepathvaultid_button": "輸出",
|
"settings_outputbasepathvaultid_button": "輸出",
|
||||||
|
|||||||
+12
-1
@@ -1,16 +1,21 @@
|
|||||||
import { TFile, TFolder, type Vault } from "obsidian";
|
import { TFile, TFolder, type Vault } from "obsidian";
|
||||||
import type { Entity, MixedEntity } from "./baseTypes";
|
import type { Entity, MixedEntity } from "./baseTypes";
|
||||||
import { listFilesInObsFolder } from "./obsFolderLister";
|
import { listFilesInObsFolder } from "./obsFolderLister";
|
||||||
|
import { Profiler } from "./profiler";
|
||||||
|
|
||||||
export const getLocalEntityList = async (
|
export const getLocalEntityList = async (
|
||||||
vault: Vault,
|
vault: Vault,
|
||||||
syncConfigDir: boolean,
|
syncConfigDir: boolean,
|
||||||
configDir: string,
|
configDir: string,
|
||||||
pluginID: string
|
pluginID: string,
|
||||||
|
profiler: Profiler
|
||||||
) => {
|
) => {
|
||||||
|
profiler.addIndent();
|
||||||
|
profiler.insert("enter getLocalEntityList");
|
||||||
const local: Entity[] = [];
|
const local: Entity[] = [];
|
||||||
|
|
||||||
const localTAbstractFiles = vault.getAllLoadedFiles();
|
const localTAbstractFiles = vault.getAllLoadedFiles();
|
||||||
|
profiler.insert("finish getting getAllLoadedFiles");
|
||||||
for (const entry of localTAbstractFiles) {
|
for (const entry of localTAbstractFiles) {
|
||||||
let r = {} as Entity;
|
let r = {} as Entity;
|
||||||
let key = entry.path;
|
let key = entry.path;
|
||||||
@@ -54,12 +59,18 @@ export const getLocalEntityList = async (
|
|||||||
local.push(r);
|
local.push(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
profiler.insert("finish transforming getAllLoadedFiles");
|
||||||
|
|
||||||
if (syncConfigDir) {
|
if (syncConfigDir) {
|
||||||
|
profiler.insert("into syncConfigDir");
|
||||||
const syncFiles = await listFilesInObsFolder(configDir, vault, pluginID);
|
const syncFiles = await listFilesInObsFolder(configDir, vault, pluginID);
|
||||||
for (const f of syncFiles) {
|
for (const f of syncFiles) {
|
||||||
local.push(f);
|
local.push(f);
|
||||||
}
|
}
|
||||||
|
profiler.insert("finish syncConfigDir");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
profiler.insert("finish getLocalEntityList");
|
||||||
|
profiler.removeIndent();
|
||||||
return local;
|
return local;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export const DEFAULT_TBL_VAULT_RANDOM_ID_MAPPING = "vaultrandomidmapping";
|
|||||||
export const DEFAULT_TBL_LOGGER_OUTPUT = "loggeroutput";
|
export const DEFAULT_TBL_LOGGER_OUTPUT = "loggeroutput";
|
||||||
export const DEFAULT_TBL_SIMPLE_KV_FOR_MISC = "simplekvformisc";
|
export const DEFAULT_TBL_SIMPLE_KV_FOR_MISC = "simplekvformisc";
|
||||||
export const DEFAULT_TBL_PREV_SYNC_RECORDS = "prevsyncrecords";
|
export const DEFAULT_TBL_PREV_SYNC_RECORDS = "prevsyncrecords";
|
||||||
|
export const DEFAULT_TBL_PROFILER_RESULTS = "profilerresults";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated
|
* @deprecated
|
||||||
@@ -58,6 +59,7 @@ export interface InternalDBs {
|
|||||||
loggerOutputTbl: LocalForage;
|
loggerOutputTbl: LocalForage;
|
||||||
simpleKVForMiscTbl: LocalForage;
|
simpleKVForMiscTbl: LocalForage;
|
||||||
prevSyncRecordsTbl: LocalForage;
|
prevSyncRecordsTbl: LocalForage;
|
||||||
|
profilerResultsTbl: LocalForage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated
|
* @deprecated
|
||||||
@@ -204,6 +206,10 @@ export const prepareDBs = async (
|
|||||||
name: DEFAULT_DB_NAME,
|
name: DEFAULT_DB_NAME,
|
||||||
storeName: DEFAULT_TBL_PREV_SYNC_RECORDS,
|
storeName: DEFAULT_TBL_PREV_SYNC_RECORDS,
|
||||||
}),
|
}),
|
||||||
|
profilerResultsTbl: localforage.createInstance({
|
||||||
|
name: DEFAULT_DB_NAME,
|
||||||
|
storeName: DEFAULT_TBL_PROFILER_RESULTS,
|
||||||
|
}),
|
||||||
|
|
||||||
fileHistoryTbl: localforage.createInstance({
|
fileHistoryTbl: localforage.createInstance({
|
||||||
name: DEFAULT_DB_NAME,
|
name: DEFAULT_DB_NAME,
|
||||||
@@ -524,3 +530,45 @@ export const upsertPluginVersionByVault = async (
|
|||||||
newVersion: newVersion,
|
newVersion: newVersion,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const insertProfilerResultByVault = async (
|
||||||
|
db: InternalDBs,
|
||||||
|
profilerStr: string,
|
||||||
|
vaultRandomID: string,
|
||||||
|
remoteType: SUPPORTED_SERVICES_TYPE
|
||||||
|
) => {
|
||||||
|
const now = Date.now();
|
||||||
|
await db.profilerResultsTbl.setItem(`${vaultRandomID}\t${now}`, profilerStr);
|
||||||
|
|
||||||
|
// clear older one while writing
|
||||||
|
const records = (await db.profilerResultsTbl.keys())
|
||||||
|
.filter((x) => x.startsWith(`${vaultRandomID}\t`))
|
||||||
|
.map((x) => parseInt(x.split("\t")[1]));
|
||||||
|
records.sort((a, b) => -(a - b)); // descending
|
||||||
|
while (records.length > 5) {
|
||||||
|
const ts = records.pop()!;
|
||||||
|
await db.profilerResultsTbl.removeItem(`${vaultRandomID}\t${ts}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const readAllProfilerResultsByVault = async (
|
||||||
|
db: InternalDBs,
|
||||||
|
vaultRandomID: string
|
||||||
|
) => {
|
||||||
|
const records = [] as { val: string; ts: number }[];
|
||||||
|
await db.profilerResultsTbl.iterate((value, key, iterationNumber) => {
|
||||||
|
if (key.startsWith(`${vaultRandomID}\t`)) {
|
||||||
|
records.push({
|
||||||
|
val: value as string,
|
||||||
|
ts: parseInt(key.split("\t")[1]),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
records.sort((a, b) => -(a.ts - b.ts)); // descending
|
||||||
|
|
||||||
|
if (records === undefined) {
|
||||||
|
return [] as string[];
|
||||||
|
} else {
|
||||||
|
return records.map((x) => x.val);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
+46
-5
@@ -35,6 +35,7 @@ import {
|
|||||||
upsertLastSuccessSyncTimeByVault,
|
upsertLastSuccessSyncTimeByVault,
|
||||||
getLastSuccessSyncTimeByVault,
|
getLastSuccessSyncTimeByVault,
|
||||||
getAllPrevSyncRecordsByVaultAndProfile,
|
getAllPrevSyncRecordsByVaultAndProfile,
|
||||||
|
insertProfilerResultByVault,
|
||||||
} from "./localdb";
|
} from "./localdb";
|
||||||
import { RemoteClient } from "./remote";
|
import { RemoteClient } from "./remote";
|
||||||
import {
|
import {
|
||||||
@@ -69,6 +70,7 @@ import AggregateError from "aggregate-error";
|
|||||||
import { exportVaultSyncPlansToFiles } from "./debugMode";
|
import { exportVaultSyncPlansToFiles } from "./debugMode";
|
||||||
import { changeMobileStatusBar, compareVersion } from "./misc";
|
import { changeMobileStatusBar, compareVersion } from "./misc";
|
||||||
import { Cipher } from "./encryptUnified";
|
import { Cipher } from "./encryptUnified";
|
||||||
|
import { Profiler } from "./profiler";
|
||||||
|
|
||||||
const DEFAULT_SETTINGS: RemotelySavePluginSettings = {
|
const DEFAULT_SETTINGS: RemotelySavePluginSettings = {
|
||||||
s3: DEFAULT_S3_CONFIG,
|
s3: DEFAULT_S3_CONFIG,
|
||||||
@@ -154,6 +156,8 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
appContainerObserver?: MutationObserver;
|
appContainerObserver?: MutationObserver;
|
||||||
|
|
||||||
async syncRun(triggerSource: SyncTriggerSourceType = "manual") {
|
async syncRun(triggerSource: SyncTriggerSourceType = "manual") {
|
||||||
|
const profiler = new Profiler("start of syncRun");
|
||||||
|
|
||||||
const t = (x: TransItemType, vars?: any) => {
|
const t = (x: TransItemType, vars?: any) => {
|
||||||
return this.i18n.t(x, vars);
|
return this.i18n.t(x, vars);
|
||||||
};
|
};
|
||||||
@@ -234,6 +238,7 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.syncStatus = "preparing";
|
this.syncStatus = "preparing";
|
||||||
|
profiler.insert("finish step1");
|
||||||
|
|
||||||
if (this.settings.currLogLevel === "info") {
|
if (this.settings.currLogLevel === "info") {
|
||||||
// pass
|
// pass
|
||||||
@@ -249,12 +254,15 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
this.settings.dropbox,
|
this.settings.dropbox,
|
||||||
this.settings.onedrive,
|
this.settings.onedrive,
|
||||||
this.app.vault.getName(),
|
this.app.vault.getName(),
|
||||||
() => self.saveSettings()
|
() => self.saveSettings(),
|
||||||
|
profiler
|
||||||
);
|
);
|
||||||
const remoteEntityList = await client.listAllFromRemote();
|
const remoteEntityList = await client.listAllFromRemote();
|
||||||
console.debug("remoteEntityList:");
|
console.debug("remoteEntityList:");
|
||||||
console.debug(remoteEntityList);
|
console.debug(remoteEntityList);
|
||||||
|
|
||||||
|
profiler.insert("finish step2 (listing remote)");
|
||||||
|
|
||||||
if (this.settings.currLogLevel === "info") {
|
if (this.settings.currLogLevel === "info") {
|
||||||
// pass
|
// pass
|
||||||
} else {
|
} else {
|
||||||
@@ -272,6 +280,8 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
throw Error(passwordCheckResult.reason);
|
throw Error(passwordCheckResult.reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
profiler.insert("finish step3 (checking password)");
|
||||||
|
|
||||||
if (this.settings.currLogLevel === "info") {
|
if (this.settings.currLogLevel === "info") {
|
||||||
// pass
|
// pass
|
||||||
} else {
|
} else {
|
||||||
@@ -282,11 +292,14 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
this.app.vault,
|
this.app.vault,
|
||||||
this.settings.syncConfigDir ?? false,
|
this.settings.syncConfigDir ?? false,
|
||||||
this.app.vault.configDir,
|
this.app.vault.configDir,
|
||||||
this.manifest.id
|
this.manifest.id,
|
||||||
|
profiler
|
||||||
);
|
);
|
||||||
console.debug("localEntityList:");
|
console.debug("localEntityList:");
|
||||||
console.debug(localEntityList);
|
console.debug(localEntityList);
|
||||||
|
|
||||||
|
profiler.insert("finish step4 (local meta)");
|
||||||
|
|
||||||
if (this.settings.currLogLevel === "info") {
|
if (this.settings.currLogLevel === "info") {
|
||||||
// pass
|
// pass
|
||||||
} else {
|
} else {
|
||||||
@@ -301,6 +314,8 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
console.debug("prevSyncEntityList:");
|
console.debug("prevSyncEntityList:");
|
||||||
console.debug(prevSyncEntityList);
|
console.debug(prevSyncEntityList);
|
||||||
|
|
||||||
|
profiler.insert("finish step5 (prev sync)");
|
||||||
|
|
||||||
if (this.settings.currLogLevel === "info") {
|
if (this.settings.currLogLevel === "info") {
|
||||||
// pass
|
// pass
|
||||||
} else {
|
} else {
|
||||||
@@ -316,17 +331,21 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
this.settings.syncUnderscoreItems ?? false,
|
this.settings.syncUnderscoreItems ?? false,
|
||||||
this.settings.ignorePaths ?? [],
|
this.settings.ignorePaths ?? [],
|
||||||
cipher,
|
cipher,
|
||||||
this.settings.serviceType
|
this.settings.serviceType,
|
||||||
|
profiler
|
||||||
);
|
);
|
||||||
|
profiler.insert("finish building partial mixedEntity");
|
||||||
mixedEntityMappings = await getSyncPlanInplace(
|
mixedEntityMappings = await getSyncPlanInplace(
|
||||||
mixedEntityMappings,
|
mixedEntityMappings,
|
||||||
this.settings.howToCleanEmptyFolder ?? "skip",
|
this.settings.howToCleanEmptyFolder ?? "skip",
|
||||||
this.settings.skipSizeLargerThan ?? -1,
|
this.settings.skipSizeLargerThan ?? -1,
|
||||||
this.settings.conflictAction ?? "keep_newer",
|
this.settings.conflictAction ?? "keep_newer",
|
||||||
this.settings.syncDirection ?? "bidirectional"
|
this.settings.syncDirection ?? "bidirectional",
|
||||||
|
profiler
|
||||||
);
|
);
|
||||||
console.info(`mixedEntityMappings:`);
|
console.info(`mixedEntityMappings:`);
|
||||||
console.info(mixedEntityMappings); // for debugging
|
console.info(mixedEntityMappings); // for debugging
|
||||||
|
profiler.insert("finish building full sync plan");
|
||||||
await insertSyncPlanRecordByVault(
|
await insertSyncPlanRecordByVault(
|
||||||
this.db,
|
this.db,
|
||||||
mixedEntityMappings,
|
mixedEntityMappings,
|
||||||
@@ -334,6 +353,9 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
client.serviceType
|
client.serviceType
|
||||||
);
|
);
|
||||||
|
|
||||||
|
profiler.insert("finish writing sync plan");
|
||||||
|
profiler.insert("finish step6 (plan)");
|
||||||
|
|
||||||
// The operations above are almost read only and kind of safe.
|
// The operations above are almost read only and kind of safe.
|
||||||
// The operations below begins to write or delete (!!!) something.
|
// The operations below begins to write or delete (!!!) something.
|
||||||
|
|
||||||
@@ -384,7 +406,8 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
decision,
|
decision,
|
||||||
triggerSource
|
triggerSource
|
||||||
),
|
),
|
||||||
this.db
|
this.db,
|
||||||
|
profiler
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.syncStatus = "syncing";
|
this.syncStatus = "syncing";
|
||||||
@@ -397,6 +420,8 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
|
|
||||||
cipher.closeResources();
|
cipher.closeResources();
|
||||||
|
|
||||||
|
profiler.insert("finish step7 (actual sync)");
|
||||||
|
|
||||||
if (this.settings.currLogLevel === "info") {
|
if (this.settings.currLogLevel === "info") {
|
||||||
getNotice(t("syncrun_shortstep2"));
|
getNotice(t("syncrun_shortstep2"));
|
||||||
} else {
|
} else {
|
||||||
@@ -406,6 +431,8 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
this.syncStatus = "finish";
|
this.syncStatus = "finish";
|
||||||
this.syncStatus = "idle";
|
this.syncStatus = "idle";
|
||||||
|
|
||||||
|
profiler.insert("finish step8");
|
||||||
|
|
||||||
const lastSuccessSyncMillis = Date.now();
|
const lastSuccessSyncMillis = Date.now();
|
||||||
await upsertLastSuccessSyncTimeByVault(
|
await upsertLastSuccessSyncTimeByVault(
|
||||||
this.db,
|
this.db,
|
||||||
@@ -429,6 +456,7 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
}-${Date.now()}: finish sync, triggerSource=${triggerSource}`
|
}-${Date.now()}: finish sync, triggerSource=${triggerSource}`
|
||||||
);
|
);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
profiler.insert("start error branch");
|
||||||
const msg = t("syncrun_abort", {
|
const msg = t("syncrun_abort", {
|
||||||
manifestID: this.manifest.id,
|
manifestID: this.manifest.id,
|
||||||
theDate: `${Date.now()}`,
|
theDate: `${Date.now()}`,
|
||||||
@@ -450,7 +478,19 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
setIcon(this.syncRibbon, iconNameSyncWait);
|
setIcon(this.syncRibbon, iconNameSyncWait);
|
||||||
this.syncRibbon.setAttribute("aria-label", originLabel);
|
this.syncRibbon.setAttribute("aria-label", originLabel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
profiler.insert("finish error branch");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
profiler.insert("finish syncRun");
|
||||||
|
console.debug(profiler.toString());
|
||||||
|
insertProfilerResultByVault(
|
||||||
|
this.db,
|
||||||
|
profiler.toString(),
|
||||||
|
this.vaultRandomID,
|
||||||
|
this.settings.serviceType
|
||||||
|
);
|
||||||
|
profiler.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
async onload() {
|
async onload() {
|
||||||
@@ -524,6 +564,7 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
this.syncStatus = "idle";
|
this.syncStatus = "idle";
|
||||||
|
|
||||||
this.registerObsidianProtocolHandler(COMMAND_URI, async (inputParams) => {
|
this.registerObsidianProtocolHandler(COMMAND_URI, async (inputParams) => {
|
||||||
|
// console.debug(inputParams);
|
||||||
const parsed = importQrCodeUri(inputParams, this.app.vault.getName());
|
const parsed = importQrCodeUri(inputParams, this.app.vault.getName());
|
||||||
if (parsed.status === "error") {
|
if (parsed.status === "error") {
|
||||||
new Notice(parsed.message);
|
new Notice(parsed.message);
|
||||||
|
|||||||
+9
-1
@@ -513,6 +513,14 @@ export const stringToFragment = (string: string) => {
|
|||||||
return wrapper.content;
|
return wrapper.content;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://stackoverflow.com/questions/39538473/using-settimeout-on-promise-chain
|
||||||
|
* @param ms
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const delay = (ms: number) =>
|
||||||
|
new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://forum.obsidian.md/t/css-to-show-status-bar-on-mobile-devices/77185
|
* https://forum.obsidian.md/t/css-to-show-status-bar-on-mobile-devices/77185
|
||||||
* @param op
|
* @param op
|
||||||
@@ -550,7 +558,7 @@ export const changeMobileStatusBar = (
|
|||||||
k.className.contains("mobile-toolbar")
|
k.className.contains("mobile-toolbar")
|
||||||
) {
|
) {
|
||||||
// have to wait, otherwise the height is not correct??
|
// have to wait, otherwise the height is not correct??
|
||||||
await new Promise((resolve) => setTimeout(resolve, 300));
|
await delay(300);
|
||||||
const height = window
|
const height = window
|
||||||
.getComputedStyle(k as Element)
|
.getComputedStyle(k as Element)
|
||||||
.getPropertyValue("height");
|
.getPropertyValue("height");
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
import { unixTimeToStr } from "./misc";
|
||||||
|
|
||||||
|
interface BreakPoint {
|
||||||
|
label: string;
|
||||||
|
fakeTimeMilli: number; // it's NOT a unix timestamp
|
||||||
|
indent: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Profiler {
|
||||||
|
startTime: number;
|
||||||
|
breakPoints: BreakPoint[];
|
||||||
|
indent: number;
|
||||||
|
constructor(label?: string) {
|
||||||
|
this.breakPoints = [];
|
||||||
|
this.indent = 0;
|
||||||
|
this.startTime = 0;
|
||||||
|
|
||||||
|
if (label !== undefined) {
|
||||||
|
this.startTime = Date.now();
|
||||||
|
this.breakPoints.push({
|
||||||
|
label: label,
|
||||||
|
fakeTimeMilli: performance.now(),
|
||||||
|
indent: this.indent,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
insert(label: string) {
|
||||||
|
if (this.breakPoints.length === 0) {
|
||||||
|
this.startTime = Date.now();
|
||||||
|
}
|
||||||
|
this.breakPoints.push({
|
||||||
|
label: label,
|
||||||
|
fakeTimeMilli: performance.now(),
|
||||||
|
indent: this.indent,
|
||||||
|
});
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
addIndent() {
|
||||||
|
this.indent += 2;
|
||||||
|
}
|
||||||
|
removeIndent() {
|
||||||
|
this.indent -= 2;
|
||||||
|
if (this.indent < 0) {
|
||||||
|
this.indent = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this.breakPoints = [];
|
||||||
|
this.indent = 0;
|
||||||
|
this.startTime = 0;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
if (this.breakPoints.length === 0) {
|
||||||
|
return "nothing in profiler";
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = `[startTime]: ${unixTimeToStr(this.startTime)}`;
|
||||||
|
for (let i = 0; i < this.breakPoints.length; ++i) {
|
||||||
|
if (i === 0) {
|
||||||
|
res += `\n[${this.breakPoints[i]["label"]}]: start`;
|
||||||
|
} else {
|
||||||
|
const label = this.breakPoints[i]["label"];
|
||||||
|
const indent = this.breakPoints[i]["indent"];
|
||||||
|
const millsec =
|
||||||
|
Math.round(
|
||||||
|
(this.breakPoints[i]["fakeTimeMilli"] -
|
||||||
|
this.breakPoints[i - 1]["fakeTimeMilli"]) *
|
||||||
|
10
|
||||||
|
) / 10.0;
|
||||||
|
res += `\n${" ".repeat(indent)}[${label}]: ${millsec}ms`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-1
@@ -13,6 +13,7 @@ import * as onedrive from "./remoteForOnedrive";
|
|||||||
import * as s3 from "./remoteForS3";
|
import * as s3 from "./remoteForS3";
|
||||||
import * as webdav from "./remoteForWebdav";
|
import * as webdav from "./remoteForWebdav";
|
||||||
import { Cipher } from "./encryptUnified";
|
import { Cipher } from "./encryptUnified";
|
||||||
|
import { Profiler } from "./profiler";
|
||||||
|
|
||||||
export class RemoteClient {
|
export class RemoteClient {
|
||||||
readonly serviceType: SUPPORTED_SERVICES_TYPE;
|
readonly serviceType: SUPPORTED_SERVICES_TYPE;
|
||||||
@@ -31,7 +32,8 @@ export class RemoteClient {
|
|||||||
dropboxConfig?: DropboxConfig,
|
dropboxConfig?: DropboxConfig,
|
||||||
onedriveConfig?: OnedriveConfig,
|
onedriveConfig?: OnedriveConfig,
|
||||||
vaultName?: string,
|
vaultName?: string,
|
||||||
saveUpdatedConfigFunc?: () => Promise<any>
|
saveUpdatedConfigFunc?: () => Promise<any>,
|
||||||
|
profiler?: Profiler
|
||||||
) {
|
) {
|
||||||
this.serviceType = serviceType;
|
this.serviceType = serviceType;
|
||||||
// the client may modify the config inplace,
|
// the client may modify the config inplace,
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { rangeDelay } from "delay";
|
|
||||||
import { Dropbox, DropboxAuth } from "dropbox";
|
import { Dropbox, DropboxAuth } from "dropbox";
|
||||||
import type { files, DropboxResponseError, DropboxResponse } from "dropbox";
|
import type { files, DropboxResponseError, DropboxResponse } from "dropbox";
|
||||||
import { Vault } from "obsidian";
|
import { Vault } from "obsidian";
|
||||||
@@ -12,6 +11,7 @@ import {
|
|||||||
} from "./baseTypes";
|
} from "./baseTypes";
|
||||||
import {
|
import {
|
||||||
bufferToArrayBuffer,
|
bufferToArrayBuffer,
|
||||||
|
delay,
|
||||||
fixEntityListCasesInplace,
|
fixEntityListCasesInplace,
|
||||||
getFolderLevels,
|
getFolderLevels,
|
||||||
hasEmojiInText,
|
hasEmojiInText,
|
||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
mkdirpInVault,
|
mkdirpInVault,
|
||||||
} from "./misc";
|
} from "./misc";
|
||||||
import { Cipher } from "./encryptUnified";
|
import { Cipher } from "./encryptUnified";
|
||||||
|
import { random } from "lodash";
|
||||||
|
|
||||||
export { Dropbox } from "dropbox";
|
export { Dropbox } from "dropbox";
|
||||||
|
|
||||||
@@ -292,7 +293,7 @@ async function retryReq<T>(
|
|||||||
2
|
2
|
||||||
)}`
|
)}`
|
||||||
);
|
);
|
||||||
await rangeDelay(secMin * 1000, secMax * 1000);
|
await delay(random(secMin * 1000, secMax * 1000));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -267,6 +267,10 @@ const fromDriveItemToEntity = (x: DriveItem, remoteBaseDir: string): Entity => {
|
|||||||
// pure english: /drive/root:/Apps/remotely-save/${remoteBaseDir}
|
// pure english: /drive/root:/Apps/remotely-save/${remoteBaseDir}
|
||||||
// or localized, e.g.: /drive/root:/应用/remotely-save/${remoteBaseDir}
|
// or localized, e.g.: /drive/root:/应用/remotely-save/${remoteBaseDir}
|
||||||
const FIRST_COMMON_PREFIX_REGEX = /^\/drive\/root:\/[^\/]+\/remotely-save\//g;
|
const FIRST_COMMON_PREFIX_REGEX = /^\/drive\/root:\/[^\/]+\/remotely-save\//g;
|
||||||
|
|
||||||
|
// why?? /drive/root:/Apps/Graph
|
||||||
|
const FIFTH_COMMON_PREFIX_REGEX = /^\/drive\/root:\/[^\/]+\/Graph\//g;
|
||||||
|
|
||||||
// or the root is absolute path /Livefolders,
|
// or the root is absolute path /Livefolders,
|
||||||
// e.g.: /Livefolders/应用/remotely-save/${remoteBaseDir}
|
// e.g.: /Livefolders/应用/remotely-save/${remoteBaseDir}
|
||||||
const SECOND_COMMON_PREFIX_REGEX = /^\/Livefolders\/[^\/]+\/remotely-save\//g;
|
const SECOND_COMMON_PREFIX_REGEX = /^\/Livefolders\/[^\/]+\/remotely-save\//g;
|
||||||
@@ -289,6 +293,7 @@ const fromDriveItemToEntity = (x: DriveItem, remoteBaseDir: string): Entity => {
|
|||||||
}
|
}
|
||||||
const fullPathOriginal = `${x.parentReference.path}/${x.name}`;
|
const fullPathOriginal = `${x.parentReference.path}/${x.name}`;
|
||||||
const matchFirstPrefixRes = fullPathOriginal.match(FIRST_COMMON_PREFIX_REGEX);
|
const matchFirstPrefixRes = fullPathOriginal.match(FIRST_COMMON_PREFIX_REGEX);
|
||||||
|
const matchFifthPrefixRes = fullPathOriginal.match(FIFTH_COMMON_PREFIX_REGEX);
|
||||||
const matchSecondPrefixRes = fullPathOriginal.match(
|
const matchSecondPrefixRes = fullPathOriginal.match(
|
||||||
SECOND_COMMON_PREFIX_REGEX
|
SECOND_COMMON_PREFIX_REGEX
|
||||||
);
|
);
|
||||||
@@ -299,6 +304,12 @@ const fromDriveItemToEntity = (x: DriveItem, remoteBaseDir: string): Entity => {
|
|||||||
) {
|
) {
|
||||||
const foundPrefix = `${matchFirstPrefixRes[0]}${remoteBaseDir}`;
|
const foundPrefix = `${matchFirstPrefixRes[0]}${remoteBaseDir}`;
|
||||||
key = fullPathOriginal.substring(foundPrefix.length + 1);
|
key = fullPathOriginal.substring(foundPrefix.length + 1);
|
||||||
|
} else if (
|
||||||
|
matchFifthPrefixRes !== null &&
|
||||||
|
fullPathOriginal.startsWith(`${matchFifthPrefixRes[0]}${remoteBaseDir}`)
|
||||||
|
) {
|
||||||
|
const foundPrefix = `${matchFifthPrefixRes[0]}${remoteBaseDir}`;
|
||||||
|
key = fullPathOriginal.substring(foundPrefix.length + 1);
|
||||||
} else if (
|
} else if (
|
||||||
matchSecondPrefixRes !== null &&
|
matchSecondPrefixRes !== null &&
|
||||||
fullPathOriginal.startsWith(`${matchSecondPrefixRes[0]}${remoteBaseDir}`)
|
fullPathOriginal.startsWith(`${matchSecondPrefixRes[0]}${remoteBaseDir}`)
|
||||||
@@ -407,6 +418,19 @@ class MyAuthProvider implements AuthenticationProvider {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* to export the settings in qrcode,
|
||||||
|
* we want to "trim" or "shrink" the settings
|
||||||
|
* @param onedriveConfig
|
||||||
|
*/
|
||||||
|
export const getShrinkedSettings = (onedriveConfig: OnedriveConfig) => {
|
||||||
|
const config = cloneDeep(onedriveConfig);
|
||||||
|
config.accessToken = "x";
|
||||||
|
config.accessTokenExpiresInSeconds = 1;
|
||||||
|
config.accessTokenExpiresAtTime = 1;
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
|
||||||
export class WrappedOnedriveClient {
|
export class WrappedOnedriveClient {
|
||||||
onedriveConfig: OnedriveConfig;
|
onedriveConfig: OnedriveConfig;
|
||||||
remoteBaseDir: string;
|
remoteBaseDir: string;
|
||||||
|
|||||||
+115
-7
@@ -23,9 +23,17 @@ import {
|
|||||||
WebdavAuthType,
|
WebdavAuthType,
|
||||||
WebdavDepthType,
|
WebdavDepthType,
|
||||||
CipherMethodType,
|
CipherMethodType,
|
||||||
|
QRExportType,
|
||||||
} from "./baseTypes";
|
} from "./baseTypes";
|
||||||
import { exportVaultSyncPlansToFiles } from "./debugMode";
|
import {
|
||||||
import { exportQrCodeUri } from "./importExport";
|
exportVaultProfilerResultsToFiles,
|
||||||
|
exportVaultSyncPlansToFiles,
|
||||||
|
} from "./debugMode";
|
||||||
|
import {
|
||||||
|
exportQrCodeUri,
|
||||||
|
importQrCodeUri,
|
||||||
|
parseUriByHand,
|
||||||
|
} from "./importExport";
|
||||||
import {
|
import {
|
||||||
clearAllPrevSyncRecordByVault,
|
clearAllPrevSyncRecordByVault,
|
||||||
clearAllSyncPlanRecords,
|
clearAllSyncPlanRecords,
|
||||||
@@ -52,6 +60,7 @@ import {
|
|||||||
stringToFragment,
|
stringToFragment,
|
||||||
} from "./misc";
|
} from "./misc";
|
||||||
import { simpleTransRemotePrefix } from "./remoteForS3";
|
import { simpleTransRemotePrefix } from "./remoteForS3";
|
||||||
|
import cloneDeep from "lodash/cloneDeep";
|
||||||
|
|
||||||
class PasswordModal extends Modal {
|
class PasswordModal extends Modal {
|
||||||
plugin: RemotelySavePlugin;
|
plugin: RemotelySavePlugin;
|
||||||
@@ -544,6 +553,15 @@ export class OnedriveAuthModal extends Modal {
|
|||||||
text: val,
|
text: val,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
if (Platform.isLinux) {
|
||||||
|
t("modal_onedriveauth_shortdesc_linux")
|
||||||
|
.split("\n")
|
||||||
|
.forEach((val) => {
|
||||||
|
contentEl.createEl("p", {
|
||||||
|
text: stringToFragment(val),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
const div2 = contentEl.createDiv();
|
const div2 = contentEl.createDiv();
|
||||||
div2.createEl(
|
div2.createEl(
|
||||||
"button",
|
"button",
|
||||||
@@ -695,9 +713,11 @@ class SyncConfigDirModal extends Modal {
|
|||||||
|
|
||||||
class ExportSettingsQrCodeModal extends Modal {
|
class ExportSettingsQrCodeModal extends Modal {
|
||||||
plugin: RemotelySavePlugin;
|
plugin: RemotelySavePlugin;
|
||||||
constructor(app: App, plugin: RemotelySavePlugin) {
|
exportType: QRExportType;
|
||||||
|
constructor(app: App, plugin: RemotelySavePlugin, exportType: QRExportType) {
|
||||||
super(app);
|
super(app);
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
|
this.exportType = exportType;
|
||||||
}
|
}
|
||||||
|
|
||||||
async onOpen() {
|
async onOpen() {
|
||||||
@@ -710,7 +730,8 @@ class ExportSettingsQrCodeModal extends Modal {
|
|||||||
const { rawUri, imgUri } = await exportQrCodeUri(
|
const { rawUri, imgUri } = await exportQrCodeUri(
|
||||||
this.plugin.settings,
|
this.plugin.settings,
|
||||||
this.app.vault.getName(),
|
this.app.vault.getName(),
|
||||||
this.plugin.manifest.version
|
this.plugin.manifest.version,
|
||||||
|
this.exportType
|
||||||
);
|
);
|
||||||
|
|
||||||
const div1 = contentEl.createDiv();
|
const div1 = contentEl.createDiv();
|
||||||
@@ -2128,15 +2149,87 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
.setName(t("settings_export"))
|
.setName(t("settings_export"))
|
||||||
.setDesc(t("settings_export_desc"))
|
.setDesc(t("settings_export_desc"))
|
||||||
.addButton(async (button) => {
|
.addButton(async (button) => {
|
||||||
button.setButtonText(t("settings_export_desc_button"));
|
button.setButtonText(t("settings_export_all_but_oauth2_button"));
|
||||||
button.onClick(async () => {
|
button.onClick(async () => {
|
||||||
new ExportSettingsQrCodeModal(this.app, this.plugin).open();
|
new ExportSettingsQrCodeModal(
|
||||||
|
this.app,
|
||||||
|
this.plugin,
|
||||||
|
"all_but_oauth2"
|
||||||
|
).open();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.addButton(async (button) => {
|
||||||
|
button.setButtonText(t("settings_export_dropbox_button"));
|
||||||
|
button.onClick(async () => {
|
||||||
|
new ExportSettingsQrCodeModal(
|
||||||
|
this.app,
|
||||||
|
this.plugin,
|
||||||
|
"dropbox"
|
||||||
|
).open();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.addButton(async (button) => {
|
||||||
|
button.setButtonText(t("settings_export_onedrive_button"));
|
||||||
|
button.onClick(async () => {
|
||||||
|
new ExportSettingsQrCodeModal(
|
||||||
|
this.app,
|
||||||
|
this.plugin,
|
||||||
|
"onedrive"
|
||||||
|
).open();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let importSettingVal = "";
|
||||||
new Setting(importExportDiv)
|
new Setting(importExportDiv)
|
||||||
.setName(t("settings_import"))
|
.setName(t("settings_import"))
|
||||||
.setDesc(t("settings_import_desc"));
|
.setDesc(t("settings_import_desc"))
|
||||||
|
.addText((text) =>
|
||||||
|
text
|
||||||
|
.setPlaceholder("obsidian://remotely-save?func=settings&...")
|
||||||
|
.setValue("")
|
||||||
|
.onChange((val) => {
|
||||||
|
importSettingVal = val;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.addButton(async (button) => {
|
||||||
|
button.setButtonText(t("confirm"));
|
||||||
|
button.onClick(async () => {
|
||||||
|
if (importSettingVal !== "") {
|
||||||
|
// console.debug(importSettingVal);
|
||||||
|
try {
|
||||||
|
const inputParams = parseUriByHand(importSettingVal);
|
||||||
|
const parsed = importQrCodeUri(
|
||||||
|
inputParams,
|
||||||
|
this.app.vault.getName()
|
||||||
|
);
|
||||||
|
if (parsed.status === "error") {
|
||||||
|
new Notice(parsed.message);
|
||||||
|
} else {
|
||||||
|
const copied = cloneDeep(parsed.result);
|
||||||
|
// new Notice(JSON.stringify(copied))
|
||||||
|
this.plugin.settings = Object.assign(
|
||||||
|
{},
|
||||||
|
this.plugin.settings,
|
||||||
|
copied
|
||||||
|
);
|
||||||
|
this.plugin.saveSettings();
|
||||||
|
new Notice(
|
||||||
|
t("protocol_saveqr", {
|
||||||
|
manifestName: this.plugin.manifest.name,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
new Notice(`${e}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
importSettingVal = "";
|
||||||
|
} else {
|
||||||
|
new Notice(t("settings_import_error_notice"));
|
||||||
|
importSettingVal = "";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
//////////////////////////////////////////////////
|
//////////////////////////////////////////////////
|
||||||
// below for debug
|
// below for debug
|
||||||
@@ -2265,6 +2358,21 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
new Setting(debugDiv)
|
||||||
|
.setName(t("settings_profiler_results"))
|
||||||
|
.setDesc(t("settings_profiler_results_desc"))
|
||||||
|
.addButton(async (button) => {
|
||||||
|
button.setButtonText(t("settings_profiler_results_button_all"));
|
||||||
|
button.onClick(async () => {
|
||||||
|
await exportVaultProfilerResultsToFiles(
|
||||||
|
this.plugin.db,
|
||||||
|
this.app.vault,
|
||||||
|
this.plugin.vaultRandomID
|
||||||
|
);
|
||||||
|
new Notice(t("settings_profiler_results_notice"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
new Setting(debugDiv)
|
new Setting(debugDiv)
|
||||||
.setName(t("settings_outputbasepathvaultid"))
|
.setName(t("settings_outputbasepathvaultid"))
|
||||||
.setDesc(t("settings_outputbasepathvaultid_desc"))
|
.setDesc(t("settings_outputbasepathvaultid_desc"))
|
||||||
|
|||||||
+43
-3
@@ -34,6 +34,7 @@ import {
|
|||||||
upsertPrevSyncRecordByVaultAndProfile,
|
upsertPrevSyncRecordByVaultAndProfile,
|
||||||
} from "./localdb";
|
} from "./localdb";
|
||||||
import { Cipher } from "./encryptUnified";
|
import { Cipher } from "./encryptUnified";
|
||||||
|
import { Profiler } from "./profiler";
|
||||||
|
|
||||||
export type SyncStatusType =
|
export type SyncStatusType =
|
||||||
| "idle"
|
| "idle"
|
||||||
@@ -323,8 +324,13 @@ export const ensembleMixedEnties = async (
|
|||||||
syncUnderscoreItems: boolean,
|
syncUnderscoreItems: boolean,
|
||||||
ignorePaths: string[],
|
ignorePaths: string[],
|
||||||
cipher: Cipher,
|
cipher: Cipher,
|
||||||
serviceType: SUPPORTED_SERVICES_TYPE
|
serviceType: SUPPORTED_SERVICES_TYPE,
|
||||||
|
|
||||||
|
profiler: Profiler
|
||||||
): Promise<SyncPlanType> => {
|
): Promise<SyncPlanType> => {
|
||||||
|
profiler.addIndent();
|
||||||
|
profiler.insert("ensembleMixedEnties: enter");
|
||||||
|
|
||||||
const finalMappings: SyncPlanType = {};
|
const finalMappings: SyncPlanType = {};
|
||||||
|
|
||||||
const synthFolders: Record<string, Entity> = {};
|
const synthFolders: Record<string, Entity> = {};
|
||||||
@@ -383,6 +389,8 @@ export const ensembleMixedEnties = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
profiler.insert("ensembleMixedEnties: finish remote");
|
||||||
|
|
||||||
console.debug(`synthFolders:`);
|
console.debug(`synthFolders:`);
|
||||||
console.debug(synthFolders);
|
console.debug(synthFolders);
|
||||||
|
|
||||||
@@ -394,6 +402,8 @@ export const ensembleMixedEnties = async (
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
profiler.insert("ensembleMixedEnties: finish synth");
|
||||||
|
|
||||||
if (Object.keys(finalMappings).length === 0 || localEntityList.length === 0) {
|
if (Object.keys(finalMappings).length === 0 || localEntityList.length === 0) {
|
||||||
// Special checking:
|
// Special checking:
|
||||||
// if one side is totally empty,
|
// if one side is totally empty,
|
||||||
@@ -438,6 +448,8 @@ export const ensembleMixedEnties = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
profiler.insert("ensembleMixedEnties: finish prevSync");
|
||||||
|
|
||||||
// local has to be last
|
// local has to be last
|
||||||
// because we want to get keyEnc based on the remote
|
// because we want to get keyEnc based on the remote
|
||||||
// (we don't consume prevSync here because it gains no benefit)
|
// (we don't consume prevSync here because it gains no benefit)
|
||||||
@@ -475,8 +487,13 @@ export const ensembleMixedEnties = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
profiler.insert("ensembleMixedEnties: finish local");
|
||||||
|
|
||||||
console.debug("in the end of ensembleMixedEnties, finalMappings is:");
|
console.debug("in the end of ensembleMixedEnties, finalMappings is:");
|
||||||
console.debug(finalMappings);
|
console.debug(finalMappings);
|
||||||
|
|
||||||
|
profiler.insert("ensembleMixedEnties: exit");
|
||||||
|
profiler.removeIndent();
|
||||||
return finalMappings;
|
return finalMappings;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -490,12 +507,16 @@ export const getSyncPlanInplace = async (
|
|||||||
howToCleanEmptyFolder: EmptyFolderCleanType,
|
howToCleanEmptyFolder: EmptyFolderCleanType,
|
||||||
skipSizeLargerThan: number,
|
skipSizeLargerThan: number,
|
||||||
conflictAction: ConflictActionType,
|
conflictAction: ConflictActionType,
|
||||||
syncDirection: SyncDirectionType
|
syncDirection: SyncDirectionType,
|
||||||
|
profiler: Profiler
|
||||||
) => {
|
) => {
|
||||||
|
profiler.addIndent();
|
||||||
|
profiler.insert("getSyncPlanInplace: enter");
|
||||||
// from long(deep) to short(shadow)
|
// from long(deep) to short(shadow)
|
||||||
const sortedKeys = Object.keys(mixedEntityMappings).sort(
|
const sortedKeys = Object.keys(mixedEntityMappings).sort(
|
||||||
(k1, k2) => k2.length - k1.length
|
(k1, k2) => k2.length - k1.length
|
||||||
);
|
);
|
||||||
|
profiler.insert("getSyncPlanInplace: finish sorting");
|
||||||
|
|
||||||
const keptFolder = new Set<string>();
|
const keptFolder = new Set<string>();
|
||||||
|
|
||||||
@@ -897,6 +918,8 @@ export const getSyncPlanInplace = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
profiler.insert("getSyncPlanInplace: finish looping");
|
||||||
|
|
||||||
keptFolder.delete("/");
|
keptFolder.delete("/");
|
||||||
keptFolder.delete("");
|
keptFolder.delete("");
|
||||||
if (keptFolder.size > 0) {
|
if (keptFolder.size > 0) {
|
||||||
@@ -916,6 +939,9 @@ export const getSyncPlanInplace = async (
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
profiler.insert("getSyncPlanInplace: exit");
|
||||||
|
profiler.removeIndent();
|
||||||
|
|
||||||
return mixedEntityMappings;
|
return mixedEntityMappings;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1261,8 +1287,11 @@ export const doActualSync = async (
|
|||||||
protectModifyPercentage: number,
|
protectModifyPercentage: number,
|
||||||
getProtectModifyPercentageErrorStrFunc: any,
|
getProtectModifyPercentageErrorStrFunc: any,
|
||||||
callbackSyncProcess: any,
|
callbackSyncProcess: any,
|
||||||
db: InternalDBs
|
db: InternalDBs,
|
||||||
|
profiler: Profiler
|
||||||
) => {
|
) => {
|
||||||
|
profiler.addIndent();
|
||||||
|
profiler.insert("doActualSync: enter");
|
||||||
console.debug(`concurrency === ${concurrency}`);
|
console.debug(`concurrency === ${concurrency}`);
|
||||||
const {
|
const {
|
||||||
onlyMarkSyncedOps,
|
onlyMarkSyncedOps,
|
||||||
@@ -1280,6 +1309,7 @@ export const doActualSync = async (
|
|||||||
console.debug(`allFilesCount: ${allFilesCount}`);
|
console.debug(`allFilesCount: ${allFilesCount}`);
|
||||||
console.debug(`realModifyDeleteCount: ${realModifyDeleteCount}`);
|
console.debug(`realModifyDeleteCount: ${realModifyDeleteCount}`);
|
||||||
console.debug(`realTotalCount: ${realTotalCount}`);
|
console.debug(`realTotalCount: ${realTotalCount}`);
|
||||||
|
profiler.insert("doActualSync: finish splitting steps");
|
||||||
|
|
||||||
console.debug(`protectModifyPercentage: ${protectModifyPercentage}`);
|
console.debug(`protectModifyPercentage: ${protectModifyPercentage}`);
|
||||||
|
|
||||||
@@ -1304,6 +1334,8 @@ export const doActualSync = async (
|
|||||||
allFilesCount
|
allFilesCount
|
||||||
);
|
);
|
||||||
|
|
||||||
|
profiler.insert("doActualSync: error branch");
|
||||||
|
profiler.removeIndent();
|
||||||
throw Error(errorStr);
|
throw Error(errorStr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1323,6 +1355,8 @@ export const doActualSync = async (
|
|||||||
|
|
||||||
let realCounter = 0;
|
let realCounter = 0;
|
||||||
for (let i = 0; i < nested.length; ++i) {
|
for (let i = 0; i < nested.length; ++i) {
|
||||||
|
profiler.addIndent();
|
||||||
|
profiler.insert(`doActualSync: step ${i} start`);
|
||||||
console.debug(logTexts[i]);
|
console.debug(logTexts[i]);
|
||||||
|
|
||||||
const operations = nested[i];
|
const operations = nested[i];
|
||||||
@@ -1398,5 +1432,11 @@ export const doActualSync = async (
|
|||||||
throw new AggregateError(potentialErrors);
|
throw new AggregateError(potentialErrors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
profiler.insert(`doActualSync: step ${i} end`);
|
||||||
|
profiler.removeIndent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
profiler.insert(`doActualSync: exit`);
|
||||||
|
profiler.removeIndent();
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user